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)} /> + +