diff --git a/.github/package-lock.json b/.github/package-lock.json
index 2e430ce056..bea1c66e46 100644
--- a/.github/package-lock.json
+++ b/.github/package-lock.json
@@ -9,9 +9,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
- "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml
index b128c3f80f..2a6c6261a8 100644
--- a/.github/workflows/static_analysis.yml
+++ b/.github/workflows/static_analysis.yml
@@ -42,6 +42,9 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
+ defaults:
+ run:
+ working-directory: ./mobile
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -56,27 +59,23 @@ jobs:
- name: Install dependencies
run: dart pub get
- working-directory: ./mobile
- name: Install DCM
- run: |
- sudo apt-get update
- wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg
- echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list
- sudo apt-get update
- sudo apt-get install dcm
+ # TODO: Move to upstream after https://github.com/CQLabs/setup-dcm/pull/235 merges
+ uses: bo0tzz/setup-dcm@b4952ab813659c03513b57bd78bfe3f634171f8a
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ version: auto
+ working-directory: ./mobile
- name: Generate translation file
run: make translation
- working-directory: ./mobile
- name: Run Build Runner
run: make build
- working-directory: ./mobile
- name: Generate platform API
run: make pigeon
- working-directory: ./mobile
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
@@ -98,19 +97,16 @@ jobs:
- name: Run dart analyze
run: dart analyze --fatal-infos
- working-directory: ./mobile
- name: Run dart format
run: dart format lib/ --set-exit-if-changed
- working-directory: ./mobile
- name: Run dart custom_lint
run: dart run custom_lint
- working-directory: ./mobile
+ # TODO: Use https://github.com/CQLabs/dcm-action
- name: Run DCM
run: dcm analyze lib --fatal-style --fatal-warnings
- working-directory: ./mobile
zizmor:
name: zizmor
diff --git a/cli/package-lock.json b/cli/package-lock.json
index b690003395..0bb42db17c 100644
--- a/cli/package-lock.json
+++ b/cli/package-lock.json
@@ -16,7 +16,7 @@
"micromatch": "^4.0.8"
},
"bin": {
- "immich": "dist/index.js"
+ "immich": "bin/immich"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -607,9 +607,9 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
- "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -622,9 +622,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
- "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -682,9 +682,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
- "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
+ "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1365,17 +1365,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
- "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+ "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/type-utils": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/type-utils": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -1389,7 +1389,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.35.0",
+ "@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -1405,16 +1405,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
- "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+ "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1430,14 +1430,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
+ "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
+ "@typescript-eslint/tsconfig-utils": "^8.35.1",
+ "@typescript-eslint/types": "^8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1452,14 +1452,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
+ "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1470,9 +1470,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
+ "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1487,14 +1487,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
- "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
+ "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -1511,9 +1511,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
+ "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1525,16 +1525,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
+ "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/project-service": "8.35.1",
+ "@typescript-eslint/tsconfig-utils": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -1580,16 +1580,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
- "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
+ "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0"
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1604,13 +1604,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
+ "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
+ "@typescript-eslint/types": "8.35.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2305,19 +2305,19 @@
}
},
"node_modules/eslint": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
- "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
+ "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.1",
- "@eslint/config-helpers": "^0.2.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.29.0",
+ "@eslint/js": "9.30.1",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -2822,9 +2822,9 @@
}
},
"node_modules/globals": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
- "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3505,9 +3505,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
- "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -4125,15 +4125,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
- "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
+ "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.35.0",
- "@typescript-eslint/parser": "8.35.0",
- "@typescript-eslint/utils": "8.35.0"
+ "@typescript-eslint/eslint-plugin": "8.35.1",
+ "@typescript-eslint/parser": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/docs/docs/administration/oauth.md b/docs/docs/administration/oauth.md
index 5105d2eb75..833b70f77a 100644
--- a/docs/docs/administration/oauth.md
+++ b/docs/docs/administration/oauth.md
@@ -62,6 +62,7 @@ Once you have a new OAuth client application configured, Immich can be configure
| Scope | string | openid email profile | Full list of scopes to send with the request (space delimited) |
| Signing Algorithm | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** |
+| Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** |
| Storage Quota Claim | string | immich_quota | Claim mapping for the user's storage**¹** |
| Default Storage Quota (GiB) | number | 0 | Default quota for user without storage quota claim (Enter 0 for unlimited quota) |
| Button Text | string | Login with OAuth | Text for the OAuth button on the web |
diff --git a/docs/package-lock.json b/docs/package-lock.json
index b2cafa05f0..53ac997e3b 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -16459,9 +16459,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
- "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
diff --git a/e2e/package-lock.json b/e2e/package-lock.json
index 162c863832..2987223fe0 100644
--- a/e2e/package-lock.json
+++ b/e2e/package-lock.json
@@ -56,7 +56,7 @@
"micromatch": "^4.0.8"
},
"bin": {
- "immich": "dist/index.js"
+ "immich": "bin/immich"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -658,9 +658,9 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
- "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -673,9 +673,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz",
- "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -733,9 +733,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
- "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
+ "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1509,13 +1509,13 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.53.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz",
- "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==",
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz",
+ "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.53.1"
+ "playwright": "1.53.2"
},
"bin": {
"playwright": "cli.js"
@@ -2028,66 +2028,6 @@
"pg-types": "^2.2.0"
}
},
- "node_modules/@types/pg/node_modules/pg-types": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
- "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pg-int8": "1.0.1",
- "postgres-array": "~2.0.0",
- "postgres-bytea": "~1.0.0",
- "postgres-date": "~1.0.4",
- "postgres-interval": "^1.1.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@types/pg/node_modules/postgres-array": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@types/pg/node_modules/postgres-bytea": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
- "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/@types/pg/node_modules/postgres-date": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/@types/pg/node_modules/postgres-interval": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "xtend": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/@types/pngjs": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz",
@@ -2160,17 +2100,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
- "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+ "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/type-utils": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/type-utils": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2184,7 +2124,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.35.0",
+ "@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -2200,16 +2140,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
- "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+ "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -2225,14 +2165,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
+ "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
+ "@typescript-eslint/tsconfig-utils": "^8.35.1",
+ "@typescript-eslint/types": "^8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -2247,14 +2187,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
+ "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2265,9 +2205,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
+ "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2282,14 +2222,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
- "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
+ "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2306,9 +2246,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
+ "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2320,16 +2260,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
+ "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/project-service": "8.35.1",
+ "@typescript-eslint/tsconfig-utils": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2375,16 +2315,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
- "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
+ "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0"
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2399,13 +2339,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
+ "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
+ "@typescript-eslint/types": "8.35.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -3498,19 +3438,19 @@
}
},
"node_modules/eslint": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
- "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
+ "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.1",
- "@eslint/config-helpers": "^0.2.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.29.0",
+ "@eslint/js": "9.30.1",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -4279,9 +4219,9 @@
}
},
"node_modules/globals": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
- "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5430,15 +5370,15 @@
}
},
"node_modules/pg": {
- "version": "8.16.2",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz",
- "integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==",
+ "version": "8.16.3",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
+ "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
- "pg-protocol": "^1.10.2",
+ "pg-protocol": "^1.10.3",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
@@ -5446,7 +5386,7 @@
"node": ">= 16.0.0"
},
"optionalDependencies": {
- "pg-cloudflare": "^1.2.6"
+ "pg-cloudflare": "^1.2.7"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
@@ -5499,7 +5439,7 @@
"dev": true,
"license": "MIT"
},
- "node_modules/pg/node_modules/pg-types": {
+ "node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
@@ -5516,49 +5456,6 @@
"node": ">=4"
}
},
- "node_modules/pg/node_modules/postgres-array": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/pg/node_modules/postgres-bytea": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
- "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/pg/node_modules/postgres-date": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/pg/node_modules/postgres-interval": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "xtend": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
@@ -5590,13 +5487,13 @@
}
},
"node_modules/playwright": {
- "version": "1.53.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
- "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==",
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz",
+ "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.53.1"
+ "playwright-core": "1.53.2"
},
"bin": {
"playwright": "cli.js"
@@ -5609,9 +5506,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.53.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz",
- "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==",
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz",
+ "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -5689,6 +5586,49 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5700,9 +5640,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
- "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -6837,15 +6777,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
- "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
+ "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.35.0",
- "@typescript-eslint/parser": "8.35.0",
- "@typescript-eslint/utils": "8.35.0"
+ "@typescript-eslint/eslint-plugin": "8.35.1",
+ "@typescript-eslint/parser": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/e2e/src/api/specs/oauth.e2e-spec.ts b/e2e/src/api/specs/oauth.e2e-spec.ts
index 9e4d64892e..58fc43a2d5 100644
--- a/e2e/src/api/specs/oauth.e2e-spec.ts
+++ b/e2e/src/api/specs/oauth.e2e-spec.ts
@@ -227,6 +227,21 @@ describe(`/oauth`, () => {
expect(user.storageLabel).toBe('user-username');
});
+ it('should set the admin status from a role claim', async () => {
+ const callbackParams = await loginWithOAuth(OAuthUser.WITH_ROLE);
+ const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
+ expect(status).toBe(201);
+ expect(body).toMatchObject({
+ accessToken: expect.any(String),
+ userId: expect.any(String),
+ userEmail: 'oauth-with-role@immich.app',
+ isAdmin: true,
+ });
+
+ const user = await getMyUser({ headers: asBearerAuth(body.accessToken) });
+ expect(user.isAdmin).toBe(true);
+ });
+
it('should work with RS256 signed tokens', async () => {
await setupOAuth(admin.accessToken, {
enabled: true,
diff --git a/e2e/src/setup/auth-server.ts b/e2e/src/setup/auth-server.ts
index 575e97d291..489bda2ee4 100644
--- a/e2e/src/setup/auth-server.ts
+++ b/e2e/src/setup/auth-server.ts
@@ -12,6 +12,7 @@ export enum OAuthUser {
NO_NAME = 'no-name',
WITH_QUOTA = 'with-quota',
WITH_USERNAME = 'with-username',
+ WITH_ROLE = 'with-role',
}
const claims = [
@@ -34,6 +35,12 @@ const claims = [
preferred_username: 'user-quota',
immich_quota: 25,
},
+ {
+ sub: OAuthUser.WITH_ROLE,
+ email: 'oauth-with-role@immich.app',
+ email_verified: true,
+ immich_role: 'admin',
+ },
];
const withDefaultClaims = (sub: string) => ({
@@ -64,7 +71,15 @@ const setup = async () => {
claims: {
openid: ['sub'],
email: ['email', 'email_verified'],
- profile: ['name', 'given_name', 'family_name', 'preferred_username', 'immich_quota', 'immich_username'],
+ profile: [
+ 'name',
+ 'given_name',
+ 'family_name',
+ 'preferred_username',
+ 'immich_quota',
+ 'immich_username',
+ 'immich_role',
+ ],
},
features: {
jwtUserinfo: {
diff --git a/i18n/en.json b/i18n/en.json
index 91a55cc85f..e5c995f957 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -196,6 +196,8 @@
"oauth_mobile_redirect_uri": "Mobile redirect URI",
"oauth_mobile_redirect_uri_override": "Mobile redirect URI override",
"oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''",
+ "oauth_role_claim": "Role Claim",
+ "oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.",
"oauth_settings": "OAuth",
"oauth_settings_description": "Manage OAuth login settings",
"oauth_settings_more_details": "For more details about this feature, refer to the docs.",
diff --git a/mobile/drift_schemas/main/drift_schema_v1.json b/mobile/drift_schemas/main/drift_schema_v1.json
index 32ba52e366..493a34cc94 100644
--- a/mobile/drift_schemas/main/drift_schema_v1.json
+++ b/mobile/drift_schemas/main/drift_schema_v1.json
@@ -1,1331 +1 @@
-{
- "_meta": {
- "description": "This file contains a serialized version of schema entities for drift.",
- "version": "1.2.0"
- },
- "options": { "store_date_time_values_as_text": true },
- "entities": [
- {
- "id": 0,
- "references": [],
- "type": "table",
- "data": {
- "name": "user_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "id",
- "getter_name": "id",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "name",
- "getter_name": "name",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "is_admin",
- "getter_name": "isAdmin",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"is_admin\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "email",
- "getter_name": "email",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "profile_image_path",
- "getter_name": "profileImagePath",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "updated_at",
- "getter_name": "updatedAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "quota_size_in_bytes",
- "getter_name": "quotaSizeInBytes",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "quota_usage_in_bytes",
- "getter_name": "quotaUsageInBytes",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["id"]
- }
- },
- {
- "id": 1,
- "references": [0],
- "type": "table",
- "data": {
- "name": "remote_asset_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "name",
- "getter_name": "name",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "type",
- "getter_name": "type",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(AssetType.values)",
- "dart_type_name": "AssetType"
- }
- },
- {
- "name": "created_at",
- "getter_name": "createdAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "updated_at",
- "getter_name": "updatedAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "width",
- "getter_name": "width",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "height",
- "getter_name": "height",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "duration_in_seconds",
- "getter_name": "durationInSeconds",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "id",
- "getter_name": "id",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "checksum",
- "getter_name": "checksum",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "is_favorite",
- "getter_name": "isFavorite",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"is_favorite\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "owner_id",
- "getter_name": "ownerId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "local_date_time",
- "getter_name": "localDateTime",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "thumb_hash",
- "getter_name": "thumbHash",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "deleted_at",
- "getter_name": "deletedAt",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "visibility",
- "getter_name": "visibility",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(AssetVisibility.values)",
- "dart_type_name": "AssetVisibility"
- }
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["id"]
- }
- },
- {
- "id": 2,
- "references": [],
- "type": "table",
- "data": {
- "name": "local_asset_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "name",
- "getter_name": "name",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "type",
- "getter_name": "type",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(AssetType.values)",
- "dart_type_name": "AssetType"
- }
- },
- {
- "name": "created_at",
- "getter_name": "createdAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "updated_at",
- "getter_name": "updatedAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "width",
- "getter_name": "width",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "height",
- "getter_name": "height",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "duration_in_seconds",
- "getter_name": "durationInSeconds",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "id",
- "getter_name": "id",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "checksum",
- "getter_name": "checksum",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "is_favorite",
- "getter_name": "isFavorite",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"is_favorite\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["id"]
- }
- },
- {
- "id": 3,
- "references": [2],
- "type": "index",
- "data": {
- "on": 2,
- "name": "idx_local_asset_checksum",
- "sql": null,
- "unique": false,
- "columns": ["checksum"]
- }
- },
- {
- "id": 4,
- "references": [1],
- "type": "index",
- "data": {
- "on": 1,
- "name": "UQ_remote_asset_owner_checksum",
- "sql": null,
- "unique": true,
- "columns": ["checksum", "owner_id"]
- }
- },
- {
- "id": 5,
- "references": [1],
- "type": "index",
- "data": {
- "on": 1,
- "name": "idx_remote_asset_checksum",
- "sql": null,
- "unique": false,
- "columns": ["checksum"]
- }
- },
- {
- "id": 6,
- "references": [0],
- "type": "table",
- "data": {
- "name": "user_metadata_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "user_id",
- "getter_name": "userId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "preferences",
- "getter_name": "preferences",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "userPreferenceConverter",
- "dart_type_name": "UserPreferences"
- }
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["user_id"]
- }
- },
- {
- "id": 7,
- "references": [0],
- "type": "table",
- "data": {
- "name": "partner_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "shared_by_id",
- "getter_name": "sharedById",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "shared_with_id",
- "getter_name": "sharedWithId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "in_timeline",
- "getter_name": "inTimeline",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"in_timeline\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["shared_by_id", "shared_with_id"]
- }
- },
- {
- "id": 8,
- "references": [],
- "type": "table",
- "data": {
- "name": "local_album_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "id",
- "getter_name": "id",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "name",
- "getter_name": "name",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "updated_at",
- "getter_name": "updatedAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "backup_selection",
- "getter_name": "backupSelection",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(BackupSelection.values)",
- "dart_type_name": "BackupSelection"
- }
- },
- {
- "name": "is_ios_shared_album",
- "getter_name": "isIosSharedAlbum",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "marker",
- "getter_name": "marker_",
- "moor_type": "bool",
- "nullable": true,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"marker\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"marker\" IN (0, 1))"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["id"]
- }
- },
- {
- "id": 9,
- "references": [2, 8],
- "type": "table",
- "data": {
- "name": "local_album_asset_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "asset_id",
- "getter_name": "assetId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "album_id",
- "getter_name": "albumId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["asset_id", "album_id"]
- }
- },
- {
- "id": 10,
- "references": [1],
- "type": "table",
- "data": {
- "name": "remote_exif_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "asset_id",
- "getter_name": "assetId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "city",
- "getter_name": "city",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "state",
- "getter_name": "state",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "country",
- "getter_name": "country",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "date_time_original",
- "getter_name": "dateTimeOriginal",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "description",
- "getter_name": "description",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "height",
- "getter_name": "height",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "width",
- "getter_name": "width",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "exposure_time",
- "getter_name": "exposureTime",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "f_number",
- "getter_name": "fNumber",
- "moor_type": "double",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "file_size",
- "getter_name": "fileSize",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "focal_length",
- "getter_name": "focalLength",
- "moor_type": "double",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "latitude",
- "getter_name": "latitude",
- "moor_type": "double",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "longitude",
- "getter_name": "longitude",
- "moor_type": "double",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "iso",
- "getter_name": "iso",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "make",
- "getter_name": "make",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "model",
- "getter_name": "model",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "orientation",
- "getter_name": "orientation",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "time_zone",
- "getter_name": "timeZone",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "rating",
- "getter_name": "rating",
- "moor_type": "int",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "projection_type",
- "getter_name": "projectionType",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["asset_id"]
- }
- },
- {
- "id": 11,
- "references": [0, 1],
- "type": "table",
- "data": {
- "name": "remote_album_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "id",
- "getter_name": "id",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "name",
- "getter_name": "name",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "description",
- "getter_name": "description",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('\\'\\'')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "created_at",
- "getter_name": "createdAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "updated_at",
- "getter_name": "updatedAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "owner_id",
- "getter_name": "ownerId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "thumbnail_asset_id",
- "getter_name": "thumbnailAssetId",
- "moor_type": "string",
- "nullable": true,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "is_activity_enabled",
- "getter_name": "isActivityEnabled",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('1')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "order",
- "getter_name": "order",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)",
- "dart_type_name": "AlbumAssetOrder"
- }
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["id"]
- }
- },
- {
- "id": 12,
- "references": [1, 11],
- "type": "table",
- "data": {
- "name": "remote_album_asset_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "asset_id",
- "getter_name": "assetId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "album_id",
- "getter_name": "albumId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["asset_id", "album_id"]
- }
- },
- {
- "id": 13,
- "references": [11, 0],
- "type": "table",
- "data": {
- "name": "remote_album_user_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "album_id",
- "getter_name": "albumId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "user_id",
- "getter_name": "userId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "role",
- "getter_name": "role",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)",
- "dart_type_name": "AlbumUserRole"
- }
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["album_id", "user_id"]
- }
- },
- {
- "id": 14,
- "references": [0],
- "type": "table",
- "data": {
- "name": "memory_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "id",
- "getter_name": "id",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "created_at",
- "getter_name": "createdAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "updated_at",
- "getter_name": "updatedAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "deleted_at",
- "getter_name": "deletedAt",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "owner_id",
- "getter_name": "ownerId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "type",
- "getter_name": "type",
- "moor_type": "int",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": [],
- "type_converter": {
- "dart_expr": "const EnumIndexConverter(MemoryType.values)",
- "dart_type_name": "MemoryType"
- }
- },
- {
- "name": "data",
- "getter_name": "data",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "is_saved",
- "getter_name": "isSaved",
- "moor_type": "bool",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))",
- "dialectAwareDefaultConstraints": {
- "sqlite": "CHECK (\"is_saved\" IN (0, 1))"
- },
- "default_dart": "const CustomExpression('0')",
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "memory_at",
- "getter_name": "memoryAt",
- "moor_type": "dateTime",
- "nullable": false,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "seen_at",
- "getter_name": "seenAt",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "show_at",
- "getter_name": "showAt",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- },
- {
- "name": "hide_at",
- "getter_name": "hideAt",
- "moor_type": "dateTime",
- "nullable": true,
- "customConstraints": null,
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": []
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["id"]
- }
- },
- {
- "id": 15,
- "references": [1, 14],
- "type": "table",
- "data": {
- "name": "memory_asset_entity",
- "was_declared_in_moor": false,
- "columns": [
- {
- "name": "asset_id",
- "getter_name": "assetId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- },
- {
- "name": "memory_id",
- "getter_name": "memoryId",
- "moor_type": "string",
- "nullable": false,
- "customConstraints": null,
- "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE",
- "dialectAwareDefaultConstraints": {
- "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE"
- },
- "default_dart": null,
- "default_client_dart": null,
- "dsl_features": ["unknown"]
- }
- ],
- "is_virtual": false,
- "without_rowid": true,
- "constraints": [],
- "strict": true,
- "explicit_pk": ["asset_id", "memory_id"]
- }
- }
- ]
-}
+{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_admin","getter_name":"isAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"profile_image_path","getter_name":"profileImagePath","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"quota_usage_in_bytes","getter_name":"quotaUsageInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"remote_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"local_date_time","getter_name":"localDateTime","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"thumb_hash","getter_name":"thumbHash","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"visibility","getter_name":"visibility","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetVisibility.values)","dart_type_name":"AssetVisibility"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":2,"references":[],"type":"table","data":{"name":"local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":3,"references":[2],"type":"index","data":{"on":2,"name":"idx_local_asset_checksum","sql":null,"unique":false,"columns":["checksum"]}},{"id":4,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_asset_owner_checksum","sql":null,"unique":true,"columns":["checksum","owner_id"]}},{"id":5,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_checksum","sql":null,"unique":false,"columns":["checksum"]}},{"id":6,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"preferences","getter_name":"preferences","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userPreferenceConverter","dart_type_name":"UserPreferences"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id"]}},{"id":7,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}},{"id":8,"references":[],"type":"table","data":{"name":"local_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"backup_selection","getter_name":"backupSelection","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BackupSelection.values)","dart_type_name":"BackupSelection"}},{"name":"is_ios_shared_album","getter_name":"isIosSharedAlbum","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_ios_shared_album\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_ios_shared_album\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":9,"references":[2,8],"type":"table","data":{"name":"local_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":10,"references":[1],"type":"table","data":{"name":"remote_exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"city","getter_name":"city","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"country","getter_name":"country","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"date_time_original","getter_name":"dateTimeOriginal","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"exposure_time","getter_name":"exposureTime","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"f_number","getter_name":"fNumber","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_size","getter_name":"fileSize","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"focal_length","getter_name":"focalLength","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"iso","getter_name":"iso","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"make","getter_name":"make","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"model","getter_name":"model","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"lens","getter_name":"lens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"rating","getter_name":"rating","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"projection_type","getter_name":"projectionType","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id"]}},{"id":11,"references":[0,1],"type":"table","data":{"name":"remote_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'\\'')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"thumbnail_asset_id","getter_name":"thumbnailAssetId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"is_activity_enabled","getter_name":"isActivityEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_activity_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_activity_enabled\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"order","getter_name":"order","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumAssetOrder.values)","dart_type_name":"AlbumAssetOrder"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":12,"references":[1,11],"type":"table","data":{"name":"remote_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":13,"references":[11,0],"type":"table","data":{"name":"remote_album_user_entity","was_declared_in_moor":false,"columns":[{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"role","getter_name":"role","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumUserRole.values)","dart_type_name":"AlbumUserRole"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["album_id","user_id"]}},{"id":14,"references":[0],"type":"table","data":{"name":"memory_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(MemoryTypeEnum.values)","dart_type_name":"MemoryTypeEnum"}},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_saved","getter_name":"isSaved","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_saved\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_saved\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"memory_at","getter_name":"memoryAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"seen_at","getter_name":"seenAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"show_at","getter_name":"showAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"hide_at","getter_name":"hideAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":15,"references":[1,14],"type":"table","data":{"name":"memory_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"memory_id","getter_name":"memoryId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES memory_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES memory_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","memory_id"]}},{"id":16,"references":[0,1],"type":"table","data":{"name":"stack_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"primary_asset_id","getter_name":"primaryAssetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}}]}
\ No newline at end of file
diff --git a/mobile/lib/domain/models/stack.model.dart b/mobile/lib/domain/models/stack.model.dart
new file mode 100644
index 0000000000..5404eb8f42
--- /dev/null
+++ b/mobile/lib/domain/models/stack.model.dart
@@ -0,0 +1,84 @@
+import 'dart:convert';
+
+// Model for a stack stored in the server
+class Stack {
+ final String id;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+ final String ownerId;
+ final String primaryAssetId;
+
+ const Stack({
+ required this.id,
+ required this.createdAt,
+ required this.updatedAt,
+ required this.ownerId,
+ required this.primaryAssetId,
+ });
+
+ Stack copyWith({
+ String? id,
+ DateTime? createdAt,
+ DateTime? updatedAt,
+ String? ownerId,
+ String? primaryAssetId,
+ }) {
+ return Stack(
+ id: id ?? this.id,
+ createdAt: createdAt ?? this.createdAt,
+ updatedAt: updatedAt ?? this.updatedAt,
+ ownerId: ownerId ?? this.ownerId,
+ primaryAssetId: primaryAssetId ?? this.primaryAssetId,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'id': id,
+ 'createdAt': createdAt.millisecondsSinceEpoch,
+ 'updatedAt': updatedAt.millisecondsSinceEpoch,
+ 'ownerId': ownerId,
+ 'primaryAssetId': primaryAssetId,
+ };
+ }
+
+ factory Stack.fromMap(Map map) {
+ return Stack(
+ id: map['id'] as String,
+ createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt'] as int),
+ updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int),
+ ownerId: map['ownerId'] as String,
+ primaryAssetId: map['primaryAssetId'] as String,
+ );
+ }
+
+ String toJson() => json.encode(toMap());
+
+ factory Stack.fromJson(String source) =>
+ Stack.fromMap(json.decode(source) as Map);
+
+ @override
+ String toString() {
+ return 'Stack(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, ownerId: $ownerId, primaryAssetId: $primaryAssetId)';
+ }
+
+ @override
+ bool operator ==(covariant Stack other) {
+ if (identical(this, other)) return true;
+
+ return other.id == id &&
+ other.createdAt == createdAt &&
+ other.updatedAt == updatedAt &&
+ other.ownerId == ownerId &&
+ other.primaryAssetId == primaryAssetId;
+ }
+
+ @override
+ int get hashCode {
+ return id.hashCode ^
+ createdAt.hashCode ^
+ updatedAt.hashCode ^
+ ownerId.hashCode ^
+ primaryAssetId.hashCode;
+ }
+}
diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart
index c4e40726b5..ee0ec6c44f 100644
--- a/mobile/lib/domain/services/sync_stream.service.dart
+++ b/mobile/lib/domain/services/sync_stream.service.dart
@@ -154,6 +154,25 @@ class SyncStreamService {
return _syncStreamRepository.updateMemoryAssetsV1(data.cast());
case SyncEntityType.memoryToAssetDeleteV1:
return _syncStreamRepository.deleteMemoryAssetsV1(data.cast());
+ case SyncEntityType.stackV1:
+ return _syncStreamRepository.updateStacksV1(data.cast());
+ case SyncEntityType.stackDeleteV1:
+ return _syncStreamRepository.deleteStacksV1(data.cast());
+ case SyncEntityType.partnerStackV1:
+ return _syncStreamRepository.updateStacksV1(
+ data.cast(),
+ debugLabel: 'partner',
+ );
+ case SyncEntityType.partnerStackBackfillV1:
+ return _syncStreamRepository.updateStacksV1(
+ data.cast(),
+ debugLabel: 'partner backfill',
+ );
+ case SyncEntityType.partnerStackDeleteV1:
+ return _syncStreamRepository.deleteStacksV1(
+ data.cast(),
+ debugLabel: 'partner',
+ );
default:
_logger.warning("Unknown sync data type: $type");
}
diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart
index 618ccd250f..5cf8341a4d 100644
--- a/mobile/lib/domain/services/timeline.service.dart
+++ b/mobile/lib/domain/services/timeline.service.dart
@@ -53,6 +53,43 @@ class TimelineFactory {
bucketSource: () =>
_timelineRepository.watchRemoteBucket(albumId, groupBy: groupBy),
);
+
+ TimelineService favorite(String userId) => TimelineService(
+ assetSource: (offset, count) => _timelineRepository
+ .getFavoriteBucketAssets(userId, offset: offset, count: count),
+ bucketSource: () =>
+ _timelineRepository.watchFavoriteBucket(userId, groupBy: groupBy),
+ );
+
+ TimelineService trash(String userId) => TimelineService(
+ assetSource: (offset, count) => _timelineRepository
+ .getTrashBucketAssets(userId, offset: offset, count: count),
+ bucketSource: () =>
+ _timelineRepository.watchTrashBucket(userId, groupBy: groupBy),
+ );
+
+ TimelineService archive(String userId) => TimelineService(
+ assetSource: (offset, count) => _timelineRepository
+ .getArchiveBucketAssets(userId, offset: offset, count: count),
+ bucketSource: () =>
+ _timelineRepository.watchArchiveBucket(userId, groupBy: groupBy),
+ );
+
+ TimelineService lockedFolder(String userId) => TimelineService(
+ assetSource: (offset, count) => _timelineRepository
+ .getLockedFolderBucketAssets(userId, offset: offset, count: count),
+ bucketSource: () => _timelineRepository.watchLockedFolderBucket(
+ userId,
+ groupBy: groupBy,
+ ),
+ );
+
+ TimelineService video(String userId) => TimelineService(
+ assetSource: (offset, count) => _timelineRepository
+ .getVideoBucketAssets(userId, offset: offset, count: count),
+ bucketSource: () =>
+ _timelineRepository.watchVideoBucket(userId, groupBy: groupBy),
+ );
}
class TimelineService {
diff --git a/mobile/lib/infrastructure/entities/stack.entity.dart b/mobile/lib/infrastructure/entities/stack.entity.dart
new file mode 100644
index 0000000000..92375f19db
--- /dev/null
+++ b/mobile/lib/infrastructure/entities/stack.entity.dart
@@ -0,0 +1,22 @@
+import 'package:drift/drift.dart';
+import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
+import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
+import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
+
+class StackEntity extends Table with DriftDefaultsMixin {
+ const StackEntity();
+
+ TextColumn get id => text()();
+
+ DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
+
+ DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
+
+ TextColumn get ownerId =>
+ text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
+
+ TextColumn get primaryAssetId => text().references(RemoteAssetEntity, #id)();
+
+ @override
+ Set get primaryKey => {id};
+}
diff --git a/mobile/lib/infrastructure/entities/stack.entity.drift.dart b/mobile/lib/infrastructure/entities/stack.entity.drift.dart
new file mode 100644
index 0000000000..c0d000e02a
--- /dev/null
+++ b/mobile/lib/infrastructure/entities/stack.entity.drift.dart
@@ -0,0 +1,706 @@
+// dart format width=80
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart' as i0;
+import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
+ as i1;
+import 'package:immich_mobile/infrastructure/entities/stack.entity.dart' as i2;
+import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
+import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
+ as i4;
+import 'package:drift/internal/modular.dart' as i5;
+import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
+ as i6;
+
+typedef $$StackEntityTableCreateCompanionBuilder = i1.StackEntityCompanion
+ Function({
+ required String id,
+ i0.Value createdAt,
+ i0.Value updatedAt,
+ required String ownerId,
+ required String primaryAssetId,
+});
+typedef $$StackEntityTableUpdateCompanionBuilder = i1.StackEntityCompanion
+ Function({
+ i0.Value id,
+ i0.Value createdAt,
+ i0.Value updatedAt,
+ i0.Value ownerId,
+ i0.Value primaryAssetId,
+});
+
+final class $$StackEntityTableReferences extends i0.BaseReferences<
+ i0.GeneratedDatabase, i1.$StackEntityTable, i1.StackEntityData> {
+ $$StackEntityTableReferences(super.$_db, super.$_table, super.$_typedResult);
+
+ static i4.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
+ i5.ReadDatabaseContainer(db)
+ .resultSet('user_entity')
+ .createAlias(i0.$_aliasNameGenerator(
+ i5.ReadDatabaseContainer(db)
+ .resultSet('stack_entity')
+ .ownerId,
+ i5.ReadDatabaseContainer(db)
+ .resultSet('user_entity')
+ .id));
+
+ i4.$$UserEntityTableProcessedTableManager get ownerId {
+ final $_column = $_itemColumn('owner_id')!;
+
+ final manager = i4
+ .$$UserEntityTableTableManager(
+ $_db,
+ i5.ReadDatabaseContainer($_db)
+ .resultSet('user_entity'))
+ .filter((f) => f.id.sqlEquals($_column));
+ final item = $_typedResult.readTableOrNull(_ownerIdTable($_db));
+ if (item == null) return manager;
+ return i0.ProcessedTableManager(
+ manager.$state.copyWith(prefetchedData: [item]));
+ }
+
+ static i6.$RemoteAssetEntityTable _primaryAssetIdTable(
+ i0.GeneratedDatabase db) =>
+ i5.ReadDatabaseContainer(db)
+ .resultSet('remote_asset_entity')
+ .createAlias(i0.$_aliasNameGenerator(
+ i5.ReadDatabaseContainer(db)
+ .resultSet('stack_entity')
+ .primaryAssetId,
+ i5.ReadDatabaseContainer(db)
+ .resultSet('remote_asset_entity')
+ .id));
+
+ i6.$$RemoteAssetEntityTableProcessedTableManager get primaryAssetId {
+ final $_column = $_itemColumn('primary_asset_id')!;
+
+ final manager = i6
+ .$$RemoteAssetEntityTableTableManager(
+ $_db,
+ i5.ReadDatabaseContainer($_db)
+ .resultSet('remote_asset_entity'))
+ .filter((f) => f.id.sqlEquals($_column));
+ final item = $_typedResult.readTableOrNull(_primaryAssetIdTable($_db));
+ if (item == null) return manager;
+ return i0.ProcessedTableManager(
+ manager.$state.copyWith(prefetchedData: [item]));
+ }
+}
+
+class $$StackEntityTableFilterComposer
+ extends i0.Composer {
+ $$StackEntityTableFilterComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnFilters get id => $composableBuilder(
+ column: $table.id, builder: (column) => i0.ColumnFilters(column));
+
+ i0.ColumnFilters get createdAt => $composableBuilder(
+ column: $table.createdAt, builder: (column) => i0.ColumnFilters(column));
+
+ i0.ColumnFilters get updatedAt => $composableBuilder(
+ column: $table.updatedAt, builder: (column) => i0.ColumnFilters(column));
+
+ i4.$$UserEntityTableFilterComposer get ownerId {
+ final i4.$$UserEntityTableFilterComposer composer = $composerBuilder(
+ composer: this,
+ getCurrentColumn: (t) => t.ownerId,
+ referencedTable: i5.ReadDatabaseContainer($db)
+ .resultSet('user_entity'),
+ getReferencedColumn: (t) => t.id,
+ builder: (joinBuilder,
+ {$addJoinBuilderToRootComposer,
+ $removeJoinBuilderFromRootComposer}) =>
+ i4.$$UserEntityTableFilterComposer(
+ $db: $db,
+ $table: i5.ReadDatabaseContainer($db)
+ .resultSet('user_entity'),
+ $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
+ joinBuilder: joinBuilder,
+ $removeJoinBuilderFromRootComposer:
+ $removeJoinBuilderFromRootComposer,
+ ));
+ return composer;
+ }
+
+ i6.$$RemoteAssetEntityTableFilterComposer get primaryAssetId {
+ final i6.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
+ composer: this,
+ getCurrentColumn: (t) => t.primaryAssetId,
+ referencedTable: i5.ReadDatabaseContainer($db)
+ .resultSet('remote_asset_entity'),
+ getReferencedColumn: (t) => t.id,
+ builder: (joinBuilder,
+ {$addJoinBuilderToRootComposer,
+ $removeJoinBuilderFromRootComposer}) =>
+ i6.$$RemoteAssetEntityTableFilterComposer(
+ $db: $db,
+ $table: i5.ReadDatabaseContainer($db)
+ .resultSet('remote_asset_entity'),
+ $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
+ joinBuilder: joinBuilder,
+ $removeJoinBuilderFromRootComposer:
+ $removeJoinBuilderFromRootComposer,
+ ));
+ return composer;
+ }
+}
+
+class $$StackEntityTableOrderingComposer
+ extends i0.Composer {
+ $$StackEntityTableOrderingComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnOrderings get id => $composableBuilder(
+ column: $table.id, builder: (column) => i0.ColumnOrderings(column));
+
+ i0.ColumnOrderings get createdAt => $composableBuilder(
+ column: $table.createdAt,
+ builder: (column) => i0.ColumnOrderings(column));
+
+ i0.ColumnOrderings get updatedAt => $composableBuilder(
+ column: $table.updatedAt,
+ builder: (column) => i0.ColumnOrderings(column));
+
+ i4.$$UserEntityTableOrderingComposer get ownerId {
+ final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder(
+ composer: this,
+ getCurrentColumn: (t) => t.ownerId,
+ referencedTable: i5.ReadDatabaseContainer($db)
+ .resultSet('user_entity'),
+ getReferencedColumn: (t) => t.id,
+ builder: (joinBuilder,
+ {$addJoinBuilderToRootComposer,
+ $removeJoinBuilderFromRootComposer}) =>
+ i4.$$UserEntityTableOrderingComposer(
+ $db: $db,
+ $table: i5.ReadDatabaseContainer($db)
+ .resultSet('user_entity'),
+ $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
+ joinBuilder: joinBuilder,
+ $removeJoinBuilderFromRootComposer:
+ $removeJoinBuilderFromRootComposer,
+ ));
+ return composer;
+ }
+
+ i6.$$RemoteAssetEntityTableOrderingComposer get primaryAssetId {
+ final i6.$$RemoteAssetEntityTableOrderingComposer composer =
+ $composerBuilder(
+ composer: this,
+ getCurrentColumn: (t) => t.primaryAssetId,
+ referencedTable: i5.ReadDatabaseContainer($db)
+ .resultSet('remote_asset_entity'),
+ getReferencedColumn: (t) => t.id,
+ builder: (joinBuilder,
+ {$addJoinBuilderToRootComposer,
+ $removeJoinBuilderFromRootComposer}) =>
+ i6.$$RemoteAssetEntityTableOrderingComposer(
+ $db: $db,
+ $table: i5.ReadDatabaseContainer($db)
+ .resultSet(
+ 'remote_asset_entity'),
+ $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
+ joinBuilder: joinBuilder,
+ $removeJoinBuilderFromRootComposer:
+ $removeJoinBuilderFromRootComposer,
+ ));
+ return composer;
+ }
+}
+
+class $$StackEntityTableAnnotationComposer
+ extends i0.Composer {
+ $$StackEntityTableAnnotationComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.GeneratedColumn get id =>
+ $composableBuilder(column: $table.id, builder: (column) => column);
+
+ i0.GeneratedColumn get createdAt =>
+ $composableBuilder(column: $table.createdAt, builder: (column) => column);
+
+ i0.GeneratedColumn get updatedAt =>
+ $composableBuilder(column: $table.updatedAt, builder: (column) => column);
+
+ i4.$$UserEntityTableAnnotationComposer get ownerId {
+ final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
+ composer: this,
+ getCurrentColumn: (t) => t.ownerId,
+ referencedTable: i5.ReadDatabaseContainer($db)
+ .resultSet('user_entity'),
+ getReferencedColumn: (t) => t.id,
+ builder: (joinBuilder,
+ {$addJoinBuilderToRootComposer,
+ $removeJoinBuilderFromRootComposer}) =>
+ i4.$$UserEntityTableAnnotationComposer(
+ $db: $db,
+ $table: i5.ReadDatabaseContainer($db)
+ .resultSet('user_entity'),
+ $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
+ joinBuilder: joinBuilder,
+ $removeJoinBuilderFromRootComposer:
+ $removeJoinBuilderFromRootComposer,
+ ));
+ return composer;
+ }
+
+ i6.$$RemoteAssetEntityTableAnnotationComposer get primaryAssetId {
+ final i6.$$RemoteAssetEntityTableAnnotationComposer composer =
+ $composerBuilder(
+ composer: this,
+ getCurrentColumn: (t) => t.primaryAssetId,
+ referencedTable: i5.ReadDatabaseContainer($db)
+ .resultSet('remote_asset_entity'),
+ getReferencedColumn: (t) => t.id,
+ builder: (joinBuilder,
+ {$addJoinBuilderToRootComposer,
+ $removeJoinBuilderFromRootComposer}) =>
+ i6.$$RemoteAssetEntityTableAnnotationComposer(
+ $db: $db,
+ $table: i5.ReadDatabaseContainer($db)
+ .resultSet(
+ 'remote_asset_entity'),
+ $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
+ joinBuilder: joinBuilder,
+ $removeJoinBuilderFromRootComposer:
+ $removeJoinBuilderFromRootComposer,
+ ));
+ return composer;
+ }
+}
+
+class $$StackEntityTableTableManager extends i0.RootTableManager<
+ i0.GeneratedDatabase,
+ i1.$StackEntityTable,
+ i1.StackEntityData,
+ i1.$$StackEntityTableFilterComposer,
+ i1.$$StackEntityTableOrderingComposer,
+ i1.$$StackEntityTableAnnotationComposer,
+ $$StackEntityTableCreateCompanionBuilder,
+ $$StackEntityTableUpdateCompanionBuilder,
+ (i1.StackEntityData, i1.$$StackEntityTableReferences),
+ i1.StackEntityData,
+ i0.PrefetchHooks Function({bool ownerId, bool primaryAssetId})> {
+ $$StackEntityTableTableManager(
+ i0.GeneratedDatabase db, i1.$StackEntityTable table)
+ : super(i0.TableManagerState(
+ db: db,
+ table: table,
+ createFilteringComposer: () =>
+ i1.$$StackEntityTableFilterComposer($db: db, $table: table),
+ createOrderingComposer: () =>
+ i1.$$StackEntityTableOrderingComposer($db: db, $table: table),
+ createComputedFieldComposer: () =>
+ i1.$$StackEntityTableAnnotationComposer($db: db, $table: table),
+ updateCompanionCallback: ({
+ i0.Value id = const i0.Value.absent(),
+ i0.Value createdAt = const i0.Value.absent(),
+ i0.Value updatedAt = const i0.Value.absent(),
+ i0.Value ownerId = const i0.Value.absent(),
+ i0.Value primaryAssetId = const i0.Value.absent(),
+ }) =>
+ i1.StackEntityCompanion(
+ id: id,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ ownerId: ownerId,
+ primaryAssetId: primaryAssetId,
+ ),
+ createCompanionCallback: ({
+ required String id,
+ i0.Value createdAt = const i0.Value.absent(),
+ i0.Value updatedAt = const i0.Value.absent(),
+ required String ownerId,
+ required String primaryAssetId,
+ }) =>
+ i1.StackEntityCompanion.insert(
+ id: id,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ ownerId: ownerId,
+ primaryAssetId: primaryAssetId,
+ ),
+ withReferenceMapper: (p0) => p0
+ .map((e) => (
+ e.readTable(table),
+ i1.$$StackEntityTableReferences(db, table, e)
+ ))
+ .toList(),
+ prefetchHooksCallback: ({ownerId = false, primaryAssetId = false}) {
+ return i0.PrefetchHooks(
+ db: db,
+ explicitlyWatchedTables: [],
+ addJoins: <
+ T extends i0.TableManagerState<
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic,
+ dynamic>>(state) {
+ if (ownerId) {
+ state = state.withJoin(
+ currentTable: table,
+ currentColumn: table.ownerId,
+ referencedTable:
+ i1.$$StackEntityTableReferences._ownerIdTable(db),
+ referencedColumn:
+ i1.$$StackEntityTableReferences._ownerIdTable(db).id,
+ ) as T;
+ }
+ if (primaryAssetId) {
+ state = state.withJoin(
+ currentTable: table,
+ currentColumn: table.primaryAssetId,
+ referencedTable: i1.$$StackEntityTableReferences
+ ._primaryAssetIdTable(db),
+ referencedColumn: i1.$$StackEntityTableReferences
+ ._primaryAssetIdTable(db)
+ .id,
+ ) as T;
+ }
+
+ return state;
+ },
+ getPrefetchedDataCallback: (items) async {
+ return [];
+ },
+ );
+ },
+ ));
+}
+
+typedef $$StackEntityTableProcessedTableManager = i0.ProcessedTableManager<
+ i0.GeneratedDatabase,
+ i1.$StackEntityTable,
+ i1.StackEntityData,
+ i1.$$StackEntityTableFilterComposer,
+ i1.$$StackEntityTableOrderingComposer,
+ i1.$$StackEntityTableAnnotationComposer,
+ $$StackEntityTableCreateCompanionBuilder,
+ $$StackEntityTableUpdateCompanionBuilder,
+ (i1.StackEntityData, i1.$$StackEntityTableReferences),
+ i1.StackEntityData,
+ i0.PrefetchHooks Function({bool ownerId, bool primaryAssetId})>;
+
+class $StackEntityTable extends i2.StackEntity
+ with i0.TableInfo<$StackEntityTable, i1.StackEntityData> {
+ @override
+ final i0.GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ $StackEntityTable(this.attachedDatabase, [this._alias]);
+ static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
+ @override
+ late final i0.GeneratedColumn id = i0.GeneratedColumn(
+ 'id', aliasedName, false,
+ type: i0.DriftSqlType.string, requiredDuringInsert: true);
+ static const i0.VerificationMeta _createdAtMeta =
+ const i0.VerificationMeta('createdAt');
+ @override
+ late final i0.GeneratedColumn createdAt =
+ i0.GeneratedColumn('created_at', aliasedName, false,
+ type: i0.DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: i3.currentDateAndTime);
+ static const i0.VerificationMeta _updatedAtMeta =
+ const i0.VerificationMeta('updatedAt');
+ @override
+ late final i0.GeneratedColumn updatedAt =
+ i0.GeneratedColumn('updated_at', aliasedName, false,
+ type: i0.DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: i3.currentDateAndTime);
+ static const i0.VerificationMeta _ownerIdMeta =
+ const i0.VerificationMeta('ownerId');
+ @override
+ late final i0.GeneratedColumn ownerId = i0.GeneratedColumn(
+ 'owner_id', aliasedName, false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
+ 'REFERENCES user_entity (id) ON DELETE CASCADE'));
+ static const i0.VerificationMeta _primaryAssetIdMeta =
+ const i0.VerificationMeta('primaryAssetId');
+ @override
+ late final i0.GeneratedColumn primaryAssetId =
+ i0.GeneratedColumn(
+ 'primary_asset_id', aliasedName, false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
+ 'REFERENCES remote_asset_entity (id)'));
+ @override
+ List get $columns =>
+ [id, createdAt, updatedAt, ownerId, primaryAssetId];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'stack_entity';
+ @override
+ i0.VerificationContext validateIntegrity(
+ i0.Insertable instance,
+ {bool isInserting = false}) {
+ final context = i0.VerificationContext();
+ final data = instance.toColumns(true);
+ if (data.containsKey('id')) {
+ context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
+ } else if (isInserting) {
+ context.missing(_idMeta);
+ }
+ if (data.containsKey('created_at')) {
+ context.handle(_createdAtMeta,
+ createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
+ }
+ if (data.containsKey('updated_at')) {
+ context.handle(_updatedAtMeta,
+ updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
+ }
+ if (data.containsKey('owner_id')) {
+ context.handle(_ownerIdMeta,
+ ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta));
+ } else if (isInserting) {
+ context.missing(_ownerIdMeta);
+ }
+ if (data.containsKey('primary_asset_id')) {
+ context.handle(
+ _primaryAssetIdMeta,
+ primaryAssetId.isAcceptableOrUnknown(
+ data['primary_asset_id']!, _primaryAssetIdMeta));
+ } else if (isInserting) {
+ context.missing(_primaryAssetIdMeta);
+ }
+ return context;
+ }
+
+ @override
+ Set get $primaryKey => {id};
+ @override
+ i1.StackEntityData map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return i1.StackEntityData(
+ id: attachedDatabase.typeMapping
+ .read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
+ createdAt: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
+ updatedAt: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
+ ownerId: attachedDatabase.typeMapping
+ .read(i0.DriftSqlType.string, data['${effectivePrefix}owner_id'])!,
+ primaryAssetId: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string, data['${effectivePrefix}primary_asset_id'])!,
+ );
+ }
+
+ @override
+ $StackEntityTable createAlias(String alias) {
+ return $StackEntityTable(attachedDatabase, alias);
+ }
+
+ @override
+ bool get withoutRowId => true;
+ @override
+ bool get isStrict => true;
+}
+
+class StackEntityData extends i0.DataClass
+ implements i0.Insertable {
+ final String id;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+ final String ownerId;
+ final String primaryAssetId;
+ const StackEntityData(
+ {required this.id,
+ required this.createdAt,
+ required this.updatedAt,
+ required this.ownerId,
+ required this.primaryAssetId});
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['id'] = i0.Variable(id);
+ map['created_at'] = i0.Variable(createdAt);
+ map['updated_at'] = i0.Variable(updatedAt);
+ map['owner_id'] = i0.Variable(ownerId);
+ map['primary_asset_id'] = i0.Variable(primaryAssetId);
+ return map;
+ }
+
+ factory StackEntityData.fromJson(Map json,
+ {i0.ValueSerializer? serializer}) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return StackEntityData(
+ id: serializer.fromJson(json['id']),
+ createdAt: serializer.fromJson(json['createdAt']),
+ updatedAt: serializer.fromJson(json['updatedAt']),
+ ownerId: serializer.fromJson(json['ownerId']),
+ primaryAssetId: serializer.fromJson(json['primaryAssetId']),
+ );
+ }
+ @override
+ Map toJson({i0.ValueSerializer? serializer}) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return {
+ 'id': serializer.toJson(id),
+ 'createdAt': serializer.toJson(createdAt),
+ 'updatedAt': serializer.toJson(updatedAt),
+ 'ownerId': serializer.toJson(ownerId),
+ 'primaryAssetId': serializer.toJson(primaryAssetId),
+ };
+ }
+
+ i1.StackEntityData copyWith(
+ {String? id,
+ DateTime? createdAt,
+ DateTime? updatedAt,
+ String? ownerId,
+ String? primaryAssetId}) =>
+ i1.StackEntityData(
+ id: id ?? this.id,
+ createdAt: createdAt ?? this.createdAt,
+ updatedAt: updatedAt ?? this.updatedAt,
+ ownerId: ownerId ?? this.ownerId,
+ primaryAssetId: primaryAssetId ?? this.primaryAssetId,
+ );
+ StackEntityData copyWithCompanion(i1.StackEntityCompanion data) {
+ return StackEntityData(
+ id: data.id.present ? data.id.value : this.id,
+ createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
+ updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
+ ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId,
+ primaryAssetId: data.primaryAssetId.present
+ ? data.primaryAssetId.value
+ : this.primaryAssetId,
+ );
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('StackEntityData(')
+ ..write('id: $id, ')
+ ..write('createdAt: $createdAt, ')
+ ..write('updatedAt: $updatedAt, ')
+ ..write('ownerId: $ownerId, ')
+ ..write('primaryAssetId: $primaryAssetId')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode =>
+ Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId);
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is i1.StackEntityData &&
+ other.id == this.id &&
+ other.createdAt == this.createdAt &&
+ other.updatedAt == this.updatedAt &&
+ other.ownerId == this.ownerId &&
+ other.primaryAssetId == this.primaryAssetId);
+}
+
+class StackEntityCompanion extends i0.UpdateCompanion {
+ final i0.Value id;
+ final i0.Value createdAt;
+ final i0.Value updatedAt;
+ final i0.Value ownerId;
+ final i0.Value primaryAssetId;
+ const StackEntityCompanion({
+ this.id = const i0.Value.absent(),
+ this.createdAt = const i0.Value.absent(),
+ this.updatedAt = const i0.Value.absent(),
+ this.ownerId = const i0.Value.absent(),
+ this.primaryAssetId = const i0.Value.absent(),
+ });
+ StackEntityCompanion.insert({
+ required String id,
+ this.createdAt = const i0.Value.absent(),
+ this.updatedAt = const i0.Value.absent(),
+ required String ownerId,
+ required String primaryAssetId,
+ }) : id = i0.Value(id),
+ ownerId = i0.Value(ownerId),
+ primaryAssetId = i0.Value(primaryAssetId);
+ static i0.Insertable custom({
+ i0.Expression? id,
+ i0.Expression? createdAt,
+ i0.Expression? updatedAt,
+ i0.Expression? ownerId,
+ i0.Expression? primaryAssetId,
+ }) {
+ return i0.RawValuesInsertable({
+ if (id != null) 'id': id,
+ if (createdAt != null) 'created_at': createdAt,
+ if (updatedAt != null) 'updated_at': updatedAt,
+ if (ownerId != null) 'owner_id': ownerId,
+ if (primaryAssetId != null) 'primary_asset_id': primaryAssetId,
+ });
+ }
+
+ i1.StackEntityCompanion copyWith(
+ {i0.Value? id,
+ i0.Value? createdAt,
+ i0.Value? updatedAt,
+ i0.Value? ownerId,
+ i0.Value? primaryAssetId}) {
+ return i1.StackEntityCompanion(
+ id: id ?? this.id,
+ createdAt: createdAt ?? this.createdAt,
+ updatedAt: updatedAt ?? this.updatedAt,
+ ownerId: ownerId ?? this.ownerId,
+ primaryAssetId: primaryAssetId ?? this.primaryAssetId,
+ );
+ }
+
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ if (id.present) {
+ map['id'] = i0.Variable(id.value);
+ }
+ if (createdAt.present) {
+ map['created_at'] = i0.Variable(createdAt.value);
+ }
+ if (updatedAt.present) {
+ map['updated_at'] = i0.Variable(updatedAt.value);
+ }
+ if (ownerId.present) {
+ map['owner_id'] = i0.Variable(ownerId.value);
+ }
+ if (primaryAssetId.present) {
+ map['primary_asset_id'] = i0.Variable(primaryAssetId.value);
+ }
+ return map;
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('StackEntityCompanion(')
+ ..write('id: $id, ')
+ ..write('createdAt: $createdAt, ')
+ ..write('updatedAt: $updatedAt, ')
+ ..write('ownerId: $ownerId, ')
+ ..write('primaryAssetId: $primaryAssetId')
+ ..write(')'))
+ .toString();
+ }
+}
diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart
index e71598a999..a7920cf7b2 100644
--- a/mobile/lib/infrastructure/repositories/db.repository.dart
+++ b/mobile/lib/infrastructure/repositories/db.repository.dart
@@ -14,6 +14,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
+import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
import 'package:isar/isar.dart';
@@ -50,6 +51,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
RemoteAlbumUserEntity,
MemoryEntity,
MemoryAssetEntity,
+ StackEntity,
],
include: {
'package:immich_mobile/infrastructure/entities/merged_asset.drift',
diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart
index 925591def8..15d445d226 100644
--- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart
+++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart
@@ -27,9 +27,11 @@ import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
as i12;
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
as i13;
-import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
+import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
as i14;
-import 'package:drift/internal/modular.dart' as i15;
+import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
+ as i15;
+import 'package:drift/internal/modular.dart' as i16;
abstract class $Drift extends i0.GeneratedDatabase {
$Drift(i0.QueryExecutor e) : super(e);
@@ -58,8 +60,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
late final i12.$MemoryEntityTable memoryEntity = i12.$MemoryEntityTable(this);
late final i13.$MemoryAssetEntityTable memoryAssetEntity =
i13.$MemoryAssetEntityTable(this);
- i14.MergedAssetDrift get mergedAssetDrift => i15.ReadDatabaseContainer(this)
- .accessor(i14.MergedAssetDrift.new);
+ late final i14.$StackEntityTable stackEntity = i14.$StackEntityTable(this);
+ i15.MergedAssetDrift get mergedAssetDrift => i16.ReadDatabaseContainer(this)
+ .accessor(i15.MergedAssetDrift.new);
@override
Iterable> get allTables =>
allSchemaEntities.whereType>();
@@ -80,7 +83,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
memoryEntity,
- memoryAssetEntity
+ memoryAssetEntity,
+ stackEntity
];
@override
i0.StreamQueryUpdateRules get streamUpdateRules =>
@@ -205,6 +209,13 @@ abstract class $Drift extends i0.GeneratedDatabase {
i0.TableUpdate('memory_asset_entity', kind: i0.UpdateKind.delete),
],
),
+ i0.WritePropagation(
+ on: i0.TableUpdateQuery.onTableName('user_entity',
+ limitUpdateKind: i0.UpdateKind.delete),
+ result: [
+ i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete),
+ ],
+ ),
],
);
@override
@@ -242,4 +253,6 @@ class $DriftManager {
i12.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
i13.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
i13.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
+ i14.$$StackEntityTableTableManager get stackEntity =>
+ i14.$$StackEntityTableTableManager(_db, _db.stackEntity);
}
diff --git a/mobile/lib/infrastructure/repositories/stack.repository.dart b/mobile/lib/infrastructure/repositories/stack.repository.dart
new file mode 100644
index 0000000000..7f97f3d9ae
--- /dev/null
+++ b/mobile/lib/infrastructure/repositories/stack.repository.dart
@@ -0,0 +1,30 @@
+import 'package:drift/drift.dart';
+import 'package:immich_mobile/domain/models/stack.model.dart';
+import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
+import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
+
+class DriftStackRepository extends DriftDatabaseRepository {
+ final Drift _db;
+ const DriftStackRepository(this._db) : super(_db);
+
+ Future> getAll(String userId) {
+ final query = _db.stackEntity.select()
+ ..where((e) => e.ownerId.equals(userId));
+
+ return query.map((stack) {
+ return stack.toDto();
+ }).get();
+ }
+}
+
+extension on StackEntityData {
+ Stack toDto() {
+ return Stack(
+ id: id,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ ownerId: ownerId,
+ primaryAssetId: primaryAssetId,
+ );
+ }
+}
diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart
index 99199a7fc6..d43f786a29 100644
--- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart
+++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart
@@ -54,6 +54,8 @@ class SyncApiRepository {
SyncRequestType.albumToAssetsV1,
SyncRequestType.memoriesV1,
SyncRequestType.memoryToAssetsV1,
+ SyncRequestType.stacksV1,
+ SyncRequestType.partnerStacksV1,
],
).toJson(),
);
@@ -163,6 +165,11 @@ const _kResponseMap = {
SyncEntityType.memoryDeleteV1: SyncMemoryDeleteV1.fromJson,
SyncEntityType.memoryToAssetV1: SyncMemoryAssetV1.fromJson,
SyncEntityType.memoryToAssetDeleteV1: SyncMemoryAssetDeleteV1.fromJson,
+ SyncEntityType.stackV1: SyncStackV1.fromJson,
+ SyncEntityType.stackDeleteV1: SyncStackDeleteV1.fromJson,
+ SyncEntityType.partnerStackV1: SyncStackV1.fromJson,
+ SyncEntityType.partnerStackBackfillV1: SyncStackV1.fromJson,
+ SyncEntityType.partnerStackDeleteV1: SyncStackDeleteV1.fromJson,
};
class _SyncAckV1 {
diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart
index b88083aa02..89f5c2f59a 100644
--- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart
+++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart
@@ -12,6 +12,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
+import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:logging/logging.dart';
@@ -69,8 +70,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: SyncPartnerDeleteV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: SyncPartnerDeleteV1', error, stack);
rethrow;
}
}
@@ -92,8 +93,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: SyncPartnerV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: SyncPartnerV1', error, stack);
rethrow;
}
}
@@ -104,10 +105,10 @@ class SyncStreamRepository extends DriftDatabaseRepository {
}) async {
try {
await _db.remoteAssetEntity.deleteWhere(
- (row) => row.id.isIn(data.map((error) => error.assetId)),
+ (row) => row.id.isIn(data.map((e) => e.assetId)),
);
- } catch (error, stackTrace) {
- _logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack);
rethrow;
}
}
@@ -142,8 +143,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: updateAssetsV1 - $debugLabel', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: updateAssetsV1 - $debugLabel', error, stack);
rethrow;
}
}
@@ -186,11 +187,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
+ } catch (error, stack) {
_logger.severe(
'Error: updateAssetsExifV1 - $debugLabel',
error,
- stackTrace,
+ stack,
);
rethrow;
}
@@ -201,8 +202,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
await _db.remoteAlbumEntity.deleteWhere(
(row) => row.id.isIn(data.map((e) => e.albumId)),
);
- } catch (error, stackTrace) {
- _logger.severe('Error: deleteAlbumsV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: deleteAlbumsV1', error, stack);
rethrow;
}
}
@@ -229,8 +230,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: updateAlbumsV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: updateAlbumsV1', error, stack);
rethrow;
}
}
@@ -248,8 +249,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: deleteAlbumUsersV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: deleteAlbumUsersV1', error, stack);
rethrow;
}
}
@@ -275,11 +276,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
+ } catch (error, stack) {
_logger.severe(
'Error: updateAlbumUsersV1 - $debugLabel',
error,
- stackTrace,
+ stack,
);
rethrow;
}
@@ -300,8 +301,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: deleteAlbumToAssetsV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: deleteAlbumToAssetsV1', error, stack);
rethrow;
}
}
@@ -325,11 +326,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
+ } catch (error, stack) {
_logger.severe(
'Error: updateAlbumToAssetsV1 - $debugLabel',
error,
- stackTrace,
+ stack,
);
rethrow;
}
@@ -359,8 +360,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: updateMemoriesV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: updateMemoriesV1', error, stack);
rethrow;
}
}
@@ -370,8 +371,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
await _db.memoryEntity.deleteWhere(
(row) => row.id.isIn(data.map((e) => e.memoryId)),
);
- } catch (error, stackTrace) {
- _logger.severe('Error: deleteMemoriesV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: deleteMemoriesV1', error, stack);
rethrow;
}
}
@@ -392,8 +393,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: updateMemoryAssetsV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: updateMemoryAssetsV1', error, stack);
rethrow;
}
}
@@ -413,8 +414,49 @@ class SyncStreamRepository extends DriftDatabaseRepository {
);
}
});
- } catch (error, stackTrace) {
- _logger.severe('Error: deleteMemoryAssetsV1', error, stackTrace);
+ } catch (error, stack) {
+ _logger.severe('Error: deleteMemoryAssetsV1', error, stack);
+ rethrow;
+ }
+ }
+
+ Future updateStacksV1(
+ Iterable data, {
+ String debugLabel = 'user',
+ }) async {
+ try {
+ await _db.batch((batch) {
+ for (final stack in data) {
+ final companion = StackEntityCompanion(
+ createdAt: Value(stack.createdAt),
+ updatedAt: Value(stack.updatedAt),
+ ownerId: Value(stack.ownerId),
+ primaryAssetId: Value(stack.primaryAssetId),
+ );
+
+ batch.insert(
+ _db.stackEntity,
+ companion.copyWith(id: Value(stack.id)),
+ onConflict: DoUpdate((_) => companion),
+ );
+ }
+ });
+ } catch (error, stack) {
+ _logger.severe('Error: updateStacksV1 - $debugLabel', error, stack);
+ rethrow;
+ }
+ }
+
+ Future deleteStacksV1(
+ Iterable data, {
+ String debugLabel = 'user',
+ }) async {
+ try {
+ await _db.stackEntity.deleteWhere(
+ (row) => row.id.isIn(data.map((e) => e.stackId)),
+ );
+ } catch (error, stack) {
+ _logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack);
rethrow;
}
}
@@ -467,7 +509,7 @@ extension on String {
Duration? toDuration() {
try {
final parts = split(':')
- .map((error) => double.parse(error).toInt())
+ .map((e) => double.parse(e).toInt())
.toList(growable: false);
return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]);
diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart
index fcd92cb30c..10f3298716 100644
--- a/mobile/lib/infrastructure/repositories/timeline.repository.dart
+++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart
@@ -213,6 +213,262 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.map((row) => row.readTable(_db.remoteAssetEntity).toDto())
.get();
}
+
+ Stream> watchFavoriteBucket(
+ String userId, {
+ GroupAssetsBy groupBy = GroupAssetsBy.day,
+ }) {
+ if (groupBy == GroupAssetsBy.none) {
+ return _db.remoteAssetEntity
+ .count(
+ where: (row) =>
+ row.isFavorite.equals(true) & row.ownerId.equals(userId),
+ )
+ .map(_generateBuckets)
+ .watchSingle();
+ }
+
+ final assetCountExp = _db.remoteAssetEntity.id.count();
+ final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
+
+ final query = _db.remoteAssetEntity.selectOnly()
+ ..addColumns([assetCountExp, dateExp])
+ ..where(
+ _db.remoteAssetEntity.ownerId.equals(userId) &
+ _db.remoteAssetEntity.isFavorite.equals(true),
+ )
+ ..groupBy([dateExp])
+ ..orderBy([OrderingTerm.desc(dateExp)]);
+
+ return query.map((row) {
+ final timeline = row.read(dateExp)!.dateFmt(groupBy);
+ final assetCount = row.read(assetCountExp)!;
+ return TimeBucket(date: timeline, assetCount: assetCount);
+ }).watch();
+ }
+
+ Future> getFavoriteBucketAssets(
+ String userId, {
+ required int offset,
+ required int count,
+ }) {
+ final query = _db.remoteAssetEntity.select()
+ ..where(
+ (row) => row.isFavorite.equals(true) & row.ownerId.equals(userId),
+ )
+ ..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
+ ..limit(count, offset: offset);
+
+ return query.map((row) => row.toDto()).get();
+ }
+
+ Stream> watchTrashBucket(
+ String userId, {
+ GroupAssetsBy groupBy = GroupAssetsBy.day,
+ }) {
+ if (groupBy == GroupAssetsBy.none) {
+ return _db.remoteAssetEntity
+ .count(
+ where: (row) =>
+ row.deletedAt.isNotNull() & row.ownerId.equals(userId),
+ )
+ .map(_generateBuckets)
+ .watchSingle();
+ }
+
+ final assetCountExp = _db.remoteAssetEntity.id.count();
+ final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
+
+ final query = _db.remoteAssetEntity.selectOnly()
+ ..addColumns([assetCountExp, dateExp])
+ ..where(
+ _db.remoteAssetEntity.ownerId.equals(userId) &
+ _db.remoteAssetEntity.deletedAt.isNotNull(),
+ )
+ ..groupBy([dateExp])
+ ..orderBy([OrderingTerm.desc(dateExp)]);
+
+ return query.map((row) {
+ final timeline = row.read(dateExp)!.dateFmt(groupBy);
+ final assetCount = row.read(assetCountExp)!;
+ return TimeBucket(date: timeline, assetCount: assetCount);
+ }).watch();
+ }
+
+ Future> getTrashBucketAssets(
+ String userId, {
+ required int offset,
+ required int count,
+ }) {
+ final query = _db.remoteAssetEntity.select()
+ ..where(
+ (row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId),
+ )
+ ..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
+ ..limit(count, offset: offset);
+
+ return query.map((row) => row.toDto()).get();
+ }
+
+ Stream> watchArchiveBucket(
+ String userId, {
+ GroupAssetsBy groupBy = GroupAssetsBy.day,
+ }) {
+ if (groupBy == GroupAssetsBy.none) {
+ return _db.remoteAssetEntity
+ .count(
+ where: (row) =>
+ row.visibility.equalsValue(AssetVisibility.archive) &
+ row.ownerId.equals(userId),
+ )
+ .map(_generateBuckets)
+ .watchSingle();
+ }
+
+ final assetCountExp = _db.remoteAssetEntity.id.count();
+ final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
+
+ final query = _db.remoteAssetEntity.selectOnly()
+ ..addColumns([assetCountExp, dateExp])
+ ..where(
+ _db.remoteAssetEntity.ownerId.equals(userId) &
+ _db.remoteAssetEntity.visibility
+ .equalsValue(AssetVisibility.archive),
+ )
+ ..groupBy([dateExp])
+ ..orderBy([OrderingTerm.desc(dateExp)]);
+
+ return query.map((row) {
+ final timeline = row.read(dateExp)!.dateFmt(groupBy);
+ final assetCount = row.read(assetCountExp)!;
+ return TimeBucket(date: timeline, assetCount: assetCount);
+ }).watch();
+ }
+
+ Future> getArchiveBucketAssets(
+ String userId, {
+ required int offset,
+ required int count,
+ }) {
+ final query = _db.remoteAssetEntity.select()
+ ..where(
+ (row) =>
+ row.ownerId.equals(userId) &
+ row.visibility.equalsValue(AssetVisibility.archive),
+ )
+ ..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
+ ..limit(count, offset: offset);
+
+ return query.map((row) => row.toDto()).get();
+ }
+
+ Stream> watchLockedFolderBucket(
+ String userId, {
+ GroupAssetsBy groupBy = GroupAssetsBy.day,
+ }) {
+ if (groupBy == GroupAssetsBy.none) {
+ return _db.remoteAssetEntity
+ .count(
+ where: (row) =>
+ row.visibility.equalsValue(AssetVisibility.locked) &
+ row.ownerId.equals(userId),
+ )
+ .map(_generateBuckets)
+ .watchSingle();
+ }
+
+ final assetCountExp = _db.remoteAssetEntity.id.count();
+ final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
+
+ final query = _db.remoteAssetEntity.selectOnly()
+ ..addColumns([assetCountExp, dateExp])
+ ..where(
+ _db.remoteAssetEntity.ownerId.equals(userId) &
+ _db.remoteAssetEntity.visibility
+ .equalsValue(AssetVisibility.locked),
+ )
+ ..groupBy([dateExp])
+ ..orderBy([OrderingTerm.desc(dateExp)]);
+
+ return query.map((row) {
+ final timeline = row.read(dateExp)!.dateFmt(groupBy);
+ final assetCount = row.read(assetCountExp)!;
+ return TimeBucket(date: timeline, assetCount: assetCount);
+ }).watch();
+ }
+
+ Future> getLockedFolderBucketAssets(
+ String userId, {
+ required int offset,
+ required int count,
+ }) {
+ final query = _db.remoteAssetEntity.select()
+ ..where(
+ (row) =>
+ row.visibility.equalsValue(AssetVisibility.locked) &
+ row.ownerId.equals(userId),
+ )
+ ..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
+ ..limit(count, offset: offset);
+
+ return query.map((row) => row.toDto()).get();
+ }
+
+ Stream> watchVideoBucket(
+ String userId, {
+ GroupAssetsBy groupBy = GroupAssetsBy.day,
+ }) {
+ if (groupBy == GroupAssetsBy.none) {
+ return _db.remoteAssetEntity
+ .count(
+ where: (row) =>
+ row.type.equalsValue(AssetType.video) &
+ row.visibility.equalsValue(AssetVisibility.timeline) &
+ row.ownerId.equals(userId),
+ )
+ .map(_generateBuckets)
+ .watchSingle();
+ }
+
+ final assetCountExp = _db.remoteAssetEntity.id.count();
+ final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
+
+ final query = _db.remoteAssetEntity.selectOnly()
+ ..addColumns([assetCountExp, dateExp])
+ ..where(
+ _db.remoteAssetEntity.ownerId.equals(userId) &
+ _db.remoteAssetEntity.type.equalsValue(AssetType.video) &
+ _db.remoteAssetEntity.visibility
+ .equalsValue(AssetVisibility.timeline),
+ )
+ ..groupBy([dateExp])
+ ..orderBy([OrderingTerm.desc(dateExp)]);
+
+ return query.map((row) {
+ final timeline = row.read(dateExp)!.dateFmt(groupBy);
+ final assetCount = row.read(assetCountExp)!;
+ return TimeBucket(date: timeline, assetCount: assetCount);
+ }).watch();
+ }
+
+ Future> getVideoBucketAssets(
+ String userId, {
+ required int offset,
+ required int count,
+ }) {
+ final query = _db.remoteAssetEntity.select()
+ ..where(
+ (row) =>
+ _db.remoteAssetEntity.type.equalsValue(AssetType.video) &
+ _db.remoteAssetEntity.visibility
+ .equalsValue(AssetVisibility.timeline) &
+ _db.remoteAssetEntity.ownerId.equals(userId),
+ )
+ ..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
+ ..limit(count, offset: offset);
+
+ return query.map((row) => row.toDto()).get();
+ }
}
extension on Expression {
diff --git a/mobile/lib/pages/common/tab_shell.page.dart b/mobile/lib/pages/common/tab_shell.page.dart
index 452c153342..d5d53360b0 100644
--- a/mobile/lib/pages/common/tab_shell.page.dart
+++ b/mobile/lib/pages/common/tab_shell.page.dart
@@ -118,7 +118,7 @@ class TabShellPage extends ConsumerWidget {
const MainTimelineRoute(),
SearchRoute(),
const DriftAlbumsRoute(),
- const LibraryRoute(),
+ const DriftLibraryRoute(),
],
duration: const Duration(milliseconds: 600),
transitionBuilder: (context, child, animation) => FadeTransition(
diff --git a/mobile/lib/presentation/pages/dev/drift_archive.page.dart b/mobile/lib/presentation/pages/dev/drift_archive.page.dart
new file mode 100644
index 0000000000..14657f7149
--- /dev/null
+++ b/mobile/lib/presentation/pages/dev/drift_archive.page.dart
@@ -0,0 +1,33 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/widgets.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
+import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
+import 'package:immich_mobile/providers/user.provider.dart';
+
+@RoutePage()
+class DriftArchivePage extends StatelessWidget {
+ const DriftArchivePage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ProviderScope(
+ overrides: [
+ timelineServiceProvider.overrideWith(
+ (ref) {
+ final user = ref.watch(currentUserProvider);
+ if (user == null) {
+ throw Exception('User must be logged in to access archive');
+ }
+
+ final timelineService =
+ ref.watch(timelineFactoryProvider).archive(user.id);
+ ref.onDispose(timelineService.dispose);
+ return timelineService;
+ },
+ ),
+ ],
+ child: const Timeline(),
+ );
+ }
+}
diff --git a/mobile/lib/presentation/pages/dev/drift_favorite.page.dart b/mobile/lib/presentation/pages/dev/drift_favorite.page.dart
new file mode 100644
index 0000000000..4055ad863b
--- /dev/null
+++ b/mobile/lib/presentation/pages/dev/drift_favorite.page.dart
@@ -0,0 +1,33 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/widgets.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
+import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
+import 'package:immich_mobile/providers/user.provider.dart';
+
+@RoutePage()
+class DriftFavoritePage extends StatelessWidget {
+ const DriftFavoritePage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ProviderScope(
+ overrides: [
+ timelineServiceProvider.overrideWith(
+ (ref) {
+ final user = ref.watch(currentUserProvider);
+ if (user == null) {
+ throw Exception('User must be logged in to access favorite');
+ }
+
+ final timelineService =
+ ref.watch(timelineFactoryProvider).favorite(user.id);
+ ref.onDispose(timelineService.dispose);
+ return timelineService;
+ },
+ ),
+ ],
+ child: const Timeline(),
+ );
+ }
+}
diff --git a/mobile/lib/presentation/pages/dev/drift_locked_folder.page.dart b/mobile/lib/presentation/pages/dev/drift_locked_folder.page.dart
new file mode 100644
index 0000000000..5ab7c71347
--- /dev/null
+++ b/mobile/lib/presentation/pages/dev/drift_locked_folder.page.dart
@@ -0,0 +1,33 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/widgets.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
+import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
+import 'package:immich_mobile/providers/user.provider.dart';
+
+@RoutePage()
+class DriftLockedFolderPage extends StatelessWidget {
+ const DriftLockedFolderPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ProviderScope(
+ overrides: [
+ timelineServiceProvider.overrideWith(
+ (ref) {
+ final user = ref.watch(currentUserProvider);
+ if (user == null) {
+ throw Exception('User must be logged in to access locked folder');
+ }
+
+ final timelineService =
+ ref.watch(timelineFactoryProvider).lockedFolder(user.id);
+ ref.onDispose(timelineService.dispose);
+ return timelineService;
+ },
+ ),
+ ],
+ child: const Timeline(),
+ );
+ }
+}
diff --git a/mobile/lib/presentation/pages/dev/drift_trash.page.dart b/mobile/lib/presentation/pages/dev/drift_trash.page.dart
new file mode 100644
index 0000000000..cbcfe50112
--- /dev/null
+++ b/mobile/lib/presentation/pages/dev/drift_trash.page.dart
@@ -0,0 +1,33 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/widgets.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
+import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
+import 'package:immich_mobile/providers/user.provider.dart';
+
+@RoutePage()
+class DriftTrashPage extends StatelessWidget {
+ const DriftTrashPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ProviderScope(
+ overrides: [
+ timelineServiceProvider.overrideWith(
+ (ref) {
+ final user = ref.watch(currentUserProvider);
+ if (user == null) {
+ throw Exception('User must be logged in to access trash');
+ }
+
+ final timelineService =
+ ref.watch(timelineFactoryProvider).trash(user.id);
+ ref.onDispose(timelineService.dispose);
+ return timelineService;
+ },
+ ),
+ ],
+ child: const Timeline(),
+ );
+ }
+}
diff --git a/mobile/lib/presentation/pages/dev/drift_video.page.dart b/mobile/lib/presentation/pages/dev/drift_video.page.dart
new file mode 100644
index 0000000000..488d027177
--- /dev/null
+++ b/mobile/lib/presentation/pages/dev/drift_video.page.dart
@@ -0,0 +1,33 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/widgets.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
+import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
+import 'package:immich_mobile/providers/user.provider.dart';
+
+@RoutePage()
+class DriftVideoPage extends StatelessWidget {
+ const DriftVideoPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ProviderScope(
+ overrides: [
+ timelineServiceProvider.overrideWith(
+ (ref) {
+ final user = ref.watch(currentUserProvider);
+ if (user == null) {
+ throw Exception('User must be logged in to video');
+ }
+
+ final timelineService =
+ ref.watch(timelineFactoryProvider).video(user.id);
+ ref.onDispose(timelineService.dispose);
+ return timelineService;
+ },
+ ),
+ ],
+ child: const Timeline(),
+ );
+ }
+}
diff --git a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart
index f10c042e10..2698eefccc 100644
--- a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart
+++ b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart
@@ -66,6 +66,9 @@ final _features = [
await db.remoteAlbumEntity.deleteAll();
await db.remoteAlbumUserEntity.deleteAll();
await db.remoteAlbumAssetEntity.deleteAll();
+ await db.memoryEntity.deleteAll();
+ await db.memoryAssetEntity.deleteAll();
+ await db.stackEntity.deleteAll();
},
),
_Feature(
@@ -96,6 +99,11 @@ final _features = [
icon: Icons.timeline_rounded,
onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()),
),
+ _Feature(
+ name: 'Video',
+ icon: Icons.video_collection_outlined,
+ onTap: (ctx, _) => ctx.pushRoute(const DriftVideoRoute()),
+ ),
];
@RoutePage()
diff --git a/mobile/lib/presentation/pages/dev/media_stat.page.dart b/mobile/lib/presentation/pages/dev/media_stat.page.dart
index f0a648fd5a..e5745fa629 100644
--- a/mobile/lib/presentation/pages/dev/media_stat.page.dart
+++ b/mobile/lib/presentation/pages/dev/media_stat.page.dart
@@ -162,6 +162,10 @@ final _remoteStats = [
name: 'Memories Assets',
load: (db) => db.managers.memoryAssetEntity.count(),
),
+ _Stat(
+ name: 'Stacks',
+ load: (db) => db.managers.stackEntity.count(),
+ ),
];
@RoutePage()
diff --git a/mobile/lib/presentation/pages/drift_library.page.dart b/mobile/lib/presentation/pages/drift_library.page.dart
new file mode 100644
index 0000000000..6c83ce0ca0
--- /dev/null
+++ b/mobile/lib/presentation/pages/drift_library.page.dart
@@ -0,0 +1,501 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/domain/models/user.model.dart';
+import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
+import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/translate_extensions.dart';
+import 'package:immich_mobile/providers/album/album.provider.dart';
+import 'package:immich_mobile/providers/partner.provider.dart';
+import 'package:immich_mobile/providers/search/people.provider.dart';
+import 'package:immich_mobile/providers/server_info.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/services/api.service.dart';
+import 'package:immich_mobile/utils/image_url_builder.dart';
+import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
+import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
+import 'package:immich_mobile/widgets/common/user_avatar.dart';
+import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
+import 'package:maplibre_gl/maplibre_gl.dart';
+
+@RoutePage()
+class DriftLibraryPage extends ConsumerWidget {
+ const DriftLibraryPage({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ return const Scaffold(
+ body: CustomScrollView(
+ slivers: [
+ ImmichSliverAppBar(),
+ _ActionButtonGrid(),
+ _CollectionCards(),
+ _QuickAccessButtonList(),
+ ],
+ ),
+ );
+ }
+}
+
+class _ActionButtonGrid extends ConsumerWidget {
+ const _ActionButtonGrid();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final isTrashEnable = ref.watch(
+ serverInfoProvider.select((state) => state.serverFeatures.trash),
+ );
+
+ return SliverPadding(
+ padding: const EdgeInsets.only(left: 16, top: 16, right: 16, bottom: 12),
+ sliver: SliverToBoxAdapter(
+ child: Column(
+ children: [
+ Row(
+ children: [
+ _ActionButton(
+ icon: Icons.favorite_outline_rounded,
+ onTap: () => context.pushRoute(const DriftFavoriteRoute()),
+ label: 'favorites'.t(context: context),
+ ),
+ const SizedBox(width: 8),
+ _ActionButton(
+ icon: Icons.archive_outlined,
+ onTap: () => context.pushRoute(const DriftArchiveRoute()),
+ label: 'archived'.t(context: context),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ _ActionButton(
+ icon: Icons.link_outlined,
+ onTap: () => context.pushRoute(const SharedLinkRoute()),
+ label: 'shared_links'.t(context: context),
+ ),
+ isTrashEnable
+ ? const SizedBox(width: 8)
+ : const SizedBox.shrink(),
+ isTrashEnable
+ ? _ActionButton(
+ icon: Icons.delete_outline_rounded,
+ onTap: () => context.pushRoute(const DriftTrashRoute()),
+ label: 'trash'.t(context: context),
+ )
+ : const SizedBox.shrink(),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class _ActionButton extends StatelessWidget {
+ const _ActionButton({
+ required this.icon,
+ required this.onTap,
+ required this.label,
+ });
+
+ final IconData icon;
+ final VoidCallback onTap;
+ final String label;
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: FilledButton.icon(
+ onPressed: onTap,
+ label: Padding(
+ padding: const EdgeInsets.only(left: 4.0),
+ child: Text(
+ label,
+ style: TextStyle(
+ color: context.colorScheme.onSurface,
+ fontSize: 15,
+ ),
+ ),
+ ),
+ style: FilledButton.styleFrom(
+ elevation: 0,
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
+ backgroundColor: context.colorScheme.surfaceContainerLow,
+ alignment: Alignment.centerLeft,
+ shape: RoundedRectangleBorder(
+ borderRadius: const BorderRadius.all(Radius.circular(25)),
+ side: BorderSide(
+ color: context.colorScheme.onSurface.withAlpha(10),
+ width: 1,
+ ),
+ ),
+ ),
+ icon: Icon(
+ icon,
+ color: context.primaryColor,
+ ),
+ ),
+ );
+ }
+}
+
+class _CollectionCards extends StatelessWidget {
+ const _CollectionCards();
+
+ @override
+ Widget build(BuildContext context) {
+ return const SliverPadding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ sliver: SliverToBoxAdapter(
+ child: Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ _PeopleCollectionCard(),
+ _PlacesCollectionCard(),
+ _LocalAlbumsCollectionCard(),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class _PeopleCollectionCard extends ConsumerWidget {
+ const _PeopleCollectionCard();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final people = ref.watch(getAllPeopleProvider);
+
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ final isTablet = constraints.maxWidth > 600;
+ final widthFactor = isTablet ? 0.25 : 0.5;
+ final size = context.width * widthFactor - 20.0;
+
+ return GestureDetector(
+ onTap: () => context.pushRoute(const PeopleCollectionRoute()),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ height: size,
+ width: size,
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(
+ Radius.circular(20),
+ ),
+ gradient: LinearGradient(
+ colors: [
+ context.colorScheme.primary.withAlpha(30),
+ context.colorScheme.primary.withAlpha(25),
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: people.widgetWhen(
+ onLoading: () => const Center(
+ child: CircularProgressIndicator(),
+ ),
+ onData: (people) {
+ return GridView.count(
+ crossAxisCount: 2,
+ padding: const EdgeInsets.all(12),
+ crossAxisSpacing: 8,
+ mainAxisSpacing: 8,
+ physics: const NeverScrollableScrollPhysics(),
+ children: people.take(4).map((person) {
+ return CircleAvatar(
+ backgroundImage: NetworkImage(
+ getFaceThumbnailUrl(person.id),
+ headers: ApiService.getRequestHeaders(),
+ ),
+ );
+ }).toList(),
+ );
+ },
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ 'people'.t(context: context),
+ style: context.textTheme.titleSmall?.copyWith(
+ color: context.colorScheme.onSurface,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _PlacesCollectionCard extends StatelessWidget {
+ const _PlacesCollectionCard();
+
+ @override
+ Widget build(BuildContext context) {
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ final isTablet = constraints.maxWidth > 600;
+ final widthFactor = isTablet ? 0.25 : 0.5;
+ final size = context.width * widthFactor - 20.0;
+
+ return GestureDetector(
+ onTap: () => context.pushRoute(
+ PlacesCollectionRoute(
+ currentLocation: null,
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: size,
+ width: size,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ color:
+ context.colorScheme.secondaryContainer.withAlpha(100),
+ ),
+ child: IgnorePointer(
+ child: MapThumbnail(
+ zoom: 8,
+ centre: const LatLng(
+ 21.44950,
+ -157.91959,
+ ),
+ showAttribution: false,
+ themeMode: context.isDarkTheme
+ ? ThemeMode.dark
+ : ThemeMode.light,
+ ),
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ 'places'.t(),
+ style: context.textTheme.titleSmall?.copyWith(
+ color: context.colorScheme.onSurface,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _LocalAlbumsCollectionCard extends ConsumerWidget {
+ const _LocalAlbumsCollectionCard();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ // TODO: Migrate to the drift after local album page
+ final albums = ref.watch(localAlbumsProvider);
+
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ final isTablet = constraints.maxWidth > 600;
+ final widthFactor = isTablet ? 0.25 : 0.5;
+ final size = context.width * widthFactor - 20.0;
+
+ return GestureDetector(
+ onTap: () => context.pushRoute(
+ const LocalAlbumsRoute(),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: size,
+ width: size,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ gradient: LinearGradient(
+ colors: [
+ context.colorScheme.primary.withAlpha(30),
+ context.colorScheme.primary.withAlpha(25),
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: GridView.count(
+ crossAxisCount: 2,
+ padding: const EdgeInsets.all(12),
+ crossAxisSpacing: 8,
+ mainAxisSpacing: 8,
+ physics: const NeverScrollableScrollPhysics(),
+ children: albums.take(4).map((album) {
+ return AlbumThumbnailCard(
+ album: album,
+ showTitle: false,
+ );
+ }).toList(),
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ 'on_this_device'.t(context: context),
+ style: context.textTheme.titleSmall?.copyWith(
+ color: context.colorScheme.onSurface,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _QuickAccessButtonList extends ConsumerWidget {
+ const _QuickAccessButtonList();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final partners = ref.watch(partnerSharedWithProvider);
+
+ return SliverPadding(
+ padding: const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 32),
+ sliver: SliverToBoxAdapter(
+ child: Container(
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: context.colorScheme.onSurface.withAlpha(10),
+ width: 1,
+ ),
+ borderRadius: const BorderRadius.all(
+ Radius.circular(20),
+ ),
+ gradient: LinearGradient(
+ colors: [
+ context.colorScheme.primary.withAlpha(10),
+ context.colorScheme.primary.withAlpha(15),
+ context.colorScheme.primary.withAlpha(20),
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: ListView(
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(0),
+ physics: const NeverScrollableScrollPhysics(),
+ children: [
+ ListTile(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.only(
+ topLeft: const Radius.circular(20),
+ topRight: const Radius.circular(20),
+ bottomLeft: Radius.circular(partners.isEmpty ? 20 : 0),
+ bottomRight: Radius.circular(partners.isEmpty ? 20 : 0),
+ ),
+ ),
+ leading: const Icon(
+ Icons.folder_outlined,
+ size: 26,
+ ),
+ title: Text(
+ 'folders'.t(context: context),
+ style: context.textTheme.titleSmall?.copyWith(
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ onTap: () => context.pushRoute(FolderRoute()),
+ ),
+ ListTile(
+ leading: const Icon(
+ Icons.lock_outline_rounded,
+ size: 26,
+ ),
+ title: Text(
+ 'locked_folder'.t(context: context),
+ style: context.textTheme.titleSmall?.copyWith(
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ // TODO: PIN code is needed
+ onTap: () => context.pushRoute(const DriftLockedFolderRoute()),
+ ),
+ ListTile(
+ leading: const Icon(
+ Icons.group_outlined,
+ size: 26,
+ ),
+ title: Text(
+ 'partners'.t(context: context),
+ style: context.textTheme.titleSmall?.copyWith(
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ onTap: () => context.pushRoute(const PartnerRoute()),
+ ),
+ _PartnerList(partners: partners),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _PartnerList extends StatelessWidget {
+ const _PartnerList({required this.partners});
+
+ final List partners;
+
+ @override
+ Widget build(BuildContext context) {
+ return ListView.builder(
+ padding: const EdgeInsets.all(0),
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: partners.length,
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ final partner = partners[index];
+ final isLastItem = index == partners.length - 1;
+ return ListTile(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.only(
+ bottomLeft: Radius.circular(isLastItem ? 20 : 0),
+ bottomRight: Radius.circular(isLastItem ? 20 : 0),
+ ),
+ ),
+ contentPadding: const EdgeInsets.only(
+ left: 12.0,
+ right: 18.0,
+ ),
+ leading: userAvatar(context, partner, radius: 16),
+ title: const Text(
+ "partner_list_user_photos",
+ style: TextStyle(
+ fontWeight: FontWeight.w500,
+ ),
+ ).t(context: context, args: {'user': partner.name}),
+ onTap: () => context.pushRoute(PartnerDetailRoute(partner: partner)),
+ );
+ },
+ );
+ }
+}
diff --git a/mobile/lib/providers/stack.provider.dart b/mobile/lib/providers/stack.provider.dart
new file mode 100644
index 0000000000..71abd1e87a
--- /dev/null
+++ b/mobile/lib/providers/stack.provider.dart
@@ -0,0 +1,7 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/infrastructure/repositories/stack.repository.dart';
+import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
+
+final driftStackProvider = Provider(
+ (ref) => DriftStackRepository(ref.watch(driftProvider)),
+);
diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart
index 3ad8e34580..4ee4d8c131 100644
--- a/mobile/lib/repositories/auth.repository.dart
+++ b/mobile/lib/repositories/auth.repository.dart
@@ -40,6 +40,9 @@ class AuthRepository extends DatabaseRepository {
_drift.remoteAlbumEntity.deleteAll(),
_drift.remoteAlbumAssetEntity.deleteAll(),
_drift.remoteAlbumUserEntity.deleteAll(),
+ _drift.memoryEntity.deleteAll(),
+ _drift.memoryAssetEntity.deleteAll(),
+ _drift.stackEntity.deleteAll(),
]);
});
}
diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart
index 56e1aa0f96..aa40ababfb 100644
--- a/mobile/lib/routing/router.dart
+++ b/mobile/lib/routing/router.dart
@@ -66,12 +66,18 @@ import 'package:immich_mobile/pages/search/person_result.page.dart';
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
import 'package:immich_mobile/pages/search/search.page.dart';
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
+import 'package:immich_mobile/presentation/pages/dev/drift_favorite.page.dart';
+import 'package:immich_mobile/presentation/pages/dev/drift_video.page.dart';
+import 'package:immich_mobile/presentation/pages/dev/drift_trash.page.dart';
+import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart';
+import 'package:immich_mobile/presentation/pages/dev/drift_locked_folder.page.dart';
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
import 'package:immich_mobile/presentation/pages/dev/local_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
import 'package:immich_mobile/presentation/pages/dev/remote_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
+import 'package:immich_mobile/presentation/pages/drift_library.page.dart';
import 'package:immich_mobile/presentation/pages/drift_memory.page.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
import 'package:immich_mobile/providers/api.provider.dart';
@@ -174,7 +180,7 @@ class AppRouter extends RootStackRouter {
maintainState: false,
),
AutoRoute(
- page: LibraryRoute.page,
+ page: DriftLibraryRoute.page,
guards: [_authGuard, _duplicateGuard],
),
AutoRoute(
@@ -392,7 +398,30 @@ class AppRouter extends RootStackRouter {
page: DriftMemoryRoute.page,
guards: [_authGuard, _duplicateGuard],
),
-
+ AutoRoute(
+ page: DriftFavoriteRoute.page,
+ guards: [_authGuard, _duplicateGuard],
+ ),
+ AutoRoute(
+ page: DriftTrashRoute.page,
+ guards: [_authGuard, _duplicateGuard],
+ ),
+ AutoRoute(
+ page: DriftArchiveRoute.page,
+ guards: [_authGuard, _duplicateGuard],
+ ),
+ AutoRoute(
+ page: DriftLockedFolderRoute.page,
+ guards: [_authGuard, _duplicateGuard],
+ ),
+ AutoRoute(
+ page: DriftVideoRoute.page,
+ guards: [_authGuard, _duplicateGuard],
+ ),
+ AutoRoute(
+ page: DriftLibraryRoute.page,
+ guards: [_authGuard, _duplicateGuard],
+ ),
// required to handle all deeplinks in deep_link.service.dart
// auto_route_library#1722
RedirectRoute(path: '*', redirectTo: '/'),
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index f8f37970f9..08e4a44ecb 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -618,6 +618,70 @@ class DriftAlbumsRoute extends PageRouteInfo {
);
}
+/// generated route for
+/// [DriftArchivePage]
+class DriftArchiveRoute extends PageRouteInfo {
+ const DriftArchiveRoute({List? children})
+ : super(DriftArchiveRoute.name, initialChildren: children);
+
+ static const String name = 'DriftArchiveRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DriftArchivePage();
+ },
+ );
+}
+
+/// generated route for
+/// [DriftFavoritePage]
+class DriftFavoriteRoute extends PageRouteInfo {
+ const DriftFavoriteRoute({List? children})
+ : super(DriftFavoriteRoute.name, initialChildren: children);
+
+ static const String name = 'DriftFavoriteRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DriftFavoritePage();
+ },
+ );
+}
+
+/// generated route for
+/// [DriftLibraryPage]
+class DriftLibraryRoute extends PageRouteInfo {
+ const DriftLibraryRoute({List? children})
+ : super(DriftLibraryRoute.name, initialChildren: children);
+
+ static const String name = 'DriftLibraryRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DriftLibraryPage();
+ },
+ );
+}
+
+/// generated route for
+/// [DriftLockedFolderPage]
+class DriftLockedFolderRoute extends PageRouteInfo {
+ const DriftLockedFolderRoute({List? children})
+ : super(DriftLockedFolderRoute.name, initialChildren: children);
+
+ static const String name = 'DriftLockedFolderRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DriftLockedFolderPage();
+ },
+ );
+}
+
/// generated route for
/// [DriftMemoryPage]
class DriftMemoryRoute extends PageRouteInfo {
@@ -670,6 +734,38 @@ class DriftMemoryRouteArgs {
}
}
+/// generated route for
+/// [DriftTrashPage]
+class DriftTrashRoute extends PageRouteInfo {
+ const DriftTrashRoute({List? children})
+ : super(DriftTrashRoute.name, initialChildren: children);
+
+ static const String name = 'DriftTrashRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DriftTrashPage();
+ },
+ );
+}
+
+/// generated route for
+/// [DriftVideoPage]
+class DriftVideoRoute extends PageRouteInfo {
+ const DriftVideoRoute({List? children})
+ : super(DriftVideoRoute.name, initialChildren: children);
+
+ static const String name = 'DriftVideoRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DriftVideoPage();
+ },
+ );
+}
+
/// generated route for
/// [EditImagePage]
class EditImageRoute extends PageRouteInfo {
diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart
index 8866cb01b0..e8501f1184 100644
--- a/mobile/lib/widgets/common/user_circle_avatar.dart
+++ b/mobile/lib/widgets/common/user_circle_avatar.dart
@@ -5,9 +5,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
-import 'package:immich_mobile/domain/models/user_metadata.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
-import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/widgets/common/transparent_image.dart';
@@ -26,7 +24,7 @@ class UserCircleAvatar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
- bool isDarkTheme = context.themeData.brightness == Brightness.dark;
+ final userAvatarColor = user.avatarColor.toColor();
final profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}';
@@ -34,14 +32,14 @@ class UserCircleAvatar extends ConsumerWidget {
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
- color: isDarkTheme && user.avatarColor == AvatarColor.primary
+ color: userAvatarColor.computeLuminance() > 0.5
? Colors.black
: Colors.white,
),
child: Text(user.name[0].toUpperCase()),
);
return CircleAvatar(
- backgroundColor: user.avatarColor.toColor(),
+ backgroundColor: userAvatarColor,
radius: radius,
child: user.profileImagePath == null
? textIcon
diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart
index e100e8e5ca..c8f91be1f1 100644
--- a/mobile/openapi/lib/model/system_config_o_auth_dto.dart
+++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart
@@ -24,6 +24,7 @@ class SystemConfigOAuthDto {
required this.mobileOverrideEnabled,
required this.mobileRedirectUri,
required this.profileSigningAlgorithm,
+ required this.roleClaim,
required this.scope,
required this.signingAlgorithm,
required this.storageLabelClaim,
@@ -55,6 +56,8 @@ class SystemConfigOAuthDto {
String profileSigningAlgorithm;
+ String roleClaim;
+
String scope;
String signingAlgorithm;
@@ -81,6 +84,7 @@ class SystemConfigOAuthDto {
other.mobileOverrideEnabled == mobileOverrideEnabled &&
other.mobileRedirectUri == mobileRedirectUri &&
other.profileSigningAlgorithm == profileSigningAlgorithm &&
+ other.roleClaim == roleClaim &&
other.scope == scope &&
other.signingAlgorithm == signingAlgorithm &&
other.storageLabelClaim == storageLabelClaim &&
@@ -102,6 +106,7 @@ class SystemConfigOAuthDto {
(mobileOverrideEnabled.hashCode) +
(mobileRedirectUri.hashCode) +
(profileSigningAlgorithm.hashCode) +
+ (roleClaim.hashCode) +
(scope.hashCode) +
(signingAlgorithm.hashCode) +
(storageLabelClaim.hashCode) +
@@ -110,7 +115,7 @@ class SystemConfigOAuthDto {
(tokenEndpointAuthMethod.hashCode);
@override
- String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]';
+ String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, roleClaim=$roleClaim, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]';
Map toJson() {
final json = {};
@@ -129,6 +134,7 @@ class SystemConfigOAuthDto {
json[r'mobileOverrideEnabled'] = this.mobileOverrideEnabled;
json[r'mobileRedirectUri'] = this.mobileRedirectUri;
json[r'profileSigningAlgorithm'] = this.profileSigningAlgorithm;
+ json[r'roleClaim'] = this.roleClaim;
json[r'scope'] = this.scope;
json[r'signingAlgorithm'] = this.signingAlgorithm;
json[r'storageLabelClaim'] = this.storageLabelClaim;
@@ -158,6 +164,7 @@ class SystemConfigOAuthDto {
mobileOverrideEnabled: mapValueOfType(json, r'mobileOverrideEnabled')!,
mobileRedirectUri: mapValueOfType(json, r'mobileRedirectUri')!,
profileSigningAlgorithm: mapValueOfType(json, r'profileSigningAlgorithm')!,
+ roleClaim: mapValueOfType(json, r'roleClaim')!,
scope: mapValueOfType(json, r'scope')!,
signingAlgorithm: mapValueOfType(json, r'signingAlgorithm')!,
storageLabelClaim: mapValueOfType(json, r'storageLabelClaim')!,
@@ -222,6 +229,7 @@ class SystemConfigOAuthDto {
'mobileOverrideEnabled',
'mobileRedirectUri',
'profileSigningAlgorithm',
+ 'roleClaim',
'scope',
'signingAlgorithm',
'storageLabelClaim',
diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart
index 28288422fd..27cd8c5b21 100644
--- a/mobile/test/domain/services/sync_stream_service_test.dart
+++ b/mobile/test/domain/services/sync_stream_service_test.dart
@@ -89,6 +89,18 @@ void main() {
.thenAnswer(successHandler);
when(() => mockSyncStreamRepo.deleteMemoryAssetsV1(any()))
.thenAnswer(successHandler);
+ when(
+ () => mockSyncStreamRepo.updateStacksV1(
+ any(),
+ debugLabel: any(named: 'debugLabel'),
+ ),
+ ).thenAnswer(successHandler);
+ when(
+ () => mockSyncStreamRepo.deleteStacksV1(
+ any(),
+ debugLabel: any(named: 'debugLabel'),
+ ),
+ ).thenAnswer(successHandler);
sut = SyncStreamService(
syncApiRepository: mockSyncApiRepo,
diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index 0dc0c43ec8..7a44a5cf6f 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -14654,6 +14654,9 @@
"profileSigningAlgorithm": {
"type": "string"
},
+ "roleClaim": {
+ "type": "string"
+ },
"scope": {
"type": "string"
},
@@ -14690,6 +14693,7 @@
"mobileOverrideEnabled",
"mobileRedirectUri",
"profileSigningAlgorithm",
+ "roleClaim",
"scope",
"signingAlgorithm",
"storageLabelClaim",
diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts
index 24f9a6d75d..9eb9990d2c 100644
--- a/open-api/typescript-sdk/src/fetch-client.ts
+++ b/open-api/typescript-sdk/src/fetch-client.ts
@@ -1398,6 +1398,7 @@ export type SystemConfigOAuthDto = {
mobileOverrideEnabled: boolean;
mobileRedirectUri: string;
profileSigningAlgorithm: string;
+ roleClaim: string;
scope: string;
signingAlgorithm: string;
storageLabelClaim: string;
diff --git a/server/package-lock.json b/server/package-lock.json
index 3243444915..ea45d89257 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -1134,9 +1134,9 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
- "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1149,9 +1149,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz",
- "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -1209,9 +1209,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
- "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
+ "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5472,9 +5472,9 @@
"license": "MIT"
},
"node_modules/@swc/core": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.7.tgz",
- "integrity": "sha512-bcpllEihyUSnqp0UtXTvXc19CT4wp3tGWLENhWnjr4B5iEOkzqMu+xHGz1FI5IBatjfqOQb29tgIfv6IL05QaA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.9.tgz",
+ "integrity": "sha512-O+LfT2JlVMsIMWG9x+rdxg8GzpzeGtCZQfXV7cKc1PjIKUkLFf1QJ7okuseA4f/9vncu37dQ2ZcRrPKy0Ndd5g==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@@ -5490,16 +5490,16 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
- "@swc/core-darwin-arm64": "1.12.7",
- "@swc/core-darwin-x64": "1.12.7",
- "@swc/core-linux-arm-gnueabihf": "1.12.7",
- "@swc/core-linux-arm64-gnu": "1.12.7",
- "@swc/core-linux-arm64-musl": "1.12.7",
- "@swc/core-linux-x64-gnu": "1.12.7",
- "@swc/core-linux-x64-musl": "1.12.7",
- "@swc/core-win32-arm64-msvc": "1.12.7",
- "@swc/core-win32-ia32-msvc": "1.12.7",
- "@swc/core-win32-x64-msvc": "1.12.7"
+ "@swc/core-darwin-arm64": "1.12.9",
+ "@swc/core-darwin-x64": "1.12.9",
+ "@swc/core-linux-arm-gnueabihf": "1.12.9",
+ "@swc/core-linux-arm64-gnu": "1.12.9",
+ "@swc/core-linux-arm64-musl": "1.12.9",
+ "@swc/core-linux-x64-gnu": "1.12.9",
+ "@swc/core-linux-x64-musl": "1.12.9",
+ "@swc/core-win32-arm64-msvc": "1.12.9",
+ "@swc/core-win32-ia32-msvc": "1.12.9",
+ "@swc/core-win32-x64-msvc": "1.12.9"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
@@ -5511,9 +5511,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.7.tgz",
- "integrity": "sha512-w6BBT0hBRS56yS+LbReVym0h+iB7/PpCddqrn1ha94ra4rZ4R/A91A/rkv+LnQlPqU/+fhqdlXtCJU9mrhCBtA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.9.tgz",
+ "integrity": "sha512-GACFEp4nD6V+TZNR2JwbMZRHB+Yyvp14FrcmB6UCUYmhuNWjkxi+CLnEvdbuiKyQYv0zA+TRpCHZ+whEs6gwfA==",
"cpu": [
"arm64"
],
@@ -5528,9 +5528,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.7.tgz",
- "integrity": "sha512-jN6LhFfGOpm4DY2mXPgwH4aa9GLOwublwMVFFZ/bGnHYYCRitLZs9+JWBbyWs7MyGcA246Ew+EREx36KVEAxjA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.9.tgz",
+ "integrity": "sha512-hv2kls7Ilkm2EpeJz+I9MCil7pGS3z55ZAgZfxklEuYsxpICycxeH+RNRv4EraggN44ms+FWCjtZFu0LGg2V3g==",
"cpu": [
"x64"
],
@@ -5545,9 +5545,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.7.tgz",
- "integrity": "sha512-rHn8XXi7G2StEtZRAeJ6c7nhJPDnqsHXmeNrAaYwk8Tvpa6ZYG2nT9E1OQNXj1/dfbSFTjdiA8M8ZvGYBlpBoA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.9.tgz",
+ "integrity": "sha512-od9tDPiG+wMU9wKtd6y3nYJdNqgDOyLdgRRcrj1/hrbHoUPOM8wZQZdwQYGarw63iLXGgsw7t5HAF9Yc51ilFA==",
"cpu": [
"arm"
],
@@ -5562,9 +5562,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.7.tgz",
- "integrity": "sha512-N15hKizSSh+hkZ2x3TDVrxq0TDcbvDbkQJi2ZrLb9fK+NdFUV/x+XF16ZDPlbxtrGXl1CT7VD439SNaMN9F7qw==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.9.tgz",
+ "integrity": "sha512-6qx1ka9LHcLzxIgn2Mros+CZLkHK2TawlXzi/h7DJeNnzi8F1Hw0Yzjp8WimxNCg6s2n+o3jnmin1oXB7gg8rw==",
"cpu": [
"arm64"
],
@@ -5579,9 +5579,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.7.tgz",
- "integrity": "sha512-jxyINtBezpxd3eIUDiDXv7UQ87YWlPsM9KumOwJk09FkFSO4oYxV2RT+Wu+Nt5tVWue4N0MdXT/p7SQsDEk4YA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.9.tgz",
+ "integrity": "sha512-yghFZWKPVVGbUdqiD7ft23G0JX6YFGDJPz9YbLLAwGuKZ9th3/jlWoQDAw1Naci31LQhVC+oIji6ozihSuwB2A==",
"cpu": [
"arm64"
],
@@ -5596,9 +5596,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.7.tgz",
- "integrity": "sha512-PR4tPVwU1BQBfFDk2XfzXxsEIjF3x/bOV1BzZpYvrlkU0TKUDbR4t2wzvsYwD/coW7/yoQmlL70/qnuPtTp1Zw==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.9.tgz",
+ "integrity": "sha512-SFUxyhWLZRNL8QmgGNqdi2Q43PNyFVkRZ2zIif30SOGFSxnxcf2JNeSeBgKIGVgaLSuk6xFVVCtJ3KIeaStgRg==",
"cpu": [
"x64"
],
@@ -5613,9 +5613,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.7.tgz",
- "integrity": "sha512-zy7JWfQtQItgMfUjSbbcS3DZqQUn2d9VuV0LSGpJxtTXwgzhRpF1S2Sj7cU9hGpbM27Y8RJ4DeFb3qbAufjbrw==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.9.tgz",
+ "integrity": "sha512-9FB0wM+6idCGTI20YsBNBg9xSWtkDBymnpaTCsZM3qDc0l4uOpJMqbfWhQvp17x7r/ulZfb2QY8RDvQmCL6AcQ==",
"cpu": [
"x64"
],
@@ -5630,9 +5630,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.7.tgz",
- "integrity": "sha512-52PeF0tyX04ZFD8nibNhy/GjMFOZWTEWPmIB3wpD1vIJ1po+smtBnEdRRll5WIXITKoiND8AeHlBNBPqcsdcwA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.9.tgz",
+ "integrity": "sha512-zHOusMVbOH9ik5RtRrMiGzLpKwxrPXgXkBm3SbUCa65HAdjV33NZ0/R9Rv1uPESALtEl2tzMYLUxYA5ECFDFhA==",
"cpu": [
"arm64"
],
@@ -5647,9 +5647,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.7.tgz",
- "integrity": "sha512-WzQwkNMuhB1qQShT9uUgz/mX2j7NIEPExEtzvGsBT7TlZ9j1kGZ8NJcZH/fwOFcSJL4W7DnkL7nAhx6DBlSPaA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.9.tgz",
+ "integrity": "sha512-aWZf0PqE0ot7tCuhAjRkDFf41AzzSQO0x2xRfTbnhpROp57BRJ/N5eee1VULO/UA2PIJRG7GKQky5bSGBYlFug==",
"cpu": [
"ia32"
],
@@ -5664,9 +5664,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
- "version": "1.12.7",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.7.tgz",
- "integrity": "sha512-R52ivBi2lgjl+Bd3XCPum0YfgbZq/W1AUExITysddP9ErsNSwnreYyNB3exEijiazWGcqHEas2ChiuMOP7NYrA==",
+ "version": "1.12.9",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.9.tgz",
+ "integrity": "sha512-C25fYftXOras3P3anSUeXXIpxmEkdAcsIL9yrr0j1xepTZ/yKwpnQ6g3coj8UXdeJy4GTVlR6+Ow/QiBgZQNOg==",
"cpu": [
"x64"
],
@@ -6037,9 +6037,9 @@
"license": "MIT"
},
"node_modules/@types/lodash": {
- "version": "4.17.19",
- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.19.tgz",
- "integrity": "sha512-NYqRyg/hIQrYPT9lbOeYc3kIRabJDn/k4qQHIXUpx88CBDww2fD15Sg5kbXlW86zm2XEW4g0QxkTI3/Kfkc7xQ==",
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"dev": true,
"license": "MIT"
},
@@ -6344,17 +6344,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
- "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+ "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/type-utils": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/type-utils": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -6368,7 +6368,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.35.0",
+ "@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -6384,16 +6384,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
- "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+ "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -6409,14 +6409,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
+ "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
+ "@typescript-eslint/tsconfig-utils": "^8.35.1",
+ "@typescript-eslint/types": "^8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -6431,14 +6431,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
+ "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6449,9 +6449,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
+ "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6466,14 +6466,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
- "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
+ "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -6490,9 +6490,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
+ "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6504,16 +6504,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
+ "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/project-service": "8.35.1",
+ "@typescript-eslint/tsconfig-utils": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -6559,16 +6559,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
- "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
+ "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0"
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6583,13 +6583,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
+ "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
+ "@typescript-eslint/types": "8.35.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -9510,19 +9510,19 @@
}
},
"node_modules/eslint": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
- "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
+ "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.1",
- "@eslint/config-helpers": "^0.2.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.29.0",
+ "@eslint/js": "9.30.1",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -10724,9 +10724,9 @@
}
},
"node_modules/globals": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
- "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12944,9 +12944,9 @@
"license": "MIT"
},
"node_modules/nodemailer": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
- "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz",
+ "integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -13007,9 +13007,9 @@
"peer": true
},
"node_modules/oauth4webapi": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.5.3.tgz",
- "integrity": "sha512-2bnHosmBLAQpXNBLOvaJMyMkr4Yya5ohE5Q9jqyxiN+aa7GFCzvDN1RRRMrp0NkfqRR2MTaQNkcSUCCjILD9oQ==",
+ "version": "3.5.5",
+ "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.5.5.tgz",
+ "integrity": "sha512-1K88D2GiAydGblHo39NBro5TebGXa+7tYoyIbxvqv3+haDDry7CBE1eSYuNbOSsYCCU6y0gdynVZAkm4YPw4hg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
@@ -13103,13 +13103,13 @@
}
},
"node_modules/openid-client": {
- "version": "6.6.1",
- "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.6.1.tgz",
- "integrity": "sha512-GmqoICGMI3IyFFjhvXxad8of4QWk2D0tm4vdJkldGm9nw7J3p1f7LPLWgGeFuKuw8HjDVe8Dd8QLGBe0NFvSSg==",
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.6.2.tgz",
+ "integrity": "sha512-Xya5TNMnnZuTM6DbHdB4q0S3ig2NTAELnii/ASie1xDEr8iiB8zZbO871OWBdrw++sd3hW6bqWjgcmSy1RTWHA==",
"license": "MIT",
"dependencies": {
"jose": "^6.0.11",
- "oauth4webapi": "^3.5.3"
+ "oauth4webapi": "^3.5.4"
},
"funding": {
"url": "https://github.com/sponsors/panva"
@@ -13453,14 +13453,14 @@
}
},
"node_modules/pg": {
- "version": "8.16.2",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz",
- "integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==",
+ "version": "8.16.3",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
+ "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
- "pg-protocol": "^1.10.2",
+ "pg-protocol": "^1.10.3",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
@@ -13468,7 +13468,7 @@
"node": ">= 16.0.0"
},
"optionalDependencies": {
- "pg-cloudflare": "^1.2.6"
+ "pg-cloudflare": "^1.2.7"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
@@ -13480,9 +13480,9 @@
}
},
"node_modules/pg-cloudflare": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz",
- "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==",
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
+ "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
"license": "MIT",
"optional": true
},
@@ -13520,9 +13520,9 @@
}
},
"node_modules/pg-protocol": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.2.tgz",
- "integrity": "sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
+ "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
"license": "MIT"
},
"node_modules/pg-types": {
@@ -13963,9 +13963,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
- "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
@@ -15655,9 +15655,9 @@
"license": "BSD-3-Clause"
},
"node_modules/sql-formatter": {
- "version": "15.6.5",
- "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.6.5.tgz",
- "integrity": "sha512-fr4TyM1udCSrOHOmouotwUi8dxIDhSLpYNmPePGFVzxq8/i8jd828IapE49QXG7Gzkswxo5WwdAGnYX4YpKoTg==",
+ "version": "15.6.6",
+ "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.6.6.tgz",
+ "integrity": "sha512-bZydXEXhaNDQBr8xYHC3a8thwcaMuTBp0CkKGjwGYDsIB26tnlWeWPwJtSQ0TEwiJcz9iJJON5mFPkx7XroHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17363,15 +17363,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
- "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
+ "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.35.0",
- "@typescript-eslint/parser": "8.35.0",
- "@typescript-eslint/utils": "8.35.0"
+ "@typescript-eslint/eslint-plugin": "8.35.1",
+ "@typescript-eslint/parser": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/server/src/config.ts b/server/src/config.ts
index ae4bdcd906..1fcc2e9782 100644
--- a/server/src/config.ts
+++ b/server/src/config.ts
@@ -101,6 +101,7 @@ export interface SystemConfig {
timeout: number;
storageLabelClaim: string;
storageQuotaClaim: string;
+ roleClaim: string;
};
passwordLogin: {
enabled: boolean;
@@ -263,6 +264,7 @@ export const defaults = Object.freeze({
profileSigningAlgorithm: 'none',
storageLabelClaim: 'preferred_username',
storageQuotaClaim: 'immich_quota',
+ roleClaim: 'immich_role',
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
timeout: 30_000,
},
diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts
index 03ef9192db..b0385984b4 100644
--- a/server/src/dtos/system-config.dto.ts
+++ b/server/src/dtos/system-config.dto.ts
@@ -395,6 +395,9 @@ class SystemConfigOAuthDto {
@IsString()
storageQuotaClaim!: string;
+
+ @IsString()
+ roleClaim!: string;
}
class SystemConfigPasswordLoginDto {
diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts
index 3568bb9d6b..85c9f07815 100644
--- a/server/src/services/auth.service.spec.ts
+++ b/server/src/services/auth.service.spec.ts
@@ -711,6 +711,7 @@ describe(AuthService.name, () => {
expect(mocks.user.create).toHaveBeenCalledWith({
email: user.email,
+ isAdmin: false,
name: ' ',
oauthId: user.oauthId,
quotaSizeInBytes: 0,
@@ -739,6 +740,7 @@ describe(AuthService.name, () => {
expect(mocks.user.create).toHaveBeenCalledWith({
email: user.email,
+ isAdmin: false,
name: ' ',
oauthId: user.oauthId,
quotaSizeInBytes: 5_368_709_120,
@@ -805,6 +807,93 @@ describe(AuthService.name, () => {
expect(mocks.user.update).not.toHaveBeenCalled();
expect(mocks.oauth.getProfilePicture).not.toHaveBeenCalled();
});
+
+ it('should only allow "admin" and "user" for the role claim', async () => {
+ const user = factory.userAdmin({ oauthId: 'oauth-id' });
+
+ mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithAutoRegister);
+ mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_role: 'foo' });
+ mocks.user.getByEmail.mockResolvedValue(void 0);
+ mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
+ mocks.user.getByOAuthId.mockResolvedValue(void 0);
+ mocks.user.create.mockResolvedValue(user);
+ mocks.session.create.mockResolvedValue(factory.session());
+
+ await expect(
+ sut.callback(
+ { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' },
+ {},
+ loginDetails,
+ ),
+ ).resolves.toEqual(oauthResponse(user));
+
+ expect(mocks.user.create).toHaveBeenCalledWith({
+ email: user.email,
+ name: ' ',
+ oauthId: user.oauthId,
+ quotaSizeInBytes: null,
+ storageLabel: null,
+ isAdmin: false,
+ });
+ });
+
+ it('should create an admin user if the role claim is set to admin', async () => {
+ const user = factory.userAdmin({ oauthId: 'oauth-id' });
+
+ mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithAutoRegister);
+ mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_role: 'admin' });
+ mocks.user.getByEmail.mockResolvedValue(void 0);
+ mocks.user.getByOAuthId.mockResolvedValue(void 0);
+ mocks.user.create.mockResolvedValue(user);
+ mocks.session.create.mockResolvedValue(factory.session());
+
+ await expect(
+ sut.callback(
+ { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' },
+ {},
+ loginDetails,
+ ),
+ ).resolves.toEqual(oauthResponse(user));
+
+ expect(mocks.user.create).toHaveBeenCalledWith({
+ email: user.email,
+ name: ' ',
+ oauthId: user.oauthId,
+ quotaSizeInBytes: null,
+ storageLabel: null,
+ isAdmin: true,
+ });
+ });
+
+ it('should accept a custom role claim', async () => {
+ const user = factory.userAdmin({ oauthId: 'oauth-id' });
+
+ mocks.systemMetadata.get.mockResolvedValue({
+ oauth: { ...systemConfigStub.oauthWithAutoRegister, roleClaim: 'my_role' },
+ });
+ mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, my_role: 'admin' });
+ mocks.user.getByEmail.mockResolvedValue(void 0);
+ mocks.user.getByOAuthId.mockResolvedValue(void 0);
+ mocks.user.create.mockResolvedValue(user);
+ mocks.session.create.mockResolvedValue(factory.session());
+
+ await expect(
+ sut.callback(
+ { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' },
+ {},
+ loginDetails,
+ ),
+ ).resolves.toEqual(oauthResponse(user));
+
+ expect(mocks.user.create).toHaveBeenCalledWith({
+ email: user.email,
+ name: ' ',
+ oauthId: user.oauthId,
+ quotaSizeInBytes: null,
+ storageLabel: null,
+ isAdmin: true,
+ });
+ });
});
describe('link', () => {
diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts
index 70da8d81d3..ec3415ec8c 100644
--- a/server/src/services/auth.service.ts
+++ b/server/src/services/auth.service.ts
@@ -250,7 +250,7 @@ export class AuthService extends BaseService {
const { oauth } = await this.getConfig({ withCache: false });
const url = this.resolveRedirectUri(oauth, dto.url);
const profile = await this.oauthRepository.getProfile(oauth, url, expectedState, codeVerifier);
- const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim } = oauth;
+ const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim, roleClaim } = oauth;
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
let user: UserAdmin | undefined = await this.userRepository.getByOAuthId(profile.sub);
@@ -290,6 +290,11 @@ export class AuthService extends BaseService {
default: defaultStorageQuota,
isValid: (value: unknown) => Number(value) >= 0,
});
+ const role = this.getClaim<'admin' | 'user'>(profile, {
+ key: roleClaim,
+ default: 'user',
+ isValid: (value: unknown) => isString(value) && ['admin', 'user'].includes(value),
+ });
const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`;
user = await this.createUser({
@@ -298,6 +303,7 @@ export class AuthService extends BaseService {
oauthId: profile.sub,
quotaSizeInBytes: storageQuota === null ? null : storageQuota * HumanReadableSize.GiB,
storageLabel: storageLabel || null,
+ isAdmin: role === 'admin',
});
}
diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts
index 87bd92129e..c7b98cc990 100644
--- a/server/src/services/system-config.service.spec.ts
+++ b/server/src/services/system-config.service.spec.ts
@@ -124,6 +124,7 @@ const updatedConfig = Object.freeze({
timeout: 30_000,
storageLabelClaim: 'preferred_username',
storageQuotaClaim: 'immich_quota',
+ roleClaim: 'immich_role',
},
passwordLogin: {
enabled: true,
diff --git a/web/package-lock.json b/web/package-lock.json
index 82942102fb..6af393b042 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -740,9 +740,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
- "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
+ "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -777,9 +777,9 @@
}
},
"node_modules/@faker-js/faker": {
- "version": "9.8.0",
- "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.8.0.tgz",
- "integrity": "sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==",
+ "version": "9.9.0",
+ "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.9.0.tgz",
+ "integrity": "sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==",
"dev": true,
"funding": [
{
@@ -1337,9 +1337,9 @@
"link": true
},
"node_modules/@immich/ui": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.23.2.tgz",
- "integrity": "sha512-eBSZPp2f5ms0jX+fOUiJizY1IqzuGn6latsJa8Pk+wSK0HizLHA6weI3CJoyzPCrOn7L8rRja/pBr8JA/E0Z6g==",
+ "version": "0.23.3",
+ "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.23.3.tgz",
+ "integrity": "sha512-YbYJSv3HqDu2+6MmiHhLThSessZ6HkoVOWun/ZoGb8mKj5x/ZZ4AyXGPIqbyKTamsjzbcD9FInij70G+m4egkg==",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@mdi/js": "^7.4.47",
@@ -2266,9 +2266,9 @@
}
},
"node_modules/@tailwindcss/node": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
- "integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
+ "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2278,13 +2278,13 @@
"lightningcss": "1.30.1",
"magic-string": "^0.30.17",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.1.10"
+ "tailwindcss": "4.1.11"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz",
- "integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
+ "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -2296,24 +2296,24 @@
"node": ">= 10"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.10",
- "@tailwindcss/oxide-darwin-arm64": "4.1.10",
- "@tailwindcss/oxide-darwin-x64": "4.1.10",
- "@tailwindcss/oxide-freebsd-x64": "4.1.10",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.10",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.10",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.10",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.10",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.10"
+ "@tailwindcss/oxide-android-arm64": "4.1.11",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.11",
+ "@tailwindcss/oxide-darwin-x64": "4.1.11",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.11",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.11",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.11",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz",
- "integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
+ "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
"cpu": [
"arm64"
],
@@ -2328,9 +2328,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz",
- "integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
+ "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
"cpu": [
"arm64"
],
@@ -2345,9 +2345,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz",
- "integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
+ "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
"cpu": [
"x64"
],
@@ -2362,9 +2362,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz",
- "integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
+ "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
"cpu": [
"x64"
],
@@ -2379,9 +2379,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz",
- "integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
+ "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
"cpu": [
"arm"
],
@@ -2396,9 +2396,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz",
- "integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
+ "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
"cpu": [
"arm64"
],
@@ -2413,9 +2413,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz",
- "integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
+ "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
"cpu": [
"arm64"
],
@@ -2430,9 +2430,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz",
- "integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
+ "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
"cpu": [
"x64"
],
@@ -2447,9 +2447,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz",
- "integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
+ "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
"cpu": [
"x64"
],
@@ -2464,9 +2464,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz",
- "integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
+ "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -2485,7 +2485,7 @@
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2",
- "@napi-rs/wasm-runtime": "^0.2.10",
+ "@napi-rs/wasm-runtime": "^0.2.11",
"@tybys/wasm-util": "^0.9.0",
"tslib": "^2.8.0"
},
@@ -2494,9 +2494,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz",
- "integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
+ "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
"cpu": [
"arm64"
],
@@ -2511,9 +2511,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz",
- "integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
+ "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
"cpu": [
"x64"
],
@@ -2605,18 +2605,18 @@
}
},
"node_modules/@tailwindcss/vite": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.10.tgz",
- "integrity": "sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz",
+ "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@tailwindcss/node": "4.1.10",
- "@tailwindcss/oxide": "4.1.10",
- "tailwindcss": "4.1.10"
+ "@tailwindcss/node": "4.1.11",
+ "@tailwindcss/oxide": "4.1.11",
+ "tailwindcss": "4.1.11"
},
"peerDependencies": {
- "vite": "^5.2.0 || ^6"
+ "vite": "^5.2.0 || ^6 || ^7"
}
},
"node_modules/@testing-library/dom": {
@@ -2942,17 +2942,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
- "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+ "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/type-utils": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/type-utils": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2966,187 +2966,11 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.35.0",
+ "@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
- "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
- "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -3157,33 +2981,17 @@
"node": ">= 4"
}
},
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/@typescript-eslint/parser": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
- "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+ "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -3198,150 +3006,6 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/parser/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/@typescript-eslint/project-service": {
"version": "8.35.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
@@ -4720,9 +4384,9 @@
}
},
"node_modules/dotenv": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
- "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -5053,19 +4717,19 @@
}
},
"node_modules/eslint": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
- "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
+ "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.1",
- "@eslint/config-helpers": "^0.2.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.29.0",
+ "@eslint/js": "9.30.1",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -5256,9 +4920,9 @@
}
},
"node_modules/eslint-plugin-svelte": {
- "version": "3.9.3",
- "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.9.3.tgz",
- "integrity": "sha512-PlcyK80sqAZ43IITeZkgl3zPFWJytx/Joup9iKGqIOsXM2m3pWfPbWuXPr5PN3loXFEypqTY/JyZwNqlSpSvRw==",
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.10.1.tgz",
+ "integrity": "sha512-csCh2x0ge/DugXC7dCANh46Igi7bjMZEy6rHZCdS13AoGVJSu7a90Kru3I8oMYLGEemPRE1hQXadxvRPVMAAXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5381,6 +5045,31 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@eslint/config-helpers": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
"node_modules/esm-env": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
@@ -5448,9 +5137,9 @@
}
},
"node_modules/esrap": {
- "version": "1.4.9",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.9.tgz",
- "integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz",
+ "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -6006,9 +5695,9 @@
}
},
"node_modules/globals": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
- "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7204,9 +6893,9 @@
}
},
"node_modules/maplibre-gl": {
- "version": "5.6.0",
- "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.6.0.tgz",
- "integrity": "sha512-7TuHMozUC4rlIp08bSsxCixFn18P24otrlZU/7UGCO5RufFTJadFzauTrvBHr9FB67MbJ6nvFXEftGd0bUl4Iw==",
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.6.1.tgz",
+ "integrity": "sha512-TTSfoTaF7RqKUR9wR5qDxCHH2J1XfZ1E85luiLOx0h8r50T/LnwAwwfV0WVNh9o8dA7rwt57Ucivf1emyeukXg==",
"license": "BSD-3-Clause",
"dependencies": {
"@mapbox/geojson-rewind": "^0.5.2",
@@ -8071,9 +7760,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
- "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -9109,9 +8798,9 @@
}
},
"node_modules/svelte": {
- "version": "5.34.8",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.34.8.tgz",
- "integrity": "sha512-TF+8irl7rpj3+fpaLuPRX5BqReTAqckp0Fumxa/mCeK3fo0/MnBb9W/Z2bLwtqj3C3r5Lm6NKIAw7YrgIv1Fwg==",
+ "version": "5.35.0",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.35.0.tgz",
+ "integrity": "sha512-zgtdzln2aPYbsyeeFSjiQVrojZZU2iPYu07FRzfNme7NCBWo00Xn3B5JP/wMhhYG7nK8BZfrqREfoJRHYAOA5w==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
@@ -9123,7 +8812,7 @@
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
- "esrap": "^1.4.8",
+ "esrap": "^2.0.0",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
@@ -9217,9 +8906,9 @@
}
},
"node_modules/svelte-maplibre": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-1.0.0.tgz",
- "integrity": "sha512-lw6t0dnsaYzIPECqymSPnODuWGQnVVzEN1F0cPTXOIDcltEMkefDL3E5MnQ5YU4BY+VYedRDFYo7swEjZwpQCA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-1.1.0.tgz",
+ "integrity": "sha512-Qo6ASfa2vCaqShlFP7rDbXxBTrI4iP0jsyRhLXryLS5WOD/G0XI6ByW4kMTydcgMqdox7CmsKZLKT9V3hQ3VCA==",
"license": "MIT",
"dependencies": {
"d3-geo": "^3.1.0",
@@ -9360,9 +9049,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
- "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
+ "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"license": "MIT"
},
"node_modules/tapable": {
@@ -9673,15 +9362,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
- "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
+ "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.35.0",
- "@typescript-eslint/parser": "8.35.0",
- "@typescript-eslint/utils": "8.35.0"
+ "@typescript-eslint/eslint-plugin": "8.35.1",
+ "@typescript-eslint/parser": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9695,174 +9384,6 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
- "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/typescript-eslint/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/typescript-eslint/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
diff --git a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
index f0dbc1a2da..a1926b4020 100644
--- a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
@@ -167,6 +167,16 @@
isEdited={!(config.oauth.storageLabelClaim == savedConfig.oauth.storageLabelClaim)}
/>
+
+