Compare commits

...

19 Commits

Author SHA1 Message Date
martabal 59300d2097 feat: preload textual model 2024-09-25 18:22:54 +02:00
martabal d34d631dd4 feat: preload textual model 2024-09-25 17:39:55 +02:00
renovate[bot] 46fe60693e chore(deps): update dependency @types/react to v18.3.8 (#12918)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-25 09:56:02 -04:00
Cary Keesler 06f1376de3 fix(web): Updated web README.md (#12899)
Updated web README.md
2024-09-25 12:59:35 +00:00
Jason Rasmussen 05d8c4c132 fix: do not use trashed assets as album covers (#12905) 2024-09-24 17:53:57 -04:00
Daniel Dietzler b45fce8ddf fix: album title state weirdness (#12874) 2024-09-24 11:13:37 -04:00
Matthew Momjian af8f3774d0 docs: details for windows users how to change docker volume (#12551)
* details for windows users

* Update requirements.md
2024-09-24 10:38:13 -04:00
renovate[bot] b85d8943e7 chore(deps): update base-image to v20240924 (major) (#12893)
chore(deps): update base-image to v20240924

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-24 10:36:25 -04:00
JonOcto f031c09687 fix(docs): typo in remote-access.md (#12895)
Fixed typo in remote-access.md

Fixed spelling of "tutorial".
2024-09-24 10:18:07 -04:00
Fynn Petersen-Frey 202082f62e refactor(mobile): use repositories in a number of services (#12891)
* UserService
* PartnerService
* HashService
* MemoryService
* PersonService
* SearchService
* StackService
2024-09-24 12:50:21 +00:00
Fynn Petersen-Frey e0fa3cdbc7 refactor(mobile): more repositories (#12879)
* ExifInfoRepository
 * ActivityApiRepository
 * initial AssetApiRepository
2024-09-24 08:24:48 +02:00
renovate[bot] 56f680ce04 chore(deps): update typescript-projects (#12882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 22:05:04 -04:00
Daniel Dietzler ec32a9e610 fix: set min values for face detection to reasonable values (#12877)
fix: set min values for face detection to >0
2024-09-23 22:03:59 -04:00
Zack Pollard bcd416477b feat: serve map tile styles from tiles.immich.cloud (#12858)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-09-23 20:30:23 +00:00
Daniel Dietzler e41785b1a1 fix: open api (#12878) 2024-09-23 20:08:01 +00:00
Jason Rasmussen ad33ce5938 refactor(mobile): open api dto upgrade (#12793) 2024-09-23 15:41:41 -04:00
Jason Rasmussen 3008050e4c fix: remove no longer needed LD_LIBRARY_PATH (#12872) 2024-09-23 13:51:03 -04:00
Daniel Dietzler 87c54d6659 fix: show asset count for unassigned faces (#12871) 2024-09-23 17:37:08 +00:00
martabal 708a53a1eb feat: preload textual model 2024-09-16 17:53:43 +02:00
301 changed files with 2792 additions and 2528 deletions
+87 -113
View File
@@ -1353,17 +1353,17 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
"integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.5.0", "@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/type-utils": "8.5.0", "@typescript-eslint/type-utils": "8.6.0",
"@typescript-eslint/utils": "8.5.0", "@typescript-eslint/utils": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0", "@typescript-eslint/visitor-keys": "8.6.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -1387,16 +1387,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
"integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.5.0", "@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/typescript-estree": "8.5.0", "@typescript-eslint/typescript-estree": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0", "@typescript-eslint/visitor-keys": "8.6.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1416,14 +1416,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
"integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0" "@typescript-eslint/visitor-keys": "8.6.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1434,14 +1434,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
"integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.5.0", "@typescript-eslint/typescript-estree": "8.6.0",
"@typescript-eslint/utils": "8.5.0", "@typescript-eslint/utils": "8.6.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@@ -1459,9 +1459,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
"integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1473,14 +1473,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
"integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0", "@typescript-eslint/visitor-keys": "8.6.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -1502,16 +1502,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
"integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.5.0", "@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/typescript-estree": "8.5.0" "@typescript-eslint/typescript-estree": "8.6.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1525,13 +1525,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
"integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@@ -1543,9 +1543,9 @@
} }
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.1.tgz",
"integrity": "sha512-yqCkr2nrV4o58VcVMxTVkS6Ggxzy7pmSD8JbTbhbH5PsQfUIES1QT716VUzo33wf2lX9EcWYdT3Vl2MMmjR59g==", "integrity": "sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1566,8 +1566,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "2.1.0", "@vitest/browser": "2.1.1",
"vitest": "2.1.0" "vitest": "2.1.1"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -1576,14 +1576,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz",
"integrity": "sha512-N3/xR4fSu0+6sVZETEtPT1orUs2+Y477JOXTcU3xKuu3uBlsgbD7/7Mz2LZ1Jr1XjwilEWlrIgSCj4N1+5ZmsQ==", "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "2.1.0", "@vitest/spy": "2.1.1",
"@vitest/utils": "2.1.0", "@vitest/utils": "2.1.1",
"chai": "^5.1.1", "chai": "^5.1.1",
"tinyrainbow": "^1.2.0" "tinyrainbow": "^1.2.0"
}, },
@@ -1592,9 +1592,9 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz",
"integrity": "sha512-ZxENovUqhzl+QiOFpagiHUNUuZ1qPd5yYTCYHomGIZOFArzn4mgX2oxZmiAItJWAaXHG6bbpb/DpSPhlk5DgtA==", "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1606,7 +1606,7 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/spy": "2.1.0", "@vitest/spy": "2.1.1",
"msw": "^2.3.5", "msw": "^2.3.5",
"vite": "^5.0.0" "vite": "^5.0.0"
}, },
@@ -1633,13 +1633,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz",
"integrity": "sha512-D9+ZiB8MbMt7qWDRJc4CRNNUlne/8E1X7dcKhZVAbcOKG58MGGYVDqAq19xlhNfMFZsW0bpVKgztBwks38Ko0w==", "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "2.1.0", "@vitest/utils": "2.1.1",
"pathe": "^1.1.2" "pathe": "^1.1.2"
}, },
"funding": { "funding": {
@@ -1647,13 +1647,13 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz",
"integrity": "sha512-x69CygGMzt9VCO283K2/FYQ+nBrOj66OTKpsPykjCR4Ac3lLV+m85hj9reaIGmjBSsKzVvbxWmjWE3kF5ha3uQ==", "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "2.1.0", "@vitest/pretty-format": "2.1.1",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
"pathe": "^1.1.2" "pathe": "^1.1.2"
}, },
@@ -1661,23 +1661,10 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.0.tgz",
"integrity": "sha512-7sxf2F3DNYatgmzXXcTh6cq+/fxwB47RIQqZJFoSH883wnVAoccSRT6g+dTKemUBo8Q5N4OYYj1EBXLuRKvp3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz",
"integrity": "sha512-IXX5NkbdgTYTog3F14i2LgnBc+20YmkXMx0IWai84mcxySUDRgm0ihbOfR4L0EVRBDFG85GjmQQEZNNKVVpkZw==", "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1688,13 +1675,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz",
"integrity": "sha512-rreyfVe0PuNqJfKYUwfPDfi6rrp0VSu0Wgvp5WBqJonP+4NvXHk48X6oBam1Lj47Hy6jbJtnMj3OcRdrkTP0tA==", "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "2.1.0", "@vitest/pretty-format": "2.1.1",
"loupe": "^3.1.1", "loupe": "^3.1.1",
"tinyrainbow": "^1.2.0" "tinyrainbow": "^1.2.0"
}, },
@@ -1702,19 +1689,6 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/utils/node_modules/@vitest/pretty-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.0.tgz",
"integrity": "sha512-7sxf2F3DNYatgmzXXcTh6cq+/fxwB47RIQqZJFoSH883wnVAoccSRT6g+dTKemUBo8Q5N4OYYj1EBXLuRKvp3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.1", "version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -4241,9 +4215,9 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.0.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz",
"integrity": "sha512-+ybYqBVUjYyIscoLzMWodus2enQDZOpGhcU6HdOVD6n8WZdk12w1GFL3mbnxLs7hPtRtqs1Wo5YF6/Tsr6fmhg==", "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4283,19 +4257,19 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.0.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz",
"integrity": "sha512-XuuEeyNkqbfr0FtAvd9vFbInSSNY1ykCQTYQ0sj9wPy4hx+1gR7gqVNdW0AX2wrrM1wWlN5fnJDjF9xG6mYRSQ==", "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "2.1.0", "@vitest/expect": "2.1.1",
"@vitest/mocker": "2.1.0", "@vitest/mocker": "2.1.1",
"@vitest/pretty-format": "^2.1.0", "@vitest/pretty-format": "^2.1.1",
"@vitest/runner": "2.1.0", "@vitest/runner": "2.1.1",
"@vitest/snapshot": "2.1.0", "@vitest/snapshot": "2.1.1",
"@vitest/spy": "2.1.0", "@vitest/spy": "2.1.1",
"@vitest/utils": "2.1.0", "@vitest/utils": "2.1.1",
"chai": "^5.1.1", "chai": "^5.1.1",
"debug": "^4.3.6", "debug": "^4.3.6",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
@@ -4306,7 +4280,7 @@
"tinypool": "^1.0.0", "tinypool": "^1.0.0",
"tinyrainbow": "^1.2.0", "tinyrainbow": "^1.2.0",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-node": "2.1.0", "vite-node": "2.1.1",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@@ -4321,8 +4295,8 @@
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0", "@types/node": "^18.0.0 || >=20.0.0",
"@vitest/browser": "2.1.0", "@vitest/browser": "2.1.1",
"@vitest/ui": "2.1.0", "@vitest/ui": "2.1.1",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
-1
View File
@@ -51,5 +51,4 @@ services:
volumes: volumes:
- /usr/lib/wsl:/usr/lib/wsl - /usr/lib/wsl:/usr/lib/wsl
environment: environment:
- LD_LIBRARY_PATH=/usr/lib/wsl/lib
- LIBVA_DRIVER_NAME=d3d12 - LIBVA_DRIVER_NAME=d3d12
+1 -1
View File
@@ -27,7 +27,7 @@ You may use a VPN service to open an encrypted connection to your Immich instanc
If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation). If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation).
:::tip Video toturial :::tip Video tutorial
You can learn how to set up Tailscale together with Immich with the [tutorial video](https://www.youtube.com/watch?v=Vt4PDUXB_fg) they created. You can learn how to set up Tailscale together with Immich with the [tutorial video](https://www.youtube.com/watch?v=Vt4PDUXB_fg) they created.
::: :::
+1
View File
@@ -58,6 +58,7 @@ Optionally, you can enable hardware acceleration for machine learning and transc
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets. - Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publically exposed, so this password is only used for local authentication. - Consider changing `DB_PASSWORD` to a custom value. Postgres is not publically exposed, so this password is only used for local authentication.
To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`. To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`.
- Set your timezone by uncommenting the `TZ=` line.
### Step 3 - Start the containers ### Step 3 - Start the containers
+8 -20
View File
@@ -27,23 +27,14 @@ If this should not work, try running `docker compose up -d --force-recreate`.
These environment variables are used by the `docker-compose.yml` file and do **NOT** affect the containers directly. These environment variables are used by the `docker-compose.yml` file and do **NOT** affect the containers directly.
::: :::
### Supported filesystems
The Immich Postgres database (`DB_DATA_LOCATION`) must be located on a filesystem that supports user/group
ownership and permissions (EXT2/3/4, ZFS, APFS, BTRFS, XFS, etc.). It will not work on any filesystem formatted in NTFS or ex/FAT/32.
It will not work in WSL (Windows Subsystem for Linux) when using a mounted host directory (commonly under `/mnt`).
If this is an issue, you can change the bind mount to a Docker volume instead.
Regardless of filesystem, it is not recommended to use a network share for your database location due to performance and possible data loss issues.
## General ## General
| Variable | Description | Default | Containers | Workers | | Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | | :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | | server | microservices | | `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | | `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | | `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location inside the container ⚠️**You probably shouldn't set this**<sup>\*1</sup>⚠️ | `./upload`<sup>\*2</sup> | server | api, microservices | | `IMMICH_MEDIA_LOCATION` | Media Location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `./upload`<sup>\*3</sup> | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | | `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | | `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | | | `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
@@ -52,16 +43,13 @@ Regardless of filesystem, it is not recommended to use a network share for your
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | | `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api | | `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
\*1: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead. \*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
\*2: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only need to be set if the Immich deployment method is changing.
:::tip
`TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution. `TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
:::
\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
\*3: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only need to be set if the Immich deployment method is changing.
## Workers ## Workers
+29 -3
View File
@@ -23,7 +23,33 @@ Immich requires the command `docker compose` - the similarly named `docker-compo
- **RAM**: Minimum 4GB, recommended 6GB. - **RAM**: Minimum 4GB, recommended 6GB.
- **CPU**: Minimum 2 cores, recommended 4 cores. - **CPU**: Minimum 2 cores, recommended 4 cores.
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions. - **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
- This can present an issue for Windows users. See [here](/docs/install/environment-variables#supported-filesystems) - This can present an issue for Windows users. See below for details and an alternative setup.
for more details and alternatives.
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average. - The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
- Network shares are supported for the storage of image and video assets only. - Network shares are supported for the storage of image and video assets only. It is not recommended to use a network share for your database location due to performance and possible data loss issues.
### Special requirements for Windows users
<details>
<summary>Database storage on Windows systems</summary>
The Immich Postgres database (`DB_DATA_LOCATION`) must be located on a filesystem that supports user/group
ownership and permissions (EXT2/3/4, ZFS, APFS, BTRFS, XFS, etc.). It will not work on any filesystem formatted in NTFS or ex/FAT/32.
It will not work in WSL (Windows Subsystem for Linux) when using a mounted host directory (commonly under `/mnt`).
If this is an issue, you can change the bind mount to a Docker volume instead as follows:
Make the following change to `.env`:
```diff
- DB_DATA_LOCATION=./postgres
+ DB_DATA_LOCATION=pgdata
```
Add the following line to the bottom of `docker-compose.yml`:
```diff
volumes:
model-cache:
+ pgdata:
```
</details>
+3 -3
View File
@@ -16091,9 +16091,9 @@
} }
}, },
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.11", "version": "3.4.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz",
"integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==", "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
+207 -223
View File
@@ -1149,13 +1149,13 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.47.0", "version": "1.47.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.0.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.1.tgz",
"integrity": "sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==", "integrity": "sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.47.0" "playwright": "1.47.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -1165,9 +1165,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
"integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1179,9 +1179,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
"integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1193,9 +1193,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
"integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1207,9 +1207,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
"integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1221,9 +1221,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
"integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1235,9 +1235,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
"integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1249,9 +1249,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
"integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1263,9 +1263,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
"integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1277,9 +1277,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
"integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -1291,9 +1291,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
"integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -1305,9 +1305,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
"integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -1319,9 +1319,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
"integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1333,9 +1333,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
"integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1347,9 +1347,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
"integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1361,9 +1361,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
"integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1375,9 +1375,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
"integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1471,9 +1471,9 @@
} }
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -1596,9 +1596,9 @@
} }
}, },
"node_modules/@types/pg": { "node_modules/@types/pg": {
"version": "8.11.9", "version": "8.11.10",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.9.tgz", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.10.tgz",
"integrity": "sha512-M4mYeJZRBD9lCBCGa72F44uKSV9eJrAFfjlPJagdA6pgIr2OPJULFB7nqnZzOdqXG0qzHlgtZKzTdIgbmHitSg==", "integrity": "sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1733,17 +1733,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
"integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.5.0", "@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/type-utils": "8.5.0", "@typescript-eslint/type-utils": "8.6.0",
"@typescript-eslint/utils": "8.5.0", "@typescript-eslint/utils": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0", "@typescript-eslint/visitor-keys": "8.6.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -1767,16 +1767,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
"integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.5.0", "@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/typescript-estree": "8.5.0", "@typescript-eslint/typescript-estree": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0", "@typescript-eslint/visitor-keys": "8.6.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1796,14 +1796,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
"integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0" "@typescript-eslint/visitor-keys": "8.6.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1814,14 +1814,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
"integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.5.0", "@typescript-eslint/typescript-estree": "8.6.0",
"@typescript-eslint/utils": "8.5.0", "@typescript-eslint/utils": "8.6.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@@ -1839,9 +1839,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
"integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1853,14 +1853,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
"integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/visitor-keys": "8.5.0", "@typescript-eslint/visitor-keys": "8.6.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -1908,16 +1908,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
"integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.5.0", "@typescript-eslint/scope-manager": "8.6.0",
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"@typescript-eslint/typescript-estree": "8.5.0" "@typescript-eslint/typescript-estree": "8.6.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1931,13 +1931,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.5.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
"integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.5.0", "@typescript-eslint/types": "8.6.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@@ -1949,9 +1949,9 @@
} }
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.1.tgz",
"integrity": "sha512-yqCkr2nrV4o58VcVMxTVkS6Ggxzy7pmSD8JbTbhbH5PsQfUIES1QT716VUzo33wf2lX9EcWYdT3Vl2MMmjR59g==", "integrity": "sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1972,8 +1972,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "2.1.0", "@vitest/browser": "2.1.1",
"vitest": "2.1.0" "vitest": "2.1.1"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -1982,14 +1982,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz",
"integrity": "sha512-N3/xR4fSu0+6sVZETEtPT1orUs2+Y477JOXTcU3xKuu3uBlsgbD7/7Mz2LZ1Jr1XjwilEWlrIgSCj4N1+5ZmsQ==", "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "2.1.0", "@vitest/spy": "2.1.1",
"@vitest/utils": "2.1.0", "@vitest/utils": "2.1.1",
"chai": "^5.1.1", "chai": "^5.1.1",
"tinyrainbow": "^1.2.0" "tinyrainbow": "^1.2.0"
}, },
@@ -1998,9 +1998,9 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz",
"integrity": "sha512-ZxENovUqhzl+QiOFpagiHUNUuZ1qPd5yYTCYHomGIZOFArzn4mgX2oxZmiAItJWAaXHG6bbpb/DpSPhlk5DgtA==", "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2012,7 +2012,7 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/spy": "2.1.0", "@vitest/spy": "2.1.1",
"msw": "^2.3.5", "msw": "^2.3.5",
"vite": "^5.0.0" "vite": "^5.0.0"
}, },
@@ -2039,13 +2039,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz",
"integrity": "sha512-D9+ZiB8MbMt7qWDRJc4CRNNUlne/8E1X7dcKhZVAbcOKG58MGGYVDqAq19xlhNfMFZsW0bpVKgztBwks38Ko0w==", "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "2.1.0", "@vitest/utils": "2.1.1",
"pathe": "^1.1.2" "pathe": "^1.1.2"
}, },
"funding": { "funding": {
@@ -2053,13 +2053,13 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz",
"integrity": "sha512-x69CygGMzt9VCO283K2/FYQ+nBrOj66OTKpsPykjCR4Ac3lLV+m85hj9reaIGmjBSsKzVvbxWmjWE3kF5ha3uQ==", "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "2.1.0", "@vitest/pretty-format": "2.1.1",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
"pathe": "^1.1.2" "pathe": "^1.1.2"
}, },
@@ -2067,23 +2067,10 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.0.tgz",
"integrity": "sha512-7sxf2F3DNYatgmzXXcTh6cq+/fxwB47RIQqZJFoSH883wnVAoccSRT6g+dTKemUBo8Q5N4OYYj1EBXLuRKvp3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz",
"integrity": "sha512-IXX5NkbdgTYTog3F14i2LgnBc+20YmkXMx0IWai84mcxySUDRgm0ihbOfR4L0EVRBDFG85GjmQQEZNNKVVpkZw==", "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2094,13 +2081,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz",
"integrity": "sha512-rreyfVe0PuNqJfKYUwfPDfi6rrp0VSu0Wgvp5WBqJonP+4NvXHk48X6oBam1Lj47Hy6jbJtnMj3OcRdrkTP0tA==", "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "2.1.0", "@vitest/pretty-format": "2.1.1",
"loupe": "^3.1.1", "loupe": "^3.1.1",
"tinyrainbow": "^1.2.0" "tinyrainbow": "^1.2.0"
}, },
@@ -2108,19 +2095,6 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/utils/node_modules/@vitest/pretty-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.0.tgz",
"integrity": "sha512-7sxf2F3DNYatgmzXXcTh6cq+/fxwB47RIQqZJFoSH883wnVAoccSRT6g+dTKemUBo8Q5N4OYYj1EBXLuRKvp3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/abbrev": { "node_modules/abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -4168,9 +4142,9 @@
} }
}, },
"node_modules/jose": { "node_modules/jose": {
"version": "5.8.0", "version": "5.9.2",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.8.0.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.2.tgz",
"integrity": "sha512-E7CqYpL/t7MMnfGnK/eg416OsFCVUrU/Y3Vwe7QjKhu/BkS1Ms455+2xsqZQVN57/U2MHMBvEb5SrmAZWAIntA==", "integrity": "sha512-ILI2xx/I57b20sd7rHZvgiiQrmp2mcotwsAH+5ajbpFQbrYVQdNHYlQhoA5cFb78CgtBOxtC05TeA+mcgkuCqQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
@@ -5016,9 +4990,9 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "6.2.2", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
"dev": true "dev": true
}, },
"node_modules/pathe": { "node_modules/pathe": {
@@ -5039,15 +5013,15 @@
} }
}, },
"node_modules/pg": { "node_modules/pg": {
"version": "8.12.0", "version": "8.13.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz",
"integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", "integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.6.4", "pg-connection-string": "^2.7.0",
"pg-pool": "^3.6.2", "pg-pool": "^3.7.0",
"pg-protocol": "^1.6.1", "pg-protocol": "^1.7.0",
"pg-types": "^2.1.0", "pg-types": "^2.1.0",
"pgpass": "1.x" "pgpass": "1.x"
}, },
@@ -5074,10 +5048,11 @@
"optional": true "optional": true
}, },
"node_modules/pg-connection-string": { "node_modules/pg-connection-string": {
"version": "2.6.4", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
"integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==", "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/pg-int8": { "node_modules/pg-int8": {
"version": "1.0.1", "version": "1.0.1",
@@ -5098,19 +5073,21 @@
} }
}, },
"node_modules/pg-pool": { "node_modules/pg-pool": {
"version": "3.6.2", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
"integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
"dev": true, "dev": true,
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"pg": ">=8.0" "pg": ">=8.0"
} }
}, },
"node_modules/pg-protocol": { "node_modules/pg-protocol": {
"version": "1.6.1", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
"integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==", "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/pg-types": { "node_modules/pg-types": {
"version": "2.2.0", "version": "2.2.0",
@@ -5158,13 +5135,13 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.47.0", "version": "1.47.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.0.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.1.tgz",
"integrity": "sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==", "integrity": "sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.47.0" "playwright-core": "1.47.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -5177,9 +5154,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.47.0", "version": "1.47.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.0.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.1.tgz",
"integrity": "sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==", "integrity": "sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@@ -5629,9 +5606,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.21.3", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
"integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5645,25 +5622,32 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.21.3", "@rollup/rollup-android-arm-eabi": "4.22.4",
"@rollup/rollup-android-arm64": "4.21.3", "@rollup/rollup-android-arm64": "4.22.4",
"@rollup/rollup-darwin-arm64": "4.21.3", "@rollup/rollup-darwin-arm64": "4.22.4",
"@rollup/rollup-darwin-x64": "4.21.3", "@rollup/rollup-darwin-x64": "4.22.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.21.3", "@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
"@rollup/rollup-linux-arm-musleabihf": "4.21.3", "@rollup/rollup-linux-arm-musleabihf": "4.22.4",
"@rollup/rollup-linux-arm64-gnu": "4.21.3", "@rollup/rollup-linux-arm64-gnu": "4.22.4",
"@rollup/rollup-linux-arm64-musl": "4.21.3", "@rollup/rollup-linux-arm64-musl": "4.22.4",
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
"@rollup/rollup-linux-riscv64-gnu": "4.21.3", "@rollup/rollup-linux-riscv64-gnu": "4.22.4",
"@rollup/rollup-linux-s390x-gnu": "4.21.3", "@rollup/rollup-linux-s390x-gnu": "4.22.4",
"@rollup/rollup-linux-x64-gnu": "4.21.3", "@rollup/rollup-linux-x64-gnu": "4.22.4",
"@rollup/rollup-linux-x64-musl": "4.21.3", "@rollup/rollup-linux-x64-musl": "4.22.4",
"@rollup/rollup-win32-arm64-msvc": "4.21.3", "@rollup/rollup-win32-arm64-msvc": "4.22.4",
"@rollup/rollup-win32-ia32-msvc": "4.21.3", "@rollup/rollup-win32-ia32-msvc": "4.22.4",
"@rollup/rollup-win32-x64-msvc": "4.21.3", "@rollup/rollup-win32-x64-msvc": "4.22.4",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true,
"license": "MIT"
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6403,9 +6387,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.6", "version": "5.4.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
"integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6463,9 +6447,9 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.0.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz",
"integrity": "sha512-+ybYqBVUjYyIscoLzMWodus2enQDZOpGhcU6HdOVD6n8WZdk12w1GFL3mbnxLs7hPtRtqs1Wo5YF6/Tsr6fmhg==", "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6500,19 +6484,19 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.0.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz",
"integrity": "sha512-XuuEeyNkqbfr0FtAvd9vFbInSSNY1ykCQTYQ0sj9wPy4hx+1gR7gqVNdW0AX2wrrM1wWlN5fnJDjF9xG6mYRSQ==", "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "2.1.0", "@vitest/expect": "2.1.1",
"@vitest/mocker": "2.1.0", "@vitest/mocker": "2.1.1",
"@vitest/pretty-format": "^2.1.0", "@vitest/pretty-format": "^2.1.1",
"@vitest/runner": "2.1.0", "@vitest/runner": "2.1.1",
"@vitest/snapshot": "2.1.0", "@vitest/snapshot": "2.1.1",
"@vitest/spy": "2.1.0", "@vitest/spy": "2.1.1",
"@vitest/utils": "2.1.0", "@vitest/utils": "2.1.1",
"chai": "^5.1.1", "chai": "^5.1.1",
"debug": "^4.3.6", "debug": "^4.3.6",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
@@ -6523,7 +6507,7 @@
"tinypool": "^1.0.0", "tinypool": "^1.0.0",
"tinyrainbow": "^1.2.0", "tinyrainbow": "^1.2.0",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-node": "2.1.0", "vite-node": "2.1.1",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@@ -6538,8 +6522,8 @@
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0", "@types/node": "^18.0.0 || >=20.0.0",
"@vitest/browser": "2.1.0", "@vitest/browser": "2.1.1",
"@vitest/ui": "2.1.0", "@vitest/ui": "2.1.1",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
+1 -64
View File
@@ -1,8 +1,7 @@
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk'; import { LoginResponseDto } from '@immich/sdk';
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path'; import { basename, join } from 'node:path';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
import { app, testAssetDir, utils } from 'src/utils'; import { app, testAssetDir, utils } from 'src/utils';
import request from 'supertest'; import request from 'supertest';
@@ -11,18 +10,13 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
describe('/map', () => { describe('/map', () => {
let websocket: Socket; let websocket: Socket;
let admin: LoginResponseDto; let admin: LoginResponseDto;
let nonAdmin: LoginResponseDto;
let asset: AssetMediaResponseDto;
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false }); admin = await utils.adminSetup({ onboarding: false });
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
websocket = await utils.connectWebsocket(admin.accessToken); websocket = await utils.connectWebsocket(admin.accessToken);
asset = await utils.createAsset(admin.accessToken);
const files = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg']; const files = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
utils.resetEvents(); utils.resetEvents();
const uploadFile = async (input: string) => { const uploadFile = async (input: string) => {
@@ -103,63 +97,6 @@ describe('/map', () => {
}); });
}); });
describe('GET /map/style.json', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/map/style.json');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should allow shared link access', async () => {
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset.id],
});
const { status, body } = await request(app).get(`/map/style.json?key=${sharedLink.key}`).query({ theme: 'dark' });
expect(status).toBe(200);
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
});
it('should throw an error if a theme is not light or dark', async () => {
for (const theme of ['dark1', true, 123, '', null, undefined]) {
const { status, body } = await request(app)
.get('/map/style.json')
.query({ theme })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['theme must be one of the following values: light, dark']));
}
});
it('should return the light style.json', async () => {
const { status, body } = await request(app)
.get('/map/style.json')
.query({ theme: 'light' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-light' }));
});
it('should return the dark style.json', async () => {
const { status, body } = await request(app)
.get('/map/style.json')
.query({ theme: 'dark' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
});
it('should not require admin authentication', async () => {
const { status, body } = await request(app)
.get('/map/style.json')
.query({ theme: 'dark' })
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
});
});
describe('GET /map/reverse-geocode', () => { describe('GET /map/reverse-geocode', () => {
it('should require authentication', async () => { it('should require authentication', async () => {
const { status, body } = await request(app).get('/map/reverse-geocode'); const { status, body } = await request(app).get('/map/reverse-geocode');
@@ -128,6 +128,8 @@ describe('/server-info', () => {
isInitialized: true, isInitialized: true,
externalDomain: '', externalDomain: '',
isOnboarded: false, isOnboarded: false,
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
}); });
}); });
}); });
+2
View File
@@ -134,6 +134,8 @@ describe('/server', () => {
isInitialized: true, isInitialized: true,
externalDomain: '', externalDomain: '',
isOnboarded: false, isOnboarded: false,
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
}); });
}); });
}); });
+32 -1
View File
@@ -11,7 +11,7 @@ from typing import Any, AsyncGenerator, Callable, Iterator
from zipfile import BadZipFile from zipfile import BadZipFile
import orjson import orjson
from fastapi import Depends, FastAPI, File, Form, HTTPException from fastapi import Depends, FastAPI, File, Form, HTTPException, Response
from fastapi.responses import ORJSONResponse from fastapi.responses import ORJSONResponse
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
from PIL.Image import Image from PIL.Image import Image
@@ -124,6 +124,23 @@ def get_entries(entries: str = Form()) -> InferenceEntries:
raise HTTPException(422, "Invalid request format.") raise HTTPException(422, "Invalid request format.")
def get_entry(entries: str = Form()) -> InferenceEntry:
try:
request: PipelineRequest = orjson.loads(entries)
for task, types in request.items():
for type, entry in types.items():
parsed: InferenceEntry = {
"name": entry["modelName"],
"task": task,
"type": type,
"options": entry.get("options", {}),
}
return parsed
except (orjson.JSONDecodeError, ValidationError, KeyError, AttributeError) as e:
log.error(f"Invalid request format: {e}")
raise HTTPException(422, "Invalid request format.")
app = FastAPI(lifespan=lifespan) app = FastAPI(lifespan=lifespan)
@@ -137,6 +154,20 @@ def ping() -> str:
return "pong" return "pong"
@app.post("/load", response_model=TextResponse)
async def load_model(entry: InferenceEntry = Depends(get_entry)) -> None:
model = await model_cache.get(entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl)
model = await load(model)
return Response(status_code=200)
@app.post("/unload", response_model=TextResponse)
async def unload_model(entry: InferenceEntry = Depends(get_entry)) -> None:
await model_cache.unload(entry["name"], entry["type"], entry["task"])
print("unload")
return Response(status_code=200)
@app.post("/predict", dependencies=[Depends(update_state)]) @app.post("/predict", dependencies=[Depends(update_state)])
async def predict( async def predict(
entries: InferenceEntries = Depends(get_entries), entries: InferenceEntries = Depends(get_entries),
+7
View File
@@ -58,3 +58,10 @@ class ModelCache:
async def revalidate(self, key: str, ttl: int | None) -> None: async def revalidate(self, key: str, ttl: int | None) -> None:
if ttl is not None and key in self.cache._handlers: if ttl is not None and key in self.cache._handlers:
await self.cache.expire(key, ttl) await self.cache.expire(key, ttl)
async def unload(self, model_name: str, model_type: ModelType, model_task: ModelTask) -> None:
key = f"{model_name}{model_type}{model_task}"
async with OptimisticLock(self.cache, key):
value = await self.cache.get(key)
if value is not None:
await self.cache.delete(key)
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"dart.flutterSdkPath": ".fvm/versions/3.24.0", "dart.flutterSdkPath": ".fvm/versions/3.24.3",
"search.exclude": { "search.exclude": {
"**/.fvm": true "**/.fvm": true
}, },
+8 -11
View File
@@ -64,45 +64,42 @@ custom_lint:
allowed: allowed:
# required / wanted # required / wanted
- lib/entities/*.entity.dart - lib/entities/*.entity.dart
- lib/repositories/{album,asset,backup,user}.repository.dart - lib/repositories/{album,asset,backup,exif_info,user}.repository.dart
# acceptable exceptions for the time being # acceptable exceptions for the time being
- integration_test/test_utils/general_helper.dart - integration_test/test_utils/general_helper.dart
- lib/main.dart - lib/main.dart
- lib/routing/router.dart - lib/routing/router.dart
- lib/utils/{db,image_url_builder,migration,renderlist_generator}.dart - lib/utils/{db,migration,renderlist_generator}.dart
- test/**.dart - test/**.dart
# refactor to make the providers and services testable # refactor to make the providers and services testable
- lib/pages/common/{album_asset_selection,gallery_viewer}.page.dart - lib/pages/common/album_asset_selection.page.dart
- lib/providers/{archive,asset,authentication,db,favorite,partner,trash,user}.provider.dart - lib/providers/{archive,asset,authentication,db,favorite,partner,trash,user}.provider.dart
- lib/providers/{album/album,album/shared_album,asset_viewer/asset_stack,asset_viewer/render_list,backup/backup,backup/manual_upload,search/all_motion_photos,search/recently_added_asset}.provider.dart - lib/providers/{album/album,album/shared_album,asset_viewer/asset_stack,asset_viewer/render_list,backup/backup,backup/manual_upload,search/all_motion_photos,search/recently_added_asset}.provider.dart
- lib/services/{asset,asset_description,background,backup,backup_verification,hash,immich_logger,memory,partner,person,search,stack,sync,user}.service.dart - lib/services/{asset,background,backup,immich_logger,sync}.service.dart
- lib/widgets/asset_grid/{asset_grid_data_structure,thumbnail_image}.dart - lib/widgets/asset_grid/asset_grid_data_structure.dart
- import_rule_openapi: - import_rule_openapi:
message: openapi must only be used through ApiRepositories message: openapi must only be used through ApiRepositories
restrict: package:openapi restrict: package:openapi
allowed: allowed:
# requried / wanted # requried / wanted
- lib/repositories/album_api.repository.dart - lib/repositories/*_api.repository.dart
# acceptable exceptions for the time being # acceptable exceptions for the time being
- lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities - lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities
- lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine - lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine
- test/modules/utils/openapi_patching_test.dart # filename is self-explanatory... - test/modules/utils/openapi_patching_test.dart # filename is self-explanatory...
# refactor # refactor
- lib/models/activities/activity.model.dart
- lib/models/map/map_marker.model.dart - lib/models/map/map_marker.model.dart
- lib/models/search/search_filter.model.dart
- lib/models/server_info/server_{config,disk_info,features,version}.model.dart - lib/models/server_info/server_{config,disk_info,features,version}.model.dart
- lib/models/shared_link/shared_link.model.dart - lib/models/shared_link/shared_link.model.dart
- lib/pages/search/search_input.page.dart
- lib/providers/asset_viewer/asset_people.provider.dart - lib/providers/asset_viewer/asset_people.provider.dart
- lib/providers/authentication.provider.dart - lib/providers/authentication.provider.dart
- lib/providers/image/immich_remote_{image,thumbnail}_provider.dart - lib/providers/image/immich_remote_{image,thumbnail}_provider.dart
- lib/providers/map/map_state.provider.dart - lib/providers/map/map_state.provider.dart
- lib/providers/search/{people,search,search_filter}.provider.dart - lib/providers/search/{search,search_filter}.provider.dart
- lib/providers/websocket.provider.dart - lib/providers/websocket.provider.dart
- lib/routing/auth_guard.dart - lib/routing/auth_guard.dart
- lib/services/{activity,api,asset,asset_description,backup,memory,oauth,partner,person,search,shared_link,stack,trash,user}.service.dart - lib/services/{api,asset,backup,memory,oauth,search,shared_link,stack,trash}.service.dart
- lib/widgets/album/album_thumbnail_listtile.dart - lib/widgets/album/album_thumbnail_listtile.dart
- lib/widgets/forms/login/login_form.dart - lib/widgets/forms/login/login_form.dart
- lib/widgets/search/search_filter/{camera_picker,location_picker,people_picker}.dart - lib/widgets/search/search_filter/{camera_picker,location_picker,people_picker}.dart
+1
View File
@@ -0,0 +1 @@
const int noDbId = -9223372036854775808; // from Isar
@@ -0,0 +1,16 @@
import 'package:immich_mobile/models/activities/activity.model.dart';
abstract interface class IActivityApiRepository {
Future<List<Activity>> getAll(
String albumId, {
String? assetId,
});
Future<Activity> create(
String albumId,
ActivityType type, {
String? assetId,
String? comment,
});
Future<void> delete(String id);
Future<ActivityStats> getStats(String albumId, {String? assetId});
}
@@ -1,5 +1,6 @@
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/device_asset.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IAssetRepository { abstract interface class IAssetRepository {
@@ -7,4 +8,20 @@ abstract interface class IAssetRepository {
Future<List<Asset>> getAllByRemoteId(Iterable<String> ids); Future<List<Asset>> getAllByRemoteId(Iterable<String> ids);
Future<List<Asset>> getByAlbum(Album album, {User? notOwnedBy}); Future<List<Asset>> getByAlbum(Album album, {User? notOwnedBy});
Future<void> deleteById(List<int> ids); Future<void> deleteById(List<int> ids);
Future<List<Asset>> getAll({
required int ownerId,
bool? remote,
int limit = 100,
});
Future<List<Asset>> updateAll(List<Asset> assets);
Future<List<Asset>> getMatches({
required List<Asset> assets,
required int ownerId,
bool? remote,
int limit = 100,
});
Future<List<DeviceAsset?>> getDeviceAssetsById(List<Object> ids);
Future<void> upsertDeviceAssets(List<DeviceAsset> deviceAssets);
} }
@@ -0,0 +1,18 @@
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IAssetApiRepository {
// Future<Asset> get(String id);
// Future<List<Asset>> getAll();
// Future<Asset> create(Asset asset);
Future<Asset> update(
String id, {
String? description,
});
// Future<void> delete(String id);
Future<List<Asset>> search({List<String> personIds = const []});
}
@@ -0,0 +1,9 @@
import 'package:immich_mobile/entities/exif_info.entity.dart';
abstract interface class IExifInfoRepository {
Future<ExifInfo?> get(int id);
Future<ExifInfo> update(ExifInfo exifInfo);
Future<void> delete(int id);
}
@@ -0,0 +1,13 @@
import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IPartnerApiRepository {
Future<List<User>> getAll(Direction direction);
Future<User> create(String id);
Future<User> update(String id, {required bool inTimeline});
Future<void> delete(String id);
}
enum Direction {
sharedWithMe,
sharedByMe,
}
@@ -0,0 +1,22 @@
abstract interface class IPersonApiRepository {
Future<List<Person>> getAll();
Future<Person> update(String id, {String? name});
}
class Person {
Person({
required this.id,
required this.isHidden,
required this.name,
required this.thumbnailPath,
this.birthDate,
this.updatedAt,
});
final String id;
final DateTime? birthDate;
final bool isHidden;
final String name;
final String thumbnailPath;
final DateTime? updatedAt;
}
@@ -3,4 +3,6 @@ import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IUserRepository { abstract interface class IUserRepository {
Future<List<User>> getByIds(List<String> ids); Future<List<User>> getByIds(List<String> ids);
Future<User?> get(String id); Future<User?> get(String id);
Future<List<User>> getAll({bool self = true});
Future<User> update(User user);
} }
@@ -0,0 +1,11 @@
import 'dart:typed_data';
import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IUserApiRepository {
Future<List<User>> getAll();
Future<({String profileImagePath})> createProfileImage({
required String name,
required Uint8List data,
});
}
@@ -1,5 +1,4 @@
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:openapi/api.dart';
enum ActivityType { comment, like } enum ActivityType { comment, like }
@@ -38,16 +37,6 @@ class Activity {
); );
} }
Activity.fromDto(ActivityResponseDto dto)
: id = dto.id,
assetId = dto.assetId,
comment = dto.comment,
createdAt = dto.createdAt,
type = dto.type == ReactionType.comment
? ActivityType.comment
: ActivityType.like,
user = User.fromSimpleUserDto(dto.user);
@override @override
String toString() { String toString() {
return 'Activity(id: $id, assetId: $assetId, comment: $comment, createdAt: $createdAt, type: $type, user: $user)'; return 'Activity(id: $id, assetId: $assetId, comment: $comment, createdAt: $createdAt, type: $type, user: $user)';
@@ -75,3 +64,9 @@ class Activity {
user.hashCode; user.hashCode;
} }
} }
class ActivityStats {
final int comments;
const ActivityStats({required this.comments});
}
@@ -2,7 +2,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:openapi/api.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart';
class SearchLocationFilter { class SearchLocationFilter {
String? country; String? country;
@@ -235,7 +235,7 @@ class SearchDisplayFilters {
class SearchFilter { class SearchFilter {
String? context; String? context;
String? filename; String? filename;
Set<PersonResponseDto> people; Set<Person> people;
SearchLocationFilter location; SearchLocationFilter location;
SearchCameraFilter camera; SearchCameraFilter camera;
SearchDateFilter date; SearchDateFilter date;
@@ -258,7 +258,7 @@ class SearchFilter {
SearchFilter copyWith({ SearchFilter copyWith({
String? context, String? context,
String? filename, String? filename,
Set<PersonResponseDto>? people, Set<Person>? people,
SearchLocationFilter? location, SearchLocationFilter? location,
SearchCameraFilter? camera, SearchCameraFilter? camera,
SearchDateFilter? date, SearchDateFilter? date,
@@ -4,11 +4,15 @@ class ServerConfig {
final int trashDays; final int trashDays;
final String oauthButtonText; final String oauthButtonText;
final String externalDomain; final String externalDomain;
final String mapDarkStyleUrl;
final String mapLightStyleUrl;
const ServerConfig({ const ServerConfig({
required this.trashDays, required this.trashDays,
required this.oauthButtonText, required this.oauthButtonText,
required this.externalDomain, required this.externalDomain,
required this.mapDarkStyleUrl,
required this.mapLightStyleUrl,
}); });
ServerConfig copyWith({ ServerConfig copyWith({
@@ -20,6 +24,8 @@ class ServerConfig {
trashDays: trashDays ?? this.trashDays, trashDays: trashDays ?? this.trashDays,
oauthButtonText: oauthButtonText ?? this.oauthButtonText, oauthButtonText: oauthButtonText ?? this.oauthButtonText,
externalDomain: externalDomain ?? this.externalDomain, externalDomain: externalDomain ?? this.externalDomain,
mapDarkStyleUrl: mapDarkStyleUrl,
mapLightStyleUrl: mapLightStyleUrl,
); );
} }
@@ -30,7 +36,9 @@ class ServerConfig {
ServerConfig.fromDto(ServerConfigDto dto) ServerConfig.fromDto(ServerConfigDto dto)
: trashDays = dto.trashDays, : trashDays = dto.trashDays,
oauthButtonText = dto.oauthButtonText, oauthButtonText = dto.oauthButtonText,
externalDomain = dto.externalDomain; externalDomain = dto.externalDomain,
mapDarkStyleUrl = dto.mapDarkStyleUrl,
mapLightStyleUrl = dto.mapLightStyleUrl;
@override @override
bool operator ==(covariant ServerConfig other) { bool operator ==(covariant ServerConfig other) {
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/pages/common/video_viewer.page.dart'; import 'package:immich_mobile/pages/common/video_viewer.page.dart';
@@ -30,7 +31,6 @@ import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart'; import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart';
import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart'; import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart';
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart'; import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart';
import 'package:isar/isar.dart';
@RoutePage() @RoutePage()
// ignore: must_be_immutable // ignore: must_be_immutable
@@ -73,7 +73,7 @@ class GalleryViewerPage extends HookConsumerWidget {
: <Asset>[]; : <Asset>[];
final stackElements = showStack ? [currentAsset, ...stack] : <Asset>[]; final stackElements = showStack ? [currentAsset, ...stack] : <Asset>[];
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = currentAsset.id == Isar.autoIncrement; final isFromDto = currentAsset.id == noDbId;
Asset asset = stackIndex.value == -1 Asset asset = stackIndex.value == -1
? currentAsset ? currentAsset
+8 -7
View File
@@ -1,4 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@@ -7,27 +8,27 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/latlngbounds_extension.dart'; import 'package:immich_mobile/extensions/latlngbounds_extension.dart';
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
import 'package:immich_mobile/models/map/map_event.model.dart'; import 'package:immich_mobile/models/map/map_event.model.dart';
import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/map/map_marker.provider.dart'; import 'package:immich_mobile/providers/map/map_marker.provider.dart';
import 'package:immich_mobile/providers/map/map_state.provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/debounce.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
import 'package:immich_mobile/utils/map_utils.dart'; import 'package:immich_mobile/utils/map_utils.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/map/map_app_bar.dart'; import 'package:immich_mobile/widgets/map/map_app_bar.dart';
import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; import 'package:immich_mobile/widgets/map/map_asset_grid.dart';
import 'package:immich_mobile/widgets/map/map_bottom_sheet.dart'; import 'package:immich_mobile/widgets/map/map_bottom_sheet.dart';
import 'package:immich_mobile/widgets/map/map_theme_override.dart'; import 'package:immich_mobile/widgets/map/map_theme_override.dart';
import 'package:immich_mobile/widgets/map/positioned_asset_marker_icon.dart'; import 'package:immich_mobile/widgets/map/positioned_asset_marker_icon.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
import 'package:immich_mobile/utils/debounce.dart';
import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:maplibre_gl/maplibre_gl.dart';
@RoutePage() @RoutePage()
@@ -304,7 +305,7 @@ class MapPage extends HookConsumerWidget {
), ),
Positioned( Positioned(
right: 0, right: 0,
bottom: MediaQuery.of(context).padding.bottom + 16, bottom: MediaQuery.paddingOf(context).bottom + 16,
child: ElevatedButton( child: ElevatedButton(
onPressed: onZoomToLocation, onPressed: onZoomToLocation,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
@@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
@@ -19,7 +20,6 @@ import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dar
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart'; import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart'; import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart'; import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
import 'package:openapi/api.dart';
@RoutePage() @RoutePage()
class SearchInputPage extends HookConsumerWidget { class SearchInputPage extends HookConsumerWidget {
@@ -110,7 +110,7 @@ class SearchInputPage extends HookConsumerWidget {
} }
showPeoplePicker() { showPeoplePicker() {
handleOnSelect(Set<PersonResponseDto> value) { handleOnSelect(Set<Person> value) {
filter.value = filter.value.copyWith( filter.value = filter.value.copyWith(
people: value, people: value,
); );
@@ -1,9 +1,9 @@
import 'package:immich_mobile/repositories/activity_api.repository.dart';
import 'package:immich_mobile/services/activity.service.dart'; import 'package:immich_mobile/services/activity.service.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'activity_service.provider.g.dart'; part 'activity_service.provider.g.dart';
@riverpod @riverpod
ActivityService activityService(ActivityServiceRef ref) => ActivityService activityService(ActivityServiceRef ref) =>
ActivityService(ref.watch(apiServiceProvider)); ActivityService(ref.watch(activityApiRepositoryProvider));
+1 -1
View File
@@ -6,7 +6,7 @@ part of 'activity_service.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$activityServiceHash() => r'5dd4955d14f5bf01c00d7f8750d07e7ace7cc4b0'; String _$activityServiceHash() => r'23a3ee7db71676d2719daa64217a683cc5c7eab0';
/// See also [activityService]. /// See also [activityService].
@ProviderFor(activityService) @ProviderFor(activityService)
@@ -11,7 +11,7 @@ class ActivityStatistics extends _$ActivityStatistics {
ref ref
.watch(activityServiceProvider) .watch(activityServiceProvider)
.getStatistics(albumId, assetId: assetId) .getStatistics(albumId, assetId: assetId)
.then((comments) => state = comments); .then((stats) => state = stats.comments);
return 0; return 0;
} }
+1 -1
View File
@@ -7,7 +7,7 @@ part of 'activity_statistics.provider.dart';
// ************************************************************************** // **************************************************************************
String _$activityStatisticsHash() => String _$activityStatisticsHash() =>
r'a5f7bbee1891c33b72919a34e632ca9ef9cd8dbf'; r'1f43f0bcb11c754ca3cb586a13570db25023b9a8';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@@ -5,5 +5,5 @@ import 'package:immich_mobile/services/user.service.dart';
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) { final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) {
UserService userService = ref.watch(userServiceProvider); UserService userService = ref.watch(userServiceProvider);
return userService.getUsersInDb(); return userService.getUsers();
}); });
@@ -1,28 +1,23 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/response_extensions.dart';
import 'package:immich_mobile/models/map/map_state.model.dart'; import 'package:immich_mobile/models/map/map_state.model.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'map_state.provider.g.dart'; part 'map_state.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
class MapStateNotifier extends _$MapStateNotifier { class MapStateNotifier extends _$MapStateNotifier {
final _log = Logger("MapStateNotifier");
@override @override
MapState build() { MapState build() {
final appSettingsProvider = ref.read(appSettingsServiceProvider); final appSettingsProvider = ref.read(appSettingsServiceProvider);
// Fetch and save the Style JSONs final lightStyleUrl =
loadStyles(); ref.read(serverInfoProvider).serverConfig.mapLightStyleUrl;
final darkStyleUrl =
ref.read(serverInfoProvider).serverConfig.mapDarkStyleUrl;
return MapState( return MapState(
themeMode: ThemeMode.values[ themeMode: ThemeMode.values[
appSettingsProvider.getSetting<int>(AppSettingsEnum.mapThemeMode)], appSettingsProvider.getSetting<int>(AppSettingsEnum.mapThemeMode)],
@@ -34,65 +29,11 @@ class MapStateNotifier extends _$MapStateNotifier {
appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapwithPartners), appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapwithPartners),
relativeTime: relativeTime:
appSettingsProvider.getSetting<int>(AppSettingsEnum.mapRelativeDate), appSettingsProvider.getSetting<int>(AppSettingsEnum.mapRelativeDate),
lightStyleFetched: AsyncData(lightStyleUrl),
darkStyleFetched: AsyncData(darkStyleUrl),
); );
} }
void loadStyles() async {
final documents = (await getApplicationDocumentsDirectory()).path;
// Set to loading
state = state.copyWith(lightStyleFetched: const AsyncLoading());
// Fetch and save light theme
final lightResponse = await ref
.read(apiServiceProvider)
.mapApi
.getMapStyleWithHttpInfo(MapTheme.light);
if (lightResponse.statusCode >= HttpStatus.badRequest) {
state = state.copyWith(
lightStyleFetched: AsyncError(lightResponse.body, StackTrace.current),
);
_log.severe(
"Cannot fetch map light style",
lightResponse.toLoggerString(),
);
return;
}
final lightJSON = lightResponse.body;
final lightFile = await File("$documents/map-style-light.json")
.writeAsString(lightJSON, flush: true);
// Update state with path
state =
state.copyWith(lightStyleFetched: AsyncData(lightFile.absolute.path));
// Set to loading
state = state.copyWith(darkStyleFetched: const AsyncLoading());
// Fetch and save dark theme
final darkResponse = await ref
.read(apiServiceProvider)
.mapApi
.getMapStyleWithHttpInfo(MapTheme.dark);
if (darkResponse.statusCode >= HttpStatus.badRequest) {
state = state.copyWith(
darkStyleFetched: AsyncError(darkResponse.body, StackTrace.current),
);
_log.severe("Cannot fetch map dark style", darkResponse.toLoggerString());
return;
}
final darkJSON = darkResponse.body;
final darkFile = await File("$documents/map-style-dark.json")
.writeAsString(darkJSON, flush: true);
// Update state with path
state = state.copyWith(darkStyleFetched: AsyncData(darkFile.absolute.path));
}
void switchTheme(ThemeMode mode) { void switchTheme(ThemeMode mode) {
ref.read(appSettingsServiceProvider).setSetting( ref.read(appSettingsServiceProvider).setSetting(
AppSettingsEnum.mapThemeMode, AppSettingsEnum.mapThemeMode,
+1 -1
View File
@@ -6,7 +6,7 @@ part of 'map_state.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$mapStateNotifierHash() => r'31fafe17aa85c48379a22ed3db3cc94af59ce5b8'; String _$mapStateNotifierHash() => r'22e4e571bd0730dbc34b109255a62b920e9c7d66';
/// See also [MapStateNotifier]. /// See also [MapStateNotifier].
@ProviderFor(MapStateNotifier) @ProviderFor(MapStateNotifier)
@@ -1,14 +1,14 @@
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/services/person.service.dart'; import 'package:immich_mobile/services/person.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'people.provider.g.dart'; part 'people.provider.g.dart';
@riverpod @riverpod
Future<List<PersonResponseDto>> getAllPeople( Future<List<Person>> getAllPeople(
GetAllPeopleRef ref, GetAllPeopleRef ref,
) async { ) async {
final PersonService personService = ref.read(personServiceProvider); final PersonService personService = ref.read(personServiceProvider);
+3 -4
View File
@@ -6,12 +6,11 @@ part of 'people.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$getAllPeopleHash() => r'4eff6666be5a74710d1e8587e01d8154310d85bd'; String _$getAllPeopleHash() => r'3417b7e0c211382d4480a415e352139995d57b6d';
/// See also [getAllPeople]. /// See also [getAllPeople].
@ProviderFor(getAllPeople) @ProviderFor(getAllPeople)
final getAllPeopleProvider = final getAllPeopleProvider = AutoDisposeFutureProvider<List<Person>>.internal(
AutoDisposeFutureProvider<List<PersonResponseDto>>.internal(
getAllPeople, getAllPeople,
name: r'getAllPeopleProvider', name: r'getAllPeopleProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
@@ -20,7 +19,7 @@ final getAllPeopleProvider =
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<PersonResponseDto>>; typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<Person>>;
String _$personAssetsHash() => r'3dfecb67a54d07e4208bcb9581b2625acd2e1832'; String _$personAssetsHash() => r'3dfecb67a54d07e4208bcb9581b2625acd2e1832';
/// Copied from Dart SDK /// Copied from Dart SDK
@@ -34,6 +34,9 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
trashDays: 30, trashDays: 30,
oauthButtonText: '', oauthButtonText: '',
externalDomain: '', externalDomain: '',
mapLightStyleUrl:
'https://tiles.immich.cloud/v1/style/light.json',
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
), ),
serverDiskInfo: const ServerDiskInfo( serverDiskInfo: const ServerDiskInfo(
diskAvailable: "0", diskAvailable: "0",
@@ -0,0 +1,67 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final activityApiRepositoryProvider = Provider(
(ref) => ActivityApiRepository(ref.watch(apiServiceProvider).activitiesApi),
);
class ActivityApiRepository extends BaseApiRepository
implements IActivityApiRepository {
final ActivitiesApi _api;
ActivityApiRepository(this._api);
@override
Future<List<Activity>> getAll(String albumId, {String? assetId}) async {
final response =
await checkNull(_api.getActivities(albumId, assetId: assetId));
return response.map(_toActivity).toList();
}
@override
Future<Activity> create(
String albumId,
ActivityType type, {
String? assetId,
String? comment,
}) async {
final dto = ActivityCreateDto(
albumId: albumId,
type: type == ActivityType.comment
? ReactionType.comment
: ReactionType.like,
assetId: assetId,
comment: comment,
);
final response = await checkNull(_api.createActivity(dto));
return _toActivity(response);
}
@override
Future<void> delete(String id) {
return checkNull(_api.deleteActivity(id));
}
@override
Future<ActivityStats> getStats(String albumId, {String? assetId}) async {
final response =
await checkNull(_api.getActivityStatistics(albumId, assetId: assetId));
return ActivityStats(comments: response.comments);
}
static Activity _toActivity(ActivityResponseDto dto) => Activity(
id: dto.id,
createdAt: dto.createdAt,
type: dto.type == ReactionType.comment
? ActivityType.comment
: ActivityType.like,
user: User.fromSimpleUserDto(dto.user),
assetId: dto.assetId,
comment: dto.comment,
);
}
@@ -1,30 +1,31 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/errors.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final albumApiRepositoryProvider = Provider( final albumApiRepositoryProvider = Provider(
(ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi), (ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
); );
class AlbumApiRepository implements IAlbumApiRepository { class AlbumApiRepository extends BaseApiRepository
implements IAlbumApiRepository {
final AlbumsApi _api; final AlbumsApi _api;
AlbumApiRepository(this._api); AlbumApiRepository(this._api);
@override @override
Future<Album> get(String id) async { Future<Album> get(String id) async {
final dto = await _checkNull(_api.getAlbumInfo(id)); final dto = await checkNull(_api.getAlbumInfo(id));
return _toAlbum(dto); return _toAlbum(dto);
} }
@override @override
Future<List<Album>> getAll({bool? shared}) async { Future<List<Album>> getAll({bool? shared}) async {
final dtos = await _checkNull(_api.getAllAlbums(shared: shared)); final dtos = await checkNull(_api.getAllAlbums(shared: shared));
return dtos.map(_toAlbum).toList().cast(); return dtos.map(_toAlbum).toList().cast();
} }
@@ -37,7 +38,7 @@ class AlbumApiRepository implements IAlbumApiRepository {
final users = sharedUserIds.map( final users = sharedUserIds.map(
(id) => AlbumUserCreateDto(userId: id, role: AlbumUserRole.editor), (id) => AlbumUserCreateDto(userId: id, role: AlbumUserRole.editor),
); );
final responseDto = await _checkNull( final responseDto = await checkNull(
_api.createAlbum( _api.createAlbum(
CreateAlbumDto( CreateAlbumDto(
albumName: name, albumName: name,
@@ -57,7 +58,7 @@ class AlbumApiRepository implements IAlbumApiRepository {
String? description, String? description,
bool? activityEnabled, bool? activityEnabled,
}) async { }) async {
final response = await _checkNull( final response = await checkNull(
_api.updateAlbumInfo( _api.updateAlbumInfo(
albumId, albumId,
UpdateAlbumDto( UpdateAlbumDto(
@@ -81,7 +82,7 @@ class AlbumApiRepository implements IAlbumApiRepository {
String albumId, String albumId,
Iterable<String> assetIds, Iterable<String> assetIds,
) async { ) async {
final response = await _checkNull( final response = await checkNull(
_api.addAssetsToAlbum( _api.addAssetsToAlbum(
albumId, albumId,
BulkIdsDto(ids: assetIds.toList()), BulkIdsDto(ids: assetIds.toList()),
@@ -106,7 +107,7 @@ class AlbumApiRepository implements IAlbumApiRepository {
String albumId, String albumId,
Iterable<String> assetIds, Iterable<String> assetIds,
) async { ) async {
final response = await _checkNull( final response = await checkNull(
_api.removeAssetFromAlbum( _api.removeAssetFromAlbum(
albumId, albumId,
BulkIdsDto(ids: assetIds.toList()), BulkIdsDto(ids: assetIds.toList()),
@@ -127,7 +128,7 @@ class AlbumApiRepository implements IAlbumApiRepository {
Future<Album> addUsers(String albumId, Iterable<String> userIds) async { Future<Album> addUsers(String albumId, Iterable<String> userIds) async {
final albumUsers = final albumUsers =
userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList(); userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList();
final response = await _checkNull( final response = await checkNull(
_api.addUsersToAlbum( _api.addUsersToAlbum(
albumId, albumId,
AddUsersDto(albumUsers: albumUsers), AddUsersDto(albumUsers: albumUsers),
@@ -141,12 +142,6 @@ class AlbumApiRepository implements IAlbumApiRepository {
return _api.removeUserFromAlbum(albumId, userId); return _api.removeUserFromAlbum(albumId, userId);
} }
static Future<T> _checkNull<T>(Future<T?> future) async {
final response = await future;
if (response == null) throw NoResponseDtoError();
return response;
}
static Album _toAlbum(AlbumResponseDto dto) { static Album _toAlbum(AlbumResponseDto dto) {
final Album album = Album( final Album album = Album(
remoteId: dto.id, remoteId: dto.id,
@@ -1,6 +1,11 @@
import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/device_asset.entity.dart';
import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
@@ -35,4 +40,104 @@ class AssetRepository implements IAssetRepository {
@override @override
Future<List<Asset>> getAllByRemoteId(Iterable<String> ids) => Future<List<Asset>> getAllByRemoteId(Iterable<String> ids) =>
_db.assets.getAllByRemoteId(ids); _db.assets.getAllByRemoteId(ids);
@override
Future<List<Asset>> getAll({
required int ownerId,
bool? remote,
int limit = 100,
}) {
if (remote == null) {
return _db.assets
.where()
.ownerIdEqualToAnyChecksum(ownerId)
.limit(limit)
.findAll();
}
final QueryBuilder<Asset, Asset, QAfterFilterCondition> query;
if (remote) {
query = _db.assets
.where()
.localIdIsNull()
.filter()
.remoteIdIsNotNull()
.ownerIdEqualTo(ownerId);
} else {
query = _db.assets
.where()
.remoteIdIsNull()
.filter()
.localIdIsNotNull()
.ownerIdEqualTo(ownerId);
}
return query.limit(limit).findAll();
}
@override
Future<List<Asset>> updateAll(List<Asset> assets) async {
await _db.writeTxn(() => _db.assets.putAll(assets));
return assets;
}
@override
Future<List<Asset>> getMatches({
required List<Asset> assets,
required int ownerId,
bool? remote,
int limit = 100,
}) {
final QueryBuilder<Asset, Asset, QAfterFilterCondition> query;
if (remote == null) {
query = _db.assets.filter().remoteIdIsNotNull().or().localIdIsNotNull();
} else if (remote) {
query = _db.assets.where().localIdIsNull().filter().remoteIdIsNotNull();
} else {
query = _db.assets.where().remoteIdIsNull().filter().localIdIsNotNull();
}
return _getMatchesImpl(query, ownerId, assets, limit);
}
@override
Future<List<DeviceAsset?>> getDeviceAssetsById(List<Object> ids) =>
Platform.isAndroid
? _db.androidDeviceAssets.getAll(ids.cast())
: _db.iOSDeviceAssets.getAllById(ids.cast());
@override
Future<void> upsertDeviceAssets(List<DeviceAsset> deviceAssets) =>
_db.writeTxn(
() => Platform.isAndroid
? _db.androidDeviceAssets.putAll(deviceAssets.cast())
: _db.iOSDeviceAssets.putAll(deviceAssets.cast()),
);
} }
Future<List<Asset>> _getMatchesImpl(
QueryBuilder<Asset, Asset, QAfterFilterCondition> query,
int ownerId,
List<Asset> assets,
int limit,
) =>
query
.ownerIdEqualTo(ownerId)
.anyOf(
assets,
(q, Asset a) => q
.fileNameEqualTo(a.fileName)
.and()
.durationInSecondsEqualTo(a.durationInSeconds)
.and()
.fileCreatedAtBetween(
a.fileCreatedAt.subtract(const Duration(hours: 12)),
a.fileCreatedAt.add(const Duration(hours: 12)),
)
.and()
.not()
.checksumEqualTo(a.checksum),
)
.sortByFileName()
.thenByFileCreatedAt()
.thenByFileModifiedAt()
.limit(limit)
.findAll();
@@ -0,0 +1,52 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final assetApiRepositoryProvider = Provider(
(ref) => AssetApiRepository(
ref.watch(apiServiceProvider).assetsApi,
ref.watch(apiServiceProvider).searchApi,
),
);
class AssetApiRepository extends BaseApiRepository
implements IAssetApiRepository {
final AssetsApi _api;
final SearchApi _searchApi;
AssetApiRepository(this._api, this._searchApi);
@override
Future<Asset> update(String id, {String? description}) async {
final response = await checkNull(
_api.updateAsset(id, UpdateAssetDto(description: description)),
);
return Asset.remote(response);
}
@override
Future<List<Asset>> search({List<String> personIds = const []}) async {
// TODO this always fetches all assets, change API and usage to actually do pagination
final List<Asset> result = [];
bool hasNext = true;
int currentPage = 1;
while (hasNext) {
final response = await checkNull(
_searchApi.searchMetadata(
MetadataSearchDto(
personIds: personIds,
page: currentPage,
size: 1000,
),
),
);
result.addAll(response.assets.items.map(Asset.remote));
hasNext = response.assets.nextPage != null;
currentPage++;
}
return result;
}
}
@@ -0,0 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/constants/errors.dart';
abstract class BaseApiRepository {
@protected
Future<T> checkNull<T>(Future<T?> future) async {
final response = await future;
if (response == null) throw NoResponseDtoError();
return response;
}
}
@@ -0,0 +1,28 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/exif_info.entity.dart';
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:isar/isar.dart';
final exifInfoRepositoryProvider =
Provider((ref) => ExifInfoRepository(ref.watch(dbProvider)));
class ExifInfoRepository implements IExifInfoRepository {
final Isar _db;
ExifInfoRepository(
this._db,
);
@override
Future<void> delete(int id) => _db.exifInfos.delete(id);
@override
Future<ExifInfo?> get(int id) => _db.exifInfos.get(id);
@override
Future<ExifInfo> update(ExifInfo exifInfo) async {
await _db.writeTxn(() => _db.exifInfos.put(exifInfo));
return exifInfo;
}
}
@@ -0,0 +1,51 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final partnerApiRepositoryProvider = Provider(
(ref) => PartnerApiRepository(
ref.watch(apiServiceProvider).partnersApi,
),
);
class PartnerApiRepository extends BaseApiRepository
implements IPartnerApiRepository {
final PartnersApi _api;
PartnerApiRepository(this._api);
@override
Future<List<User>> getAll(Direction direction) async {
final response = await checkNull(
_api.getPartners(
direction == Direction.sharedByMe
? PartnerDirection.by
: PartnerDirection.with_,
),
);
return response.map(User.fromPartnerDto).toList();
}
@override
Future<User> create(String id) async {
final dto = await checkNull(_api.createPartner(id));
return User.fromPartnerDto(dto);
}
@override
Future<void> delete(String id) => checkNull(_api.removePartner(id));
@override
Future<User> update(String id, {required bool inTimeline}) async {
final dto = await checkNull(
_api.updatePartner(
id,
UpdatePartnerDto(inTimeline: inTimeline),
),
);
return User.fromPartnerDto(dto);
}
}
@@ -0,0 +1,38 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final personApiRepositoryProvider = Provider(
(ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi),
);
class PersonApiRepository extends BaseApiRepository
implements IPersonApiRepository {
final PeopleApi _api;
PersonApiRepository(this._api);
@override
Future<List<Person>> getAll() async {
final dto = await checkNull(_api.getAllPeople());
return dto.people.map(_toPerson).toList();
}
@override
Future<Person> update(String id, {String? name}) async {
final dto = await checkNull(
_api.updatePerson(id, PersonUpdateDto(name: name)),
);
return _toPerson(dto);
}
static Person _toPerson(PersonResponseDto dto) => Person(
birthDate: dto.birthDate,
id: dto.id,
isHidden: dto.isHidden,
name: dto.name,
thumbnailPath: dto.thumbnailPath,
);
}
@@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/user.interface.dart'; import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
@@ -20,4 +21,19 @@ class UserRepository implements IUserRepository {
@override @override
Future<User?> get(String id) => _db.users.getById(id); Future<User?> get(String id) => _db.users.getById(id);
@override
Future<List<User>> getAll({bool self = true}) {
if (self) {
return _db.users.where().findAll();
}
final int userId = Store.get(StoreKey.currentUser).isarId;
return _db.users.where().isarIdNotEqualTo(userId).findAll();
}
@override
Future<User> update(User user) async {
await _db.writeTxn(() => _db.users.put(user));
return user;
}
} }
@@ -0,0 +1,41 @@
import 'dart:typed_data';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/user_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final userApiRepositoryProvider = Provider(
(ref) => UserApiRepository(
ref.watch(apiServiceProvider).usersApi,
),
);
class UserApiRepository extends BaseApiRepository
implements IUserApiRepository {
final UsersApi _api;
UserApiRepository(this._api);
@override
Future<List<User>> getAll() async {
final dto = await checkNull(_api.searchUsers());
return dto.map(User.fromSimpleUserDto).toList();
}
@override
Future<({String profileImagePath})> createProfileImage({
required String name,
required Uint8List data,
}) async {
final response = await checkNull(
_api.createProfileImage(
MultipartFile.fromBytes('file', data, filename: name),
),
);
return (profileImagePath: response.profileImagePath);
}
}
+14 -34
View File
@@ -1,41 +1,31 @@
import 'package:immich_mobile/constants/errors.dart'; import 'package:immich_mobile/interfaces/activity_api.interface.dart';
import 'package:immich_mobile/mixins/error_logger.mixin.dart'; import 'package:immich_mobile/mixins/error_logger.mixin.dart';
import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
class ActivityService with ErrorLoggerMixin { class ActivityService with ErrorLoggerMixin {
final ApiService _apiService; final IActivityApiRepository _activityApiRepository;
@override @override
final Logger logger = Logger("ActivityService"); final Logger logger = Logger("ActivityService");
ActivityService(this._apiService); ActivityService(this._activityApiRepository);
Future<List<Activity>> getAllActivities( Future<List<Activity>> getAllActivities(
String albumId, { String albumId, {
String? assetId, String? assetId,
}) async { }) async {
return logError( return logError(
() async { () => _activityApiRepository.getAll(albumId, assetId: assetId),
final list = await _apiService.activitiesApi
.getActivities(albumId, assetId: assetId);
return list != null ? list.map(Activity.fromDto).toList() : [];
},
defaultValue: [], defaultValue: [],
errorMessage: "Failed to get all activities for album $albumId", errorMessage: "Failed to get all activities for album $albumId",
); );
} }
Future<int> getStatistics(String albumId, {String? assetId}) async { Future<ActivityStats> getStatistics(String albumId, {String? assetId}) async {
return logError( return logError(
() async { () => _activityApiRepository.getStats(albumId, assetId: assetId),
final dto = await _apiService.activitiesApi defaultValue: const ActivityStats(comments: 0),
.getActivityStatistics(albumId, assetId: assetId);
return dto?.comments ?? 0;
},
defaultValue: 0,
errorMessage: "Failed to statistics for album $albumId", errorMessage: "Failed to statistics for album $albumId",
); );
} }
@@ -43,7 +33,7 @@ class ActivityService with ErrorLoggerMixin {
Future<bool> removeActivity(String id) async { Future<bool> removeActivity(String id) async {
return logError( return logError(
() async { () async {
await _apiService.activitiesApi.deleteActivity(id); await _activityApiRepository.delete(id);
return true; return true;
}, },
defaultValue: false, defaultValue: false,
@@ -58,22 +48,12 @@ class ActivityService with ErrorLoggerMixin {
String? comment, String? comment,
}) async { }) async {
return guardError( return guardError(
() async { () => _activityApiRepository.create(
final dto = await _apiService.activitiesApi.createActivity( albumId,
ActivityCreateDto( type,
albumId: albumId, assetId: assetId,
type: type == ActivityType.comment comment: comment,
? ReactionType.comment ),
: ReactionType.like,
assetId: assetId,
comment: comment,
),
);
if (dto != null) {
return Activity.fromDto(dto);
}
throw NoResponseDtoError();
},
errorMessage: "Failed to create $type for album $albumId", errorMessage: "Failed to create $type for album $albumId",
); );
} }
+52
View File
@@ -9,9 +9,13 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart';
import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/exif_info.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/exif_info.repository.dart';
import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/backup.service.dart'; import 'package:immich_mobile/services/backup.service.dart';
@@ -24,6 +28,8 @@ import 'package:openapi/api.dart';
final assetServiceProvider = Provider( final assetServiceProvider = Provider(
(ref) => AssetService( (ref) => AssetService(
ref.watch(assetApiRepositoryProvider),
ref.watch(exifInfoRepositoryProvider),
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(syncServiceProvider), ref.watch(syncServiceProvider),
ref.watch(userServiceProvider), ref.watch(userServiceProvider),
@@ -34,6 +40,8 @@ final assetServiceProvider = Provider(
); );
class AssetService { class AssetService {
final IAssetApiRepository _assetApiRepository;
final IExifInfoRepository _exifInfoRepository;
final ApiService _apiService; final ApiService _apiService;
final SyncService _syncService; final SyncService _syncService;
final UserService _userService; final UserService _userService;
@@ -43,6 +51,8 @@ class AssetService {
final Isar _db; final Isar _db;
AssetService( AssetService(
this._assetApiRepository,
this._exifInfoRepository,
this._apiService, this._apiService,
this._syncService, this._syncService,
this._userService, this._userService,
@@ -342,4 +352,46 @@ class AssetService {
log.severe("Error while syncing uploaded asset to albums", error, stack); log.severe("Error while syncing uploaded asset to albums", error, stack);
} }
} }
Future<void> setDescription(
Asset asset,
String newDescription,
) async {
final remoteAssetId = asset.remoteId;
final localExifId = asset.exifInfo?.id;
// Guard [remoteAssetId] and [localExifId] null
if (remoteAssetId == null || localExifId == null) {
return;
}
final result = await _assetApiRepository.update(
remoteAssetId,
description: newDescription,
);
final description = result.exifInfo?.description;
if (description != null) {
var exifInfo = await _exifInfoRepository.get(localExifId);
if (exifInfo != null) {
exifInfo.description = description;
await _exifInfoRepository.update(exifInfo);
}
}
}
Future<String> getDescription(Asset asset) async {
final localExifId = asset.exifInfo?.id;
// Guard [remoteAssetId] and [localExifId] null
if (localExifId == null) {
return "";
}
final exifInfo = await _exifInfoRepository.get(localExifId);
return exifInfo?.description ?? "";
}
} }
@@ -1,66 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/exif_info.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart';
class AssetDescriptionService {
AssetDescriptionService(this._db, this._api);
final Isar _db;
final ApiService _api;
Future<void> setDescription(
Asset asset,
String newDescription,
) async {
final remoteAssetId = asset.remoteId;
final localExifId = asset.exifInfo?.id;
// Guard [remoteAssetId] and [localExifId] null
if (remoteAssetId == null || localExifId == null) {
return;
}
final result = await _api.assetsApi.updateAsset(
remoteAssetId,
UpdateAssetDto(description: newDescription),
);
final description = result?.exifInfo?.description;
if (description != null) {
var exifInfo = await _db.exifInfos.get(localExifId);
if (exifInfo != null) {
exifInfo.description = description;
await _db.writeTxn(
() => _db.exifInfos.put(exifInfo),
);
}
}
}
String getAssetDescription(Asset asset) {
final localExifId = asset.exifInfo?.id;
// Guard [remoteAssetId] and [localExifId] null
if (localExifId == null) {
return "";
}
final exifInfo = _db.exifInfos.getSync(localExifId);
return exifInfo?.description ?? "";
}
}
final assetDescriptionServiceProvider = Provider(
(ref) => AssetDescriptionService(
ref.watch(dbProvider),
ref.watch(apiServiceProvider),
),
);
+14 -5
View File
@@ -18,7 +18,9 @@ import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:immich_mobile/repositories/user_api.repository.dart';
import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/entity.service.dart';
import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/services/hash.service.dart';
@@ -30,7 +32,6 @@ import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/partner.service.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/services/user.service.dart'; import 'package:immich_mobile/services/user.service.dart';
import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/backup_progress.dart';
@@ -362,16 +363,20 @@ class BackgroundService {
apiService.setAccessToken(Store.get(StoreKey.accessToken)); apiService.setAccessToken(Store.get(StoreKey.accessToken));
AppSettingsService settingService = AppSettingsService(); AppSettingsService settingService = AppSettingsService();
AppSettingsService settingsService = AppSettingsService(); AppSettingsService settingsService = AppSettingsService();
PartnerService partnerService = PartnerService(apiService, db);
AlbumRepository albumRepository = AlbumRepository(db); AlbumRepository albumRepository = AlbumRepository(db);
AssetRepository assetRepository = AssetRepository(db); AssetRepository assetRepository = AssetRepository(db);
BackupRepository backupAlbumRepository = BackupRepository(db); BackupRepository backupAlbumRepository = BackupRepository(db);
AlbumMediaRepository albumMediaRepository = AlbumMediaRepository(); AlbumMediaRepository albumMediaRepository = AlbumMediaRepository();
FileMediaRepository fileMediaRepository = FileMediaRepository(); FileMediaRepository fileMediaRepository = FileMediaRepository();
UserRepository userRepository = UserRepository(db); UserRepository userRepository = UserRepository(db);
UserApiRepository userApiRepository =
UserApiRepository(apiService.usersApi);
AlbumApiRepository albumApiRepository = AlbumApiRepository albumApiRepository =
AlbumApiRepository(apiService.albumsApi); AlbumApiRepository(apiService.albumsApi);
HashService hashService = HashService(db, this, albumMediaRepository); PartnerApiRepository partnerApiRepository =
PartnerApiRepository(apiService.partnersApi);
HashService hashService =
HashService(assetRepository, this, albumMediaRepository);
EntityService entityService = EntityService entityService =
EntityService(assetRepository, userRepository); EntityService(assetRepository, userRepository);
SyncService syncSerive = SyncService( SyncService syncSerive = SyncService(
@@ -381,8 +386,12 @@ class BackgroundService {
albumMediaRepository, albumMediaRepository,
albumApiRepository, albumApiRepository,
); );
UserService userService = UserService userService = UserService(
UserService(apiService, db, syncSerive, partnerService); partnerApiRepository,
userApiRepository,
userRepository,
syncSerive,
);
AlbumService albumService = AlbumService( AlbumService albumService = AlbumService(
userService, userService,
syncSerive, syncSerive,
@@ -8,41 +8,46 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/exif_info.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/exif_info.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
import 'package:isar/isar.dart';
/// Finds duplicates originating from missing EXIF information /// Finds duplicates originating from missing EXIF information
class BackupVerificationService { class BackupVerificationService {
final Isar _db;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository;
BackupVerificationService(this._db, this._fileMediaRepository); BackupVerificationService(
this._fileMediaRepository,
this._assetRepository,
this._exifInfoRepository,
);
/// Returns at most [limit] assets that were backed up without exif /// Returns at most [limit] assets that were backed up without exif
Future<List<Asset>> findWronglyBackedUpAssets({int limit = 100}) async { Future<List<Asset>> findWronglyBackedUpAssets({int limit = 100}) async {
final owner = Store.get(StoreKey.currentUser).isarId; final owner = Store.get(StoreKey.currentUser).isarId;
final List<Asset> onlyLocal = await _db.assets final List<Asset> onlyLocal = await _assetRepository.getAll(
.where() ownerId: owner,
.remoteIdIsNull() remote: false,
.filter() limit: limit,
.ownerIdEqualTo(owner)
.localIdIsNotNull()
.findAll();
final List<Asset> remoteMatches = await _getMatches(
_db.assets.where().localIdIsNull().filter().remoteIdIsNotNull(),
owner,
onlyLocal,
limit,
); );
final List<Asset> localMatches = await _getMatches( final List<Asset> remoteMatches = await _assetRepository.getMatches(
_db.assets.where().remoteIdIsNull().filter().localIdIsNotNull(), assets: onlyLocal,
owner, ownerId: owner,
remoteMatches, remote: true,
limit, limit: limit,
);
final List<Asset> localMatches = await _assetRepository.getMatches(
assets: remoteMatches,
ownerId: owner,
remote: false,
limit: limit,
); );
final List<Asset> deleteCandidates = [], originals = []; final List<Asset> deleteCandidates = [], originals = [];
@@ -52,7 +57,7 @@ class BackupVerificationService {
localMatches, localMatches,
compare: (a, b) => a.fileName.compareTo(b.fileName), compare: (a, b) => a.fileName.compareTo(b.fileName),
both: (a, b) async { both: (a, b) async {
a.exifInfo = await _db.exifInfos.get(a.id); a.exifInfo = await _exifInfoRepository.get(a.id);
deleteCandidates.add(a); deleteCandidates.add(a);
originals.add(b); originals.add(b);
return false; return false;
@@ -192,35 +197,6 @@ class BackupVerificationService {
return bytes.buffer.asUint64List(start); return bytes.buffer.asUint64List(start);
} }
static Future<List<Asset>> _getMatches(
QueryBuilder<Asset, Asset, QAfterFilterCondition> query,
int ownerId,
List<Asset> assets,
int limit,
) =>
query
.ownerIdEqualTo(ownerId)
.anyOf(
assets,
(q, Asset a) => q
.fileNameEqualTo(a.fileName)
.and()
.durationInSecondsEqualTo(a.durationInSeconds)
.and()
.fileCreatedAtBetween(
a.fileCreatedAt.subtract(const Duration(hours: 12)),
a.fileCreatedAt.add(const Duration(hours: 12)),
)
.and()
.not()
.checksumEqualTo(a.checksum),
)
.sortByFileName()
.thenByFileCreatedAt()
.thenByFileModifiedAt()
.limit(limit)
.findAll();
static bool _sameExceptTimeZone(DateTime a, DateTime b) { static bool _sameExceptTimeZone(DateTime a, DateTime b) {
final ms = a.isAfter(b) final ms = a.isAfter(b)
? a.millisecondsSinceEpoch - b.millisecondsSinceEpoch ? a.millisecondsSinceEpoch - b.millisecondsSinceEpoch
@@ -233,7 +209,8 @@ class BackupVerificationService {
final backupVerificationServiceProvider = Provider( final backupVerificationServiceProvider = Provider(
(ref) => BackupVerificationService( (ref) => BackupVerificationService(
ref.watch(dbProvider),
ref.watch(fileMediaRepositoryProvider), ref.watch(fileMediaRepositoryProvider),
ref.watch(assetRepositoryProvider),
ref.watch(exifInfoRepositoryProvider),
), ),
); );
+12 -17
View File
@@ -4,20 +4,24 @@ import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/android_device_asset.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/device_asset.entity.dart'; import 'package:immich_mobile/entities/device_asset.entity.dart';
import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/extensions/string_extensions.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class HashService { class HashService {
HashService(this._db, this._backgroundService, this._albumMediaRepository); HashService(
final Isar _db; this._assetRepository,
this._backgroundService,
this._albumMediaRepository,
);
final IAssetRepository _assetRepository;
final BackgroundService _backgroundService; final BackgroundService _backgroundService;
final IAlbumMediaRepository _albumMediaRepository; final IAlbumMediaRepository _albumMediaRepository;
final _log = Logger('HashService'); final _log = Logger('HashService');
@@ -55,7 +59,8 @@ class HashService {
final ids = assets final ids = assets
.map(Platform.isAndroid ? (a) => a.localId!.toInt() : (a) => a.localId!) .map(Platform.isAndroid ? (a) => a.localId!.toInt() : (a) => a.localId!)
.toList(); .toList();
final List<DeviceAsset?> hashes = await _lookupHashes(ids); final List<DeviceAsset?> hashes =
await _assetRepository.getDeviceAssetsById(ids);
final List<DeviceAsset> toAdd = []; final List<DeviceAsset> toAdd = [];
final List<String> toHash = []; final List<String> toHash = [];
@@ -106,12 +111,6 @@ class HashService {
return _getHashedAssets(assets, hashes); return _getHashedAssets(assets, hashes);
} }
/// Lookup hashes of assets by their local ID
Future<List<DeviceAsset?>> _lookupHashes(List<Object> ids) =>
Platform.isAndroid
? _db.androidDeviceAssets.getAll(ids.cast())
: _db.iOSDeviceAssets.getAllById(ids.cast());
/// Processes a batch of files and saves any successfully hashed /// Processes a batch of files and saves any successfully hashed
/// values to the DB table. /// values to the DB table.
Future<void> _processBatch( Future<void> _processBatch(
@@ -131,11 +130,7 @@ class HashService {
final validHashes = anyNull final validHashes = anyNull
? toAdd.where((e) => e.hash.length == 20).toList(growable: false) ? toAdd.where((e) => e.hash.length == 20).toList(growable: false)
: toAdd; : toAdd;
await _db.writeTxn( await _assetRepository.upsertDeviceAssets(validHashes);
() => Platform.isAndroid
? _db.androidDeviceAssets.putAll(validHashes.cast())
: _db.iOSDeviceAssets.putAll(validHashes.cast()),
);
_log.fine("Hashed ${validHashes.length}/${toHash.length} assets"); _log.fine("Hashed ${validHashes.length}/${toHash.length} assets");
} }
@@ -168,7 +163,7 @@ class HashService {
final hashServiceProvider = Provider( final hashServiceProvider = Provider(
(ref) => HashService( (ref) => HashService(
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
ref.watch(backgroundServiceProvider), ref.watch(backgroundServiceProvider),
ref.watch(albumMediaRepositoryProvider), ref.watch(albumMediaRepositoryProvider),
), ),
+6 -7
View File
@@ -1,18 +1,17 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/models/memories/memory.model.dart'; import 'package:immich_mobile/models/memories/memory.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final memoryServiceProvider = StateProvider<MemoryService>((ref) { final memoryServiceProvider = StateProvider<MemoryService>((ref) {
return MemoryService( return MemoryService(
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
); );
}); });
@@ -20,9 +19,9 @@ class MemoryService {
final log = Logger("MemoryService"); final log = Logger("MemoryService");
final ApiService _apiService; final ApiService _apiService;
final Isar _db; final IAssetRepository _assetRepository;
MemoryService(this._apiService, this._db); MemoryService(this._apiService, this._assetRepository);
Future<List<Memory>?> getMemoryLane() async { Future<List<Memory>?> getMemoryLane() async {
try { try {
@@ -39,7 +38,7 @@ class MemoryService {
List<Memory> memories = []; List<Memory> memories = [];
for (final MemoryLaneResponseDto(:yearsAgo, :assets) in data) { for (final MemoryLaneResponseDto(:yearsAgo, :assets) in data) {
final dbAssets = final dbAssets =
await _db.assets.getAllByRemoteId(assets.map((e) => e.id)); await _assetRepository.getAllByRemoteId(assets.map((e) => e.id));
if (dbAssets.isNotEmpty) { if (dbAssets.isNotEmpty) {
final String title = yearsAgo <= 1 final String title = yearsAgo <= 1
? 'memories_year_ago'.tr() ? 'memories_year_ago'.tr()
+25 -37
View File
@@ -1,43 +1,33 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:isar/isar.dart'; import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
final partnerServiceProvider = Provider( final partnerServiceProvider = Provider(
(ref) => PartnerService( (ref) => PartnerService(
ref.watch(apiServiceProvider), ref.watch(partnerApiRepositoryProvider),
ref.watch(dbProvider), ref.watch(userRepositoryProvider),
), ),
); );
class PartnerService { class PartnerService {
final ApiService _apiService; final IPartnerApiRepository _partnerApiRepository;
final Isar _db; final IUserRepository _userRepository;
final Logger _log = Logger("PartnerService"); final Logger _log = Logger("PartnerService");
PartnerService(this._apiService, this._db); PartnerService(
this._partnerApiRepository,
Future<List<User>?> getPartners(PartnerDirection direction) async { this._userRepository,
try { );
final userDtos = await _apiService.partnersApi.getPartners(direction);
if (userDtos != null) {
return userDtos.map((u) => User.fromPartnerDto(u)).toList();
}
} catch (e) {
_log.warning("Failed to get partners for direction $direction", e);
}
return null;
}
Future<bool> removePartner(User partner) async { Future<bool> removePartner(User partner) async {
try { try {
await _apiService.partnersApi.removePartner(partner.id); await _partnerApiRepository.delete(partner.id);
partner.isPartnerSharedBy = false; partner.isPartnerSharedBy = false;
await _db.writeTxn(() => _db.users.put(partner)); await _userRepository.update(partner);
} catch (e) { } catch (e) {
_log.warning("Failed to remove partner ${partner.id}", e); _log.warning("Failed to remove partner ${partner.id}", e);
return false; return false;
@@ -47,12 +37,10 @@ class PartnerService {
Future<bool> addPartner(User partner) async { Future<bool> addPartner(User partner) async {
try { try {
final dto = await _apiService.partnersApi.createPartner(partner.id); await _partnerApiRepository.create(partner.id);
if (dto != null) { partner.isPartnerSharedBy = true;
partner.isPartnerSharedBy = true; await _userRepository.update(partner);
await _db.writeTxn(() => _db.users.put(partner)); return true;
return true;
}
} catch (e) { } catch (e) {
_log.warning("Failed to add partner ${partner.id}", e); _log.warning("Failed to add partner ${partner.id}", e);
} }
@@ -61,13 +49,13 @@ class PartnerService {
Future<bool> updatePartner(User partner, {required bool inTimeline}) async { Future<bool> updatePartner(User partner, {required bool inTimeline}) async {
try { try {
final dto = await _apiService.partnersApi final dto = await _partnerApiRepository.update(
.updatePartner(partner.id, UpdatePartnerDto(inTimeline: inTimeline)); partner.id,
if (dto != null) { inTimeline: inTimeline,
partner.inTimeline = dto.inTimeline ?? partner.inTimeline; );
await _db.writeTxn(() => _db.users.put(partner)); partner.inTimeline = dto.inTimeline;
return true; await _userRepository.update(partner);
} return true;
} catch (e) { } catch (e) {
_log.warning("Failed to update partner ${partner.id}", e); _log.warning("Failed to update partner ${partner.id}", e);
} }
+27 -50
View File
@@ -1,29 +1,37 @@
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:isar/isar.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/person_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'person.service.g.dart'; part 'person.service.g.dart';
@riverpod @riverpod
PersonService personService(PersonServiceRef ref) => PersonService personService(PersonServiceRef ref) => PersonService(
PersonService(ref.read(apiServiceProvider), ref.read(dbProvider)); ref.watch(personApiRepositoryProvider),
ref.watch(assetApiRepositoryProvider),
ref.read(assetRepositoryProvider),
);
class PersonService { class PersonService {
final Logger _log = Logger("PersonService"); final Logger _log = Logger("PersonService");
final ApiService _apiService; final IPersonApiRepository _personApiRepository;
final Isar _db; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository;
PersonService(this._apiService, this._db); PersonService(
this._personApiRepository,
this._assetApiRepository,
this._assetRepository,
);
Future<List<PersonResponseDto>> getAllPeople() async { Future<List<Person>> getAllPeople() async {
try { try {
final peopleResponseDto = await _apiService.peopleApi.getAllPeople(); return await _personApiRepository.getAll();
return peopleResponseDto?.people ?? [];
} catch (error, stack) { } catch (error, stack) {
_log.severe("Error while fetching curated people", error, stack); _log.severe("Error while fetching curated people", error, stack);
return []; return [];
@@ -31,50 +39,19 @@ class PersonService {
} }
Future<List<Asset>> getPersonAssets(String id) async { Future<List<Asset>> getPersonAssets(String id) async {
List<Asset> result = [];
var hasNext = true;
var currentPage = 1;
try { try {
while (hasNext) { final assets = await _assetApiRepository.search(personIds: [id]);
final response = await _apiService.searchApi.searchMetadata( return await _assetRepository
MetadataSearchDto( .getAllByRemoteId(assets.map((a) => a.remoteId!));
personIds: [id],
page: currentPage,
size: 1000,
),
);
if (response == null) {
break;
}
if (response.assets.nextPage == null) {
hasNext = false;
}
final assets = response.assets.items;
final mapAssets =
await _db.assets.getAllByRemoteId(assets.map((e) => e.id));
result.addAll(mapAssets);
currentPage++;
}
} catch (error, stack) { } catch (error, stack) {
_log.severe("Error while fetching person assets", error, stack); _log.severe("Error while fetching person assets", error, stack);
} }
return [];
return result;
} }
Future<PersonResponseDto?> updateName(String id, String name) async { Future<Person?> updateName(String id, String name) async {
try { try {
return await _apiService.peopleApi.updatePerson( return await _personApiRepository.update(id, name: name);
id,
PersonUpdateDto(
name: name,
),
);
} catch (error, stack) { } catch (error, stack) {
_log.severe("Error while updating person name", error, stack); _log.severe("Error while updating person name", error, stack);
} }
+1 -1
View File
@@ -6,7 +6,7 @@ part of 'person.service.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$personServiceHash() => r'54e6df4b8eea744f6de009f8315c9fe6230f6798'; String _$personServiceHash() => r'32f28cb5a3de0553c17447e33a0efde7409a43ed';
/// See also [personService]. /// See also [personService].
@ProviderFor(personService) @ProviderFor(personService)
+6 -6
View File
@@ -1,27 +1,27 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final searchServiceProvider = Provider( final searchServiceProvider = Provider(
(ref) => SearchService( (ref) => SearchService(
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
), ),
); );
class SearchService { class SearchService {
final ApiService _apiService; final ApiService _apiService;
final Isar _db; final IAssetRepository _assetRepository;
final _log = Logger("SearchService"); final _log = Logger("SearchService");
SearchService(this._apiService, this._db); SearchService(this._apiService, this._assetRepository);
Future<List<String>?> getSearchSuggestions( Future<List<String>?> getSearchSuggestions(
SearchSuggestionType type, { SearchSuggestionType type, {
@@ -103,7 +103,7 @@ class SearchService {
return null; return null;
} }
return _db.assets return _assetRepository
.getAllByRemoteId(response.assets.items.map((e) => e.id)); .getAllByRemoteId(response.assets.items.map((e) => e.id));
} catch (error, stackTrace) { } catch (error, stackTrace) {
_log.severe("Failed to search for assets", error, stackTrace); _log.severe("Failed to search for assets", error, stackTrace);
+6 -9
View File
@@ -1,17 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class StackService { class StackService {
StackService(this._api, this._db); StackService(this._api, this._assetRepository);
final ApiService _api; final ApiService _api;
final Isar _db; final IAssetRepository _assetRepository;
Future<StackResponseDto?> getStack(String stackId) async { Future<StackResponseDto?> getStack(String stackId) async {
try { try {
@@ -61,10 +61,7 @@ class StackService {
removeAssets.add(asset); removeAssets.add(asset);
} }
await _assetRepository.updateAll(removeAssets);
_db.writeTxn(() async {
await _db.assets.putAll(removeAssets);
});
} catch (error) { } catch (error) {
debugPrint("Error while deleting stack: $error"); debugPrint("Error while deleting stack: $error");
} }
@@ -74,6 +71,6 @@ class StackService {
final stackServiceProvider = Provider( final stackServiceProvider = Provider(
(ref) => StackService( (ref) => StackService(
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
), ),
); );
+33 -47
View File
@@ -1,68 +1,48 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/services/partner.service.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:immich_mobile/interfaces/user_api.interface.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:immich_mobile/repositories/user_api.repository.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
final userServiceProvider = Provider( final userServiceProvider = Provider(
(ref) => UserService( (ref) => UserService(
ref.watch(apiServiceProvider), ref.watch(partnerApiRepositoryProvider),
ref.watch(dbProvider), ref.watch(userApiRepositoryProvider),
ref.watch(userRepositoryProvider),
ref.watch(syncServiceProvider), ref.watch(syncServiceProvider),
ref.watch(partnerServiceProvider),
), ),
); );
class UserService { class UserService {
final ApiService _apiService; final IPartnerApiRepository _partnerApiRepository;
final Isar _db; final IUserApiRepository _userApiRepository;
final IUserRepository _userRepository;
final SyncService _syncService; final SyncService _syncService;
final PartnerService _partnerService;
final Logger _log = Logger("UserService"); final Logger _log = Logger("UserService");
UserService( UserService(
this._apiService, this._partnerApiRepository,
this._db, this._userApiRepository,
this._userRepository,
this._syncService, this._syncService,
this._partnerService,
); );
Future<List<User>?> _getAllUsers() async { Future<List<User>> getUsers({bool self = false}) =>
try { _userRepository.getAll(self: self);
final dto = await _apiService.usersApi.searchUsers();
return dto?.map(User.fromSimpleUserDto).toList();
} catch (e) {
_log.warning("Failed get all users", e);
return null;
}
}
Future<List<User>> getUsersInDb({bool self = false}) async { Future<({String profileImagePath})?> uploadProfileImage(XFile image) async {
if (self) {
return _db.users.where().findAll();
}
final int userId = Store.get(StoreKey.currentUser).isarId;
return _db.users.where().isarIdNotEqualTo(userId).findAll();
}
Future<CreateProfileImageResponseDto?> uploadProfileImage(XFile image) async {
try { try {
return await _apiService.usersApi.createProfileImage( return await _userApiRepository.createProfileImage(
MultipartFile.fromBytes( name: image.name,
'file', data: await image.readAsBytes(),
await image.readAsBytes(),
filename: image.name,
),
); );
} catch (e) { } catch (e) {
_log.warning("Failed to upload profile image", e); _log.warning("Failed to upload profile image", e);
@@ -71,13 +51,19 @@ class UserService {
} }
Future<List<User>?> getUsersFromServer() async { Future<List<User>?> getUsersFromServer() async {
final List<User>? users = await _getAllUsers(); List<User>? users;
final List<User>? sharedBy = try {
await _partnerService.getPartners(PartnerDirection.by); users = await _userApiRepository.getAll();
final List<User>? sharedWith = } catch (e) {
await _partnerService.getPartners(PartnerDirection.with_); _log.warning("Failed to fetch users", e);
users = null;
}
final List<User> sharedBy =
await _partnerApiRepository.getAll(Direction.sharedByMe);
final List<User> sharedWith =
await _partnerApiRepository.getAll(Direction.sharedWithMe);
if (users == null || sharedBy == null || sharedWith == null) { if (users == null) {
_log.warning("Failed to refresh users"); _log.warning("Failed to refresh users");
return null; return null;
} }
+2 -2
View File
@@ -1,7 +1,7 @@
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
String getThumbnailUrl( String getThumbnailUrl(
@@ -61,7 +61,7 @@ String getOriginalUrlForRemoteId(final String id) {
String getImageCacheKey(final Asset asset) { String getImageCacheKey(final Asset asset) {
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == Isar.autoIncrement; final isFromDto = asset.id == noDbId;
return '${isFromDto ? asset.remoteId : asset.id}_fullStage'; return '${isFromDto ? asset.remoteId : asset.id}_fullStage';
} }
+13
View File
@@ -12,6 +12,19 @@ dynamic upgradeDto(dynamic value, String targetType) {
addDefault(value, 'tags', TagsResponse().toJson()); addDefault(value, 'tags', TagsResponse().toJson());
} }
break; break;
case 'ServerConfigDto':
if (value is Map) {
addDefault(
value,
'mapLightStyleUrl',
'https://tiles.immich.cloud/v1/style/light.json',
);
addDefault(
value,
'mapDarkStyleUrl',
'https://tiles.immich.cloud/v1/style/dark.json',
);
}
case 'UserResponseDto': case 'UserResponseDto':
if (value is Map) { if (value is Map) {
addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String());
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
import 'package:immich_mobile/utils/storage_indicator.dart'; import 'package:immich_mobile/utils/storage_indicator.dart';
import 'package:isar/isar.dart';
class ThumbnailImage extends ConsumerWidget { class ThumbnailImage extends ConsumerWidget {
/// The asset to show the thumbnail image for /// The asset to show the thumbnail image for
@@ -46,7 +46,7 @@ class ThumbnailImage extends ConsumerWidget {
? context.primaryColor.darken(amount: 0.6) ? context.primaryColor.darken(amount: 0.6)
: context.primaryColor.lighten(amount: 0.8); : context.primaryColor.lighten(amount: 0.8);
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == Isar.autoIncrement; final isFromDto = asset.id == noDbId;
Widget buildSelectionIcon(Asset asset) { Widget buildSelectionIcon(Asset asset) {
if (isSelected) { if (isSelected) {
@@ -8,7 +8,7 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/asset_description.service.dart'; import 'package:immich_mobile/services/asset.service.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -29,14 +29,16 @@ class DescriptionInput extends HookConsumerWidget {
final focusNode = useFocusNode(); final focusNode = useFocusNode();
final isFocus = useState(false); final isFocus = useState(false);
final isTextEmpty = useState(controller.text.isEmpty); final isTextEmpty = useState(controller.text.isEmpty);
final descriptionProvider = ref.watch(assetDescriptionServiceProvider); final assetService = ref.watch(assetServiceProvider);
final owner = ref.watch(currentUserProvider); final owner = ref.watch(currentUserProvider);
final hasError = useState(false); final hasError = useState(false);
final assetWithExif = ref.watch(assetDetailProvider(asset)); final assetWithExif = ref.watch(assetDetailProvider(asset));
useEffect( useEffect(
() { () {
controller.text = descriptionProvider.getAssetDescription(asset); assetService
.getDescription(asset)
.then((value) => controller.text = value);
return null; return null;
}, },
[assetWithExif.value], [assetWithExif.value],
@@ -45,7 +47,7 @@ class DescriptionInput extends HookConsumerWidget {
submitDescription(String description) async { submitDescription(String description) async {
hasError.value = false; hasError.value = false;
try { try {
await descriptionProvider.setDescription( await assetService.setDescription(
asset, asset,
description, description,
); );
@@ -3,23 +3,23 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
class PeoplePicker extends HookConsumerWidget { class PeoplePicker extends HookConsumerWidget {
const PeoplePicker({super.key, required this.onSelect, this.filter}); const PeoplePicker({super.key, required this.onSelect, this.filter});
final Function(Set<PersonResponseDto>) onSelect; final Function(Set<Person>) onSelect;
final Set<PersonResponseDto>? filter; final Set<Person>? filter;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
var imageSize = 45.0; var imageSize = 45.0;
final people = ref.watch(getAllPeopleProvider); final people = ref.watch(getAllPeopleProvider);
final headers = ApiService.getRequestHeaders(); final headers = ApiService.getRequestHeaders();
final selectedPeople = useState<Set<PersonResponseDto>>(filter ?? {}); final selectedPeople = useState<Set<Person>>(filter ?? {});
return people.widgetWhen( return people.widgetWhen(
onData: (people) { onData: (people) {
+1 -7
View File
@@ -116,7 +116,6 @@ Class | Method | HTTP request | Description
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up | *AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | *AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
*DeprecatedApi* | [**getPersonAssets**](doc//DeprecatedApi.md#getpersonassets) | **GET** /people/{id}/assets | *DeprecatedApi* | [**getPersonAssets**](doc//DeprecatedApi.md#getpersonassets) | **GET** /people/{id}/assets |
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random |
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | *DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive |
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | *DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | *DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates |
@@ -125,7 +124,6 @@ Class | Method | HTTP request | Description
*FileReportsApi* | [**fixAuditFiles**](doc//FileReportsApi.md#fixauditfiles) | **POST** /reports/fix | *FileReportsApi* | [**fixAuditFiles**](doc//FileReportsApi.md#fixauditfiles) | **POST** /reports/fix |
*FileReportsApi* | [**getAuditFiles**](doc//FileReportsApi.md#getauditfiles) | **GET** /reports | *FileReportsApi* | [**getAuditFiles**](doc//FileReportsApi.md#getauditfiles) | **GET** /reports |
*FileReportsApi* | [**getFileChecksums**](doc//FileReportsApi.md#getfilechecksums) | **POST** /reports/checksum | *FileReportsApi* | [**getFileChecksums**](doc//FileReportsApi.md#getfilechecksums) | **POST** /reports/checksum |
*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs |
*JobsApi* | [**getAllJobsStatus**](doc//JobsApi.md#getalljobsstatus) | **GET** /jobs | *JobsApi* | [**getAllJobsStatus**](doc//JobsApi.md#getalljobsstatus) | **GET** /jobs |
*JobsApi* | [**sendJobCommand**](doc//JobsApi.md#sendjobcommand) | **PUT** /jobs/{id} | *JobsApi* | [**sendJobCommand**](doc//JobsApi.md#sendjobcommand) | **PUT** /jobs/{id} |
*LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries | *LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries |
@@ -173,7 +171,6 @@ Class | Method | HTTP request | Description
*SearchApi* | [**searchMetadata**](doc//SearchApi.md#searchmetadata) | **POST** /search/metadata | *SearchApi* | [**searchMetadata**](doc//SearchApi.md#searchmetadata) | **POST** /search/metadata |
*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | *SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person |
*SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places | *SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places |
*SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random |
*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart | *SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart |
*ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license | *ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license |
*ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about | *ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about |
@@ -333,7 +330,6 @@ Class | Method | HTTP request | Description
- [JobCommand](doc//JobCommand.md) - [JobCommand](doc//JobCommand.md)
- [JobCommandDto](doc//JobCommandDto.md) - [JobCommandDto](doc//JobCommandDto.md)
- [JobCountsDto](doc//JobCountsDto.md) - [JobCountsDto](doc//JobCountsDto.md)
- [JobCreateDto](doc//JobCreateDto.md)
- [JobName](doc//JobName.md) - [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md) - [JobSettingsDto](doc//JobSettingsDto.md)
- [JobStatusDto](doc//JobStatusDto.md) - [JobStatusDto](doc//JobStatusDto.md)
@@ -341,11 +337,11 @@ Class | Method | HTTP request | Description
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md) - [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
- [LicenseKeyDto](doc//LicenseKeyDto.md) - [LicenseKeyDto](doc//LicenseKeyDto.md)
- [LicenseResponseDto](doc//LicenseResponseDto.md) - [LicenseResponseDto](doc//LicenseResponseDto.md)
- [LoadTextualModelOnConnection](doc//LoadTextualModelOnConnection.md)
- [LogLevel](doc//LogLevel.md) - [LogLevel](doc//LogLevel.md)
- [LoginCredentialDto](doc//LoginCredentialDto.md) - [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md) - [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md) - [LogoutResponseDto](doc//LogoutResponseDto.md)
- [ManualJobName](doc//ManualJobName.md)
- [MapMarkerResponseDto](doc//MapMarkerResponseDto.md) - [MapMarkerResponseDto](doc//MapMarkerResponseDto.md)
- [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md) - [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md)
- [MapTheme](doc//MapTheme.md) - [MapTheme](doc//MapTheme.md)
@@ -381,7 +377,6 @@ Class | Method | HTTP request | Description
- [PurchaseResponse](doc//PurchaseResponse.md) - [PurchaseResponse](doc//PurchaseResponse.md)
- [PurchaseUpdate](doc//PurchaseUpdate.md) - [PurchaseUpdate](doc//PurchaseUpdate.md)
- [QueueStatusDto](doc//QueueStatusDto.md) - [QueueStatusDto](doc//QueueStatusDto.md)
- [RandomSearchDto](doc//RandomSearchDto.md)
- [RatingsResponse](doc//RatingsResponse.md) - [RatingsResponse](doc//RatingsResponse.md)
- [RatingsUpdate](doc//RatingsUpdate.md) - [RatingsUpdate](doc//RatingsUpdate.md)
- [ReactionLevel](doc//ReactionLevel.md) - [ReactionLevel](doc//ReactionLevel.md)
@@ -455,7 +450,6 @@ Class | Method | HTTP request | Description
- [ToneMapping](doc//ToneMapping.md) - [ToneMapping](doc//ToneMapping.md)
- [TranscodeHWAccel](doc//TranscodeHWAccel.md) - [TranscodeHWAccel](doc//TranscodeHWAccel.md)
- [TranscodePolicy](doc//TranscodePolicy.md) - [TranscodePolicy](doc//TranscodePolicy.md)
- [TrashResponseDto](doc//TrashResponseDto.md)
- [UpdateAlbumDto](doc//UpdateAlbumDto.md) - [UpdateAlbumDto](doc//UpdateAlbumDto.md)
- [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md) - [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md)
- [UpdateAssetDto](doc//UpdateAssetDto.md) - [UpdateAssetDto](doc//UpdateAssetDto.md)
+1 -4
View File
@@ -144,7 +144,6 @@ part 'model/image_format.dart';
part 'model/job_command.dart'; part 'model/job_command.dart';
part 'model/job_command_dto.dart'; part 'model/job_command_dto.dart';
part 'model/job_counts_dto.dart'; part 'model/job_counts_dto.dart';
part 'model/job_create_dto.dart';
part 'model/job_name.dart'; part 'model/job_name.dart';
part 'model/job_settings_dto.dart'; part 'model/job_settings_dto.dart';
part 'model/job_status_dto.dart'; part 'model/job_status_dto.dart';
@@ -152,11 +151,11 @@ part 'model/library_response_dto.dart';
part 'model/library_stats_response_dto.dart'; part 'model/library_stats_response_dto.dart';
part 'model/license_key_dto.dart'; part 'model/license_key_dto.dart';
part 'model/license_response_dto.dart'; part 'model/license_response_dto.dart';
part 'model/load_textual_model_on_connection.dart';
part 'model/log_level.dart'; part 'model/log_level.dart';
part 'model/login_credential_dto.dart'; part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart'; part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart'; part 'model/logout_response_dto.dart';
part 'model/manual_job_name.dart';
part 'model/map_marker_response_dto.dart'; part 'model/map_marker_response_dto.dart';
part 'model/map_reverse_geocode_response_dto.dart'; part 'model/map_reverse_geocode_response_dto.dart';
part 'model/map_theme.dart'; part 'model/map_theme.dart';
@@ -192,7 +191,6 @@ part 'model/places_response_dto.dart';
part 'model/purchase_response.dart'; part 'model/purchase_response.dart';
part 'model/purchase_update.dart'; part 'model/purchase_update.dart';
part 'model/queue_status_dto.dart'; part 'model/queue_status_dto.dart';
part 'model/random_search_dto.dart';
part 'model/ratings_response.dart'; part 'model/ratings_response.dart';
part 'model/ratings_update.dart'; part 'model/ratings_update.dart';
part 'model/reaction_level.dart'; part 'model/reaction_level.dart';
@@ -266,7 +264,6 @@ part 'model/time_bucket_size.dart';
part 'model/tone_mapping.dart'; part 'model/tone_mapping.dart';
part 'model/transcode_hw_accel.dart'; part 'model/transcode_hw_accel.dart';
part 'model/transcode_policy.dart'; part 'model/transcode_policy.dart';
part 'model/trash_response_dto.dart';
part 'model/update_album_dto.dart'; part 'model/update_album_dto.dart';
part 'model/update_album_user_dto.dart'; part 'model/update_album_user_dto.dart';
part 'model/update_asset_dto.dart'; part 'model/update_asset_dto.dart';
-56
View File
@@ -105,62 +105,6 @@ class MapApi {
return null; return null;
} }
/// Performs an HTTP 'GET /map/style.json' operation and returns the [Response].
/// Parameters:
///
/// * [MapTheme] theme (required):
///
/// * [String] key:
Future<Response> getMapStyleWithHttpInfo(MapTheme theme, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/map/style.json';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
queryParams.addAll(_queryParams('', 'theme', theme));
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [MapTheme] theme (required):
///
/// * [String] key:
Future<Object?> getMapStyle(MapTheme theme, { String? key, }) async {
final response = await getMapStyleWithHttpInfo(theme, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
}
return null;
}
/// Performs an HTTP 'GET /map/reverse-geocode' operation and returns the [Response]. /// Performs an HTTP 'GET /map/reverse-geocode' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
+2 -8
View File
@@ -343,8 +343,6 @@ class ApiClient {
return JobCommandDto.fromJson(value); return JobCommandDto.fromJson(value);
case 'JobCountsDto': case 'JobCountsDto':
return JobCountsDto.fromJson(value); return JobCountsDto.fromJson(value);
case 'JobCreateDto':
return JobCreateDto.fromJson(value);
case 'JobName': case 'JobName':
return JobNameTypeTransformer().decode(value); return JobNameTypeTransformer().decode(value);
case 'JobSettingsDto': case 'JobSettingsDto':
@@ -359,6 +357,8 @@ class ApiClient {
return LicenseKeyDto.fromJson(value); return LicenseKeyDto.fromJson(value);
case 'LicenseResponseDto': case 'LicenseResponseDto':
return LicenseResponseDto.fromJson(value); return LicenseResponseDto.fromJson(value);
case 'LoadTextualModelOnConnection':
return LoadTextualModelOnConnection.fromJson(value);
case 'LogLevel': case 'LogLevel':
return LogLevelTypeTransformer().decode(value); return LogLevelTypeTransformer().decode(value);
case 'LoginCredentialDto': case 'LoginCredentialDto':
@@ -367,8 +367,6 @@ class ApiClient {
return LoginResponseDto.fromJson(value); return LoginResponseDto.fromJson(value);
case 'LogoutResponseDto': case 'LogoutResponseDto':
return LogoutResponseDto.fromJson(value); return LogoutResponseDto.fromJson(value);
case 'ManualJobName':
return ManualJobNameTypeTransformer().decode(value);
case 'MapMarkerResponseDto': case 'MapMarkerResponseDto':
return MapMarkerResponseDto.fromJson(value); return MapMarkerResponseDto.fromJson(value);
case 'MapReverseGeocodeResponseDto': case 'MapReverseGeocodeResponseDto':
@@ -439,8 +437,6 @@ class ApiClient {
return PurchaseUpdate.fromJson(value); return PurchaseUpdate.fromJson(value);
case 'QueueStatusDto': case 'QueueStatusDto':
return QueueStatusDto.fromJson(value); return QueueStatusDto.fromJson(value);
case 'RandomSearchDto':
return RandomSearchDto.fromJson(value);
case 'RatingsResponse': case 'RatingsResponse':
return RatingsResponse.fromJson(value); return RatingsResponse.fromJson(value);
case 'RatingsUpdate': case 'RatingsUpdate':
@@ -587,8 +583,6 @@ class ApiClient {
return TranscodeHWAccelTypeTransformer().decode(value); return TranscodeHWAccelTypeTransformer().decode(value);
case 'TranscodePolicy': case 'TranscodePolicy':
return TranscodePolicyTypeTransformer().decode(value); return TranscodePolicyTypeTransformer().decode(value);
case 'TrashResponseDto':
return TrashResponseDto.fromJson(value);
case 'UpdateAlbumDto': case 'UpdateAlbumDto':
return UpdateAlbumDto.fromJson(value); return UpdateAlbumDto.fromJson(value);
case 'UpdateAlbumUserDto': case 'UpdateAlbumUserDto':
-3
View File
@@ -100,9 +100,6 @@ String parameterToString(dynamic value) {
if (value is ManualJobName) { if (value is ManualJobName) {
return ManualJobNameTypeTransformer().encode(value).toString(); return ManualJobNameTypeTransformer().encode(value).toString();
} }
if (value is MapTheme) {
return MapThemeTypeTransformer().encode(value).toString();
}
if (value is MemoryType) { if (value is MemoryType) {
return MemoryTypeTypeTransformer().encode(value).toString(); return MemoryTypeTypeTransformer().encode(value).toString();
} }
+1
View File
@@ -78,6 +78,7 @@ class ActivityCreateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static ActivityCreateDto? fromJson(dynamic value) { static ActivityCreateDto? fromJson(dynamic value) {
upgradeDto(value, "ActivityCreateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -78,6 +78,7 @@ class ActivityResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static ActivityResponseDto? fromJson(dynamic value) { static ActivityResponseDto? fromJson(dynamic value) {
upgradeDto(value, "ActivityResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -40,6 +40,7 @@ class ActivityStatisticsResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static ActivityStatisticsResponseDto? fromJson(dynamic value) { static ActivityStatisticsResponseDto? fromJson(dynamic value) {
upgradeDto(value, "ActivityStatisticsResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -40,6 +40,7 @@ class AddUsersDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AddUsersDto? fromJson(dynamic value) { static AddUsersDto? fromJson(dynamic value) {
upgradeDto(value, "AddUsersDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -40,6 +40,7 @@ class AdminOnboardingUpdateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AdminOnboardingUpdateDto? fromJson(dynamic value) { static AdminOnboardingUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "AdminOnboardingUpdateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -186,6 +186,7 @@ class AlbumResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AlbumResponseDto? fromJson(dynamic value) { static AlbumResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AlbumResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -52,6 +52,7 @@ class AlbumStatisticsResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AlbumStatisticsResponseDto? fromJson(dynamic value) { static AlbumStatisticsResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AlbumStatisticsResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -56,6 +56,7 @@ class AlbumUserAddDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AlbumUserAddDto? fromJson(dynamic value) { static AlbumUserAddDto? fromJson(dynamic value) {
upgradeDto(value, "AlbumUserAddDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -46,6 +46,7 @@ class AlbumUserCreateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AlbumUserCreateDto? fromJson(dynamic value) { static AlbumUserCreateDto? fromJson(dynamic value) {
upgradeDto(value, "AlbumUserCreateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -46,6 +46,7 @@ class AlbumUserResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AlbumUserResponseDto? fromJson(dynamic value) { static AlbumUserResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AlbumUserResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -118,6 +118,7 @@ class AllJobStatusResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AllJobStatusResponseDto? fromJson(dynamic value) { static AllJobStatusResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AllJobStatusResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -56,6 +56,7 @@ class APIKeyCreateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static APIKeyCreateDto? fromJson(dynamic value) { static APIKeyCreateDto? fromJson(dynamic value) {
upgradeDto(value, "APIKeyCreateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -46,6 +46,7 @@ class APIKeyCreateResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static APIKeyCreateResponseDto? fromJson(dynamic value) { static APIKeyCreateResponseDto? fromJson(dynamic value) {
upgradeDto(value, "APIKeyCreateResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -64,6 +64,7 @@ class APIKeyResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static APIKeyResponseDto? fromJson(dynamic value) { static APIKeyResponseDto? fromJson(dynamic value) {
upgradeDto(value, "APIKeyResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -40,6 +40,7 @@ class APIKeyUpdateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static APIKeyUpdateDto? fromJson(dynamic value) { static APIKeyUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "APIKeyUpdateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -56,6 +56,7 @@ class AssetBulkDeleteDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetBulkDeleteDto? fromJson(dynamic value) { static AssetBulkDeleteDto? fromJson(dynamic value) {
upgradeDto(value, "AssetBulkDeleteDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -148,6 +148,7 @@ class AssetBulkUpdateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetBulkUpdateDto? fromJson(dynamic value) { static AssetBulkUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "AssetBulkUpdateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -40,6 +40,7 @@ class AssetBulkUploadCheckDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetBulkUploadCheckDto? fromJson(dynamic value) { static AssetBulkUploadCheckDto? fromJson(dynamic value) {
upgradeDto(value, "AssetBulkUploadCheckDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -47,6 +47,7 @@ class AssetBulkUploadCheckItem {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetBulkUploadCheckItem? fromJson(dynamic value) { static AssetBulkUploadCheckItem? fromJson(dynamic value) {
upgradeDto(value, "AssetBulkUploadCheckItem");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -40,6 +40,7 @@ class AssetBulkUploadCheckResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetBulkUploadCheckResponseDto? fromJson(dynamic value) { static AssetBulkUploadCheckResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AssetBulkUploadCheckResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -88,6 +88,7 @@ class AssetBulkUploadCheckResult {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetBulkUploadCheckResult? fromJson(dynamic value) { static AssetBulkUploadCheckResult? fromJson(dynamic value) {
upgradeDto(value, "AssetBulkUploadCheckResult");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -46,6 +46,7 @@ class AssetDeltaSyncDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetDeltaSyncDto? fromJson(dynamic value) { static AssetDeltaSyncDto? fromJson(dynamic value) {
upgradeDto(value, "AssetDeltaSyncDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -52,6 +52,7 @@ class AssetDeltaSyncResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetDeltaSyncResponseDto? fromJson(dynamic value) { static AssetDeltaSyncResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AssetDeltaSyncResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -102,6 +102,7 @@ class AssetFaceResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetFaceResponseDto? fromJson(dynamic value) { static AssetFaceResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AssetFaceResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -40,6 +40,7 @@ class AssetFaceUpdateDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetFaceUpdateDto? fromJson(dynamic value) { static AssetFaceUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "AssetFaceUpdateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
+1
View File
@@ -46,6 +46,7 @@ class AssetFaceUpdateItem {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetFaceUpdateItem? fromJson(dynamic value) { static AssetFaceUpdateItem? fromJson(dynamic value) {
upgradeDto(value, "AssetFaceUpdateItem");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
@@ -92,6 +92,7 @@ class AssetFaceWithoutPersonResponseDto {
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetFaceWithoutPersonResponseDto? fromJson(dynamic value) { static AssetFaceWithoutPersonResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AssetFaceWithoutPersonResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();

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