Compare commits

...

51 Commits

Author SHA1 Message Date
bo0tzz
66bde40ef8 chore: use non-default database name for medium tests 2025-06-20 21:17:51 +02:00
Alex
c707f9cef4 refactor(mobile): partner.interface.dart (#19338) 2025-06-20 18:37:59 +00:00
dotlambda
6fda863c08 fix(server): don't hardcode database name in migration (#19376) 2025-06-20 21:33:34 +03:00
Daniel Dietzler
373b654156 chore: migrate profile picture cropper modal (#19378) 2025-06-20 18:16:10 +00:00
Daniel Dietzler
a5d84ba552 chore: consistent modal footer spacing (#19377) 2025-06-20 18:05:39 +00:00
Daniel Dietzler
1dc8fa2979 chore: rename edit album form modal (#19375) 2025-06-20 13:51:14 -04:00
Alex
0426699f13 refactor(mobile): partner_api.interface.dart (#19337)
* refactor(mobile): partner_api.interface.dart

* merge main
2025-06-20 17:04:15 +00:00
Alex
8154ec29df refactor(mobile): person_api.interface.dart (#19336) 2025-06-20 11:45:31 -05:00
Alex
3024cd343b refactor(mobile): timeline.interface.dart (#19331)
refactor(mobile): timeline.repository.dart
2025-06-20 11:44:02 -05:00
Zack Pollard
0b44d4b6f2 fix: partner and album backfill acks (#19371)
fix: partner sync being entirely broken
2025-06-20 16:14:36 +00:00
github-actions
a04c6ed80d chore: version v1.135.2 2025-06-20 14:52:47 +00:00
Brandon Wees
1c50e19894 fix: use icons instead of toggles for admin user features view (#19369)
* fix: use icons instead of toggles for admin user features view

* fix: use red for X icon
2025-06-20 14:48:18 +00:00
Alex
e61d7f2616 refactor(mobile): album_media.interface.dart (#19355)
* refactor(mobile): album_media.interface.dart

* refactor: album_media repo

* make dcm happy

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-06-20 14:23:43 +00:00
Alex
a6b0869714 refactor(mobile): secure_storage.interface.dart (#19335)
* refactor(mobile): secure_storage.interface.dart

* fix: lint
2025-06-20 09:12:07 -05:00
Alex
9c25b8ba7d refactor(mobile): download.interface.dart (#19345) 2025-06-20 09:11:52 -05:00
Alex
3c72f489d8 refactor(mobile): sessions_api.interface.dart (#19334)
refactor(mobile): sessions_api.repository.dart
2025-06-20 09:11:33 -05:00
Alex
1f2c779b36 refactor(mobile): shared_handler.interface.dart (#19333)
refactor(mobile): shared_handler.repository.dart
2025-06-20 09:11:21 -05:00
Alex
5c74f634b7 refactor(mobile): network.interface.dart (#19341)
* refactor(mobile): network.interface.dart

* refactor(mobile): network.interface.dart
2025-06-20 09:11:03 -05:00
Alex
ecc99bfd16 refactor(mobile): biometric.interface.dart (#19347) 2025-06-20 09:10:53 -05:00
Alex
ff4d70e351 refactor(mobile): album_api.interface.dart (#19356) 2025-06-20 09:08:23 -05:00
Alex
42c2389eb5 refactor(mobile): activity_api.interface.dart (#19357) 2025-06-20 09:08:12 -05:00
Jason Rasmussen
33c9f88ba4 fix: time bucket grouping (#19329) 2025-06-20 09:46:30 -04:00
Mert
11c469907f fix(server): migration failing on pg15+ (#19363)
* reset params

* unused parameter
2025-06-20 08:36:07 -05:00
Mert
7c43e6c3c8 fix: bump vchord default to 0.4.3 (#19365)
bump default to 0.4.3
2025-06-20 08:35:32 -05:00
Zack Pollard
00aa385972 fix: people ordering by asset count (#19366) 2025-06-20 07:34:04 -05:00
Min Idzelis
a5ed453929 chore: unused deps (#19256)
remove joi
2025-06-20 00:30:23 -04:00
Jason Rasmussen
dd8969cb7d fix: container padding (#19316) 2025-06-19 21:33:12 -05:00
Alex
bce4f93c90 refactor(mobile): (1) user.interface.dart (#19322)
* refactor(mobile): user.interface.dart

* generate files

* refactor(mobile): (2) user_api.interface.dart (#19323)

* refactor(mobile): (2) user_api.interface.dart

* generate files

* refactor(mobile): (3) sync_stream.interface.dart (#19325)
2025-06-19 23:25:18 +00:00
Alex
a4c0dc5007 chore: post release tasks (#19311) 2025-06-19 15:35:25 -04:00
Matthew Momjian
d233a7d97a fix(server): remove excessive inactivity log (#19306) 2025-06-19 19:13:13 +00:00
Jason Rasmussen
5cdbb65d28 feat: better contrast for checkmark indicator (#19312)
feat: better constrast
2025-06-19 13:20:57 -05:00
github-actions
3434544864 chore: version v1.135.1 2025-06-19 17:37:39 +00:00
Brandon Wees
269bf4b344 fix: iOS 17.0 target version for widget (#19308) 2025-06-19 17:00:54 +00:00
Zack Pollard
f9435a538b revert: fix(web): wrap long names with textarea (#19305)
Revert "fix(web): wrap long names with textarea (#19301)"

This reverts commit 747a72120e.
2025-06-19 16:28:10 +00:00
Alex
10e2ec2841 chore: skip truncating table in this release (#19300) 2025-06-19 16:11:18 +00:00
Zack Pollard
fe91b44ab9 fix: people ordering incorrect (#19298) 2025-06-19 16:05:03 +00:00
Jin Xuan
747a72120e fix(web): wrap long names with textarea (#19301) 2025-06-19 15:57:54 +00:00
Jason Rasmussen
910661e75c chore: remove unused mocks (#19299) 2025-06-19 10:35:09 -05:00
Alex
c8a135a7ae fix: .find() iterator api combat (#19293)
* fix: .find() iterator api combar

* Update web/src/lib/managers/timeline-manager/month-group.svelte.ts

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2025-06-19 14:59:14 +00:00
xCJPECKOVERx
08d1cf5bde fix(web): Stack assets in asset-viewer cut off on the left (#19253)
* - move overflow and scrollbar to stack-slideshow inner div

* - format
2025-06-19 09:20:25 -05:00
Alex
3e62497fd0 fix: local network permission (#19285) 2025-06-19 14:18:51 +00:00
Alex
a1bc862a32 chore: post release tasks (#19249) 2025-06-19 09:11:24 -05:00
Alex
75bf3aa1be chore: correct vchord version in docker-compose.yml (#19251) 2025-06-19 09:11:15 -05:00
Dag Stuan
38e68d16f9 fix(web): exit slideshow when exiting fullscreen. (#19247)
Exit slideshow when exiting fullscreen.

Browsers do not send a keyboard event when exiting fullscreen, so if
the user exits fullscreen with the escape key, the slideshow
remains open, requiring another escape key press to close it. Fix this
by listening for the fullscreenchange event and closing the slideshow
when exiting fullscreen.
2025-06-19 14:10:10 +00:00
Daniel Dietzler
caf11fbb96 fix: album asset viewer (#19252) 2025-06-19 09:09:23 -05:00
Mert
f99c6feac5 fix(server): unset prewarm dim parameter (#19271)
unset prewarm dim
2025-06-19 09:04:52 -05:00
Matthew Momjian
5122512f19 fix(docs): REINDEX vchord on upgrade (#19282)
* reindex

* lint

* collapse migrations

* remove title

* reformat
2025-06-19 09:04:18 -05:00
Mert
49ed212af8 fix(server): drop vector indices before updating extension (#19283)
drop indices before updating
2025-06-19 09:03:40 -05:00
Paul Larsen
e29103b69f fix album list CSS margins (#19262) 2025-06-19 14:03:14 +00:00
Min Idzelis
14b771d7c7 fix: devcontainer in codespaces (#19259)
* fix: devcontainer perms

* Fix host based auth

* use path tricks to get to volume mount, but remain compat with current meaning of variables

* eureka, i think

* bit of cleanup
2025-06-19 08:29:22 -05:00
Daniel Dietzler
07aa51638c fix: panning interrupted while moving around the map (#19276) 2025-06-19 11:28:53 +00:00
151 changed files with 679 additions and 921 deletions

View File

@@ -55,7 +55,7 @@
"userEnvProbe": "loginInteractiveShell", "userEnvProbe": "loginInteractiveShell",
"remoteEnv": { "remoteEnv": {
// The location where your uploaded files are stored // The location where your uploaded files are stored
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:upload-devcontainer-volume}", "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
// Connection secret for postgres. You should change it to a random password // Connection secret for postgres. You should change it to a random password
// Please use only the characters `A-Za-z0-9`, without special characters or spaces // Please use only the characters `A-Za-z0-9`, without special characters or spaces
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}", "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",

View File

@@ -51,14 +51,19 @@ fix_permissions() {
run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} + run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} +
run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \ # Change ownership for directories that exist
for dir in "${IMMICH_WORKSPACE}/.vscode" \
"${IMMICH_WORKSPACE}/cli/node_modules" \ "${IMMICH_WORKSPACE}/cli/node_modules" \
"${IMMICH_WORKSPACE}/e2e/node_modules" \ "${IMMICH_WORKSPACE}/e2e/node_modules" \
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \ "${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
"${IMMICH_WORKSPACE}/server/node_modules" \ "${IMMICH_WORKSPACE}/server/node_modules" \
"${IMMICH_WORKSPACE}/server/dist" \ "${IMMICH_WORKSPACE}/server/dist" \
"${IMMICH_WORKSPACE}/web/node_modules" \ "${IMMICH_WORKSPACE}/web/node_modules" \
"${IMMICH_WORKSPACE}/web/dist" "${IMMICH_WORKSPACE}/web/dist"; do
if [ -d "$dir" ]; then
run_cmd sudo chown node -R "$dir"
fi
done
log "" log ""
} }

View File

@@ -12,8 +12,8 @@ services:
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules - open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
- server_node_modules:/workspaces/immich/server/node_modules - server_node_modules:/workspaces/immich/server/node_modules
- web_node_modules:/workspaces/immich/web/node_modules - web_node_modules:/workspaces/immich/web/node_modules
- ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload - ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/workspaces/immich/server/upload
- ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload - ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/workspaces/immich/server/upload/upload
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
immich-web: immich-web:
@@ -29,8 +29,9 @@ services:
POSTGRES_USER: ${DB_USERNAME-postgres} POSTGRES_USER: ${DB_USERNAME-postgres}
POSTGRES_DB: ${DB_DATABASE_NAME-immich} POSTGRES_DB: ${DB_DATABASE_NAME-immich}
POSTGRES_INITDB_ARGS: '--data-checksums' POSTGRES_INITDB_ARGS: '--data-checksums'
volumes: POSTGRES_HOST_AUTH_METHOD: md5
- ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data volumes:
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
redis: redis:
env_file: !reset [] env_file: !reset []
@@ -42,4 +43,6 @@ volumes:
open_api_node_modules: open_api_node_modules:
server_node_modules: server_node_modules:
web_node_modules: web_node_modules:
upload-devcontainer-volume: upload1-devcontainer-volume:
upload2-devcontainer-volume:
postgres-devcontainer-volume:

View File

@@ -644,7 +644,7 @@ jobs:
contents: read contents: read
services: services:
postgres: postgres:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.71",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.71",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.71",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",

View File

@@ -16,7 +16,7 @@ name: immich-dev
services: services:
immich-server: immich-server:
container_name: immich_server container_name: immich_server
command: [ '/usr/src/app/bin/immich-dev' ] command: ['/usr/src/app/bin/immich-dev']
image: immich-server-dev:latest image: immich-server-dev:latest
# extends: # extends:
# file: hwaccel.transcoding.yml # file: hwaccel.transcoding.yml
@@ -70,7 +70,7 @@ services:
# user: 0:0 # user: 0:0
build: build:
context: ../web context: ../web
command: [ '/usr/src/app/bin/immich-web' ] command: ['/usr/src/app/bin/immich-web']
env_file: env_file:
- .env - .env
ports: ports:
@@ -122,7 +122,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:

View File

@@ -63,7 +63,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:
@@ -91,7 +91,7 @@ services:
# add data source for http://immich-prometheus:9090 to get started # add data source for http://immich-prometheus:9090 to get started
immich-grafana: immich-grafana:
container_name: immich_grafana container_name: immich_grafana
command: [ './run.sh', '-disable-reporting' ] command: ['./run.sh', '-disable-reporting']
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055 image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055

View File

@@ -56,7 +56,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
environment: environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME} POSTGRES_USER: ${DB_USERNAME}

View File

@@ -64,7 +64,13 @@ COMMIT;
### Updating VectorChord ### Updating VectorChord
When installing a new version of VectorChord, you will need to manually update the extension by connecting to the Immich database and running `ALTER EXTENSION vchord UPDATE;`. When installing a new version of VectorChord, you will need to manually update the extension and reindex by connecting to the Immich database and running:
```
ALTER EXTENSION vchord UPDATE;
REINDEX INDEX face_index;
REINDEX INDEX clip_index;
```
## Migrating to VectorChord ## Migrating to VectorChord
@@ -76,6 +82,8 @@ Support for pgvecto.rs will be dropped in a later release, hence we recommend al
The easiest option is to have both extensions installed during the migration: The easiest option is to have both extensions installed during the migration:
<details>
<summary>Migration steps (automatic)</summary>
1. Ensure you still have pgvecto.rs installed 1. Ensure you still have pgvecto.rs installed
2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`) 2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`)
3. [Install VectorChord][vchord-install] 3. [Install VectorChord][vchord-install]
@@ -89,8 +97,12 @@ The easiest option is to have both extensions installed during the migration:
11. Restart the Postgres database 11. Restart the Postgres database
12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord` 12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord`
</details>
If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps: If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps:
<details>
<summary>Migration steps (manual)</summary>
1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later 1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later
```sql ```sql
@@ -123,14 +135,20 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
5. Start Immich and let it create new indices using VectorChord 5. Start Immich and let it create new indices using VectorChord
</details>
### Migrating from pgvector ### Migrating from pgvector
<details>
<summary>Migration steps</summary>
1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client 1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
2. Follow the Prerequisites to install VectorChord 2. Follow the Prerequisites to install VectorChord
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;` 3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set 4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
5. Start Immich and let it create new indices using VectorChord 5. Start Immich and let it create new indices using VectorChord
</details>
Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps. Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps.
[vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html [vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html

View File

@@ -1,4 +1,12 @@
[ [
{
"label": "v1.135.2",
"url": "https://v1.135.2.archive.immich.app"
},
{
"label": "v1.135.1",
"url": "https://v1.135.1.archive.immich.app"
},
{ {
"label": "v1.135.0", "label": "v1.135.0",
"url": "https://v1.135.0.archive.immich.app" "url": "https://v1.135.0.archive.immich.app"

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.0", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.0", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -44,7 +44,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.71",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -93,7 +93,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.0", "version": "1.135.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -14,7 +14,11 @@ describe('/people', () => {
let nameAlicePerson: PersonResponseDto; let nameAlicePerson: PersonResponseDto;
let nameBobPerson: PersonResponseDto; let nameBobPerson: PersonResponseDto;
let nameCharliePerson: PersonResponseDto; let nameCharliePerson: PersonResponseDto;
let nameNullPerson: PersonResponseDto; let nameNullPerson4Assets: PersonResponseDto;
let nameNullPerson3Assets: PersonResponseDto;
let nameNullPerson1Asset: PersonResponseDto;
let nameBillPersonFavourite: PersonResponseDto;
let nameFreddyPersonFavourite: PersonResponseDto;
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
@@ -27,7 +31,11 @@ describe('/people', () => {
nameCharliePerson, nameCharliePerson,
nameBobPerson, nameBobPerson,
nameAlicePerson, nameAlicePerson,
nameNullPerson, nameNullPerson4Assets,
nameNullPerson3Assets,
nameNullPerson1Asset,
nameBillPersonFavourite,
nameFreddyPersonFavourite,
] = await Promise.all([ ] = await Promise.all([
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: 'visible_person', name: 'visible_person',
@@ -52,11 +60,26 @@ describe('/people', () => {
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: '', name: '',
}), }),
utils.createPerson(admin.accessToken, {
name: '',
}),
utils.createPerson(admin.accessToken, {
name: '',
}),
utils.createPerson(admin.accessToken, {
name: 'Bill',
isFavorite: true,
}),
utils.createPerson(admin.accessToken, {
name: 'Freddy',
isFavorite: true,
}),
]); ]);
const asset1 = await utils.createAsset(admin.accessToken); const asset1 = await utils.createAsset(admin.accessToken);
const asset2 = await utils.createAsset(admin.accessToken); const asset2 = await utils.createAsset(admin.accessToken);
const asset3 = await utils.createAsset(admin.accessToken); const asset3 = await utils.createAsset(admin.accessToken);
const asset4 = await utils.createAsset(admin.accessToken);
await Promise.all([ await Promise.all([
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }), utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
@@ -64,15 +87,27 @@ describe('/people', () => {
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), // 4 assets
// Named persons // Named persons
utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset
utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }), utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }),
utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets
utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset
// Null-named person // Null-named person 4 assets
utils.createFace({ assetId: asset1.id, personId: nameNullPerson.id }), utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson.id }), // 2 assets utils.createFace({ assetId: asset2.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset3.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset4.id, personId: nameNullPerson4Assets.id }), // 4 assets
// Null-named person 3 assets
utils.createFace({ assetId: asset1.id, personId: nameNullPerson3Assets.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson3Assets.id }),
utils.createFace({ assetId: asset3.id, personId: nameNullPerson3Assets.id }), // 3 assets
// Null-named person 1 asset
utils.createFace({ assetId: asset3.id, personId: nameNullPerson1Asset.id }),
// Favourite People
utils.createFace({ assetId: asset1.id, personId: nameFreddyPersonFavourite.id }),
utils.createFace({ assetId: asset2.id, personId: nameFreddyPersonFavourite.id }),
utils.createFace({ assetId: asset1.id, personId: nameBillPersonFavourite.id }),
]); ]);
}); });
@@ -87,15 +122,19 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 7, total: 11,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }), expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }), expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ name: 'hidden_person' }), expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
expect.objectContaining({ name: 'hidden_person' }), // Should really be before the null names
], ],
}); });
}); });
@@ -105,17 +144,21 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body.hasNextPage).toBe(false); expect(body.hasNextPage).toBe(false);
expect(body.total).toBe(7); // All persons expect(body.total).toBe(11); // All persons
expect(body.hidden).toBe(1); // 'hidden_person' expect(body.hidden).toBe(1); // 'hidden_person'
const people = body.people as PersonResponseDto[]; const people = body.people as PersonResponseDto[];
expect(people.map((p) => p.id)).toEqual([ expect(people.map((p) => p.id)).toEqual([
nameFreddyPersonFavourite.id, // name: 'Freddy', count: 2
nameBillPersonFavourite.id, // name: 'Bill', count: 1
multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3 multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3
nameBobPerson.id, // name: 'Bob', count: 2 nameBobPerson.id, // name: 'Bob', count: 2
nameAlicePerson.id, // name: 'Alice', count: 1 nameAlicePerson.id, // name: 'Alice', count: 1
nameCharliePerson.id, // name: 'Charlie', count: 1 nameCharliePerson.id, // name: 'Charlie', count: 1
visiblePerson.id, // name: 'visible_person', count: 1 visiblePerson.id, // name: 'visible_person', count: 1
nameNullPerson4Assets.id, // name: '', count: 4
nameNullPerson3Assets.id, // name: '', count: 3
]); ]);
expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false); expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false);
@@ -127,14 +170,18 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 7, total: 11,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }), expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }), expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
], ],
}); });
}); });
@@ -148,9 +195,9 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: true, hasNextPage: true,
total: 7, total: 11,
hidden: 1, hidden: 1,
people: [expect.objectContaining({ name: 'visible_person' })], people: [expect.objectContaining({ name: 'Alice' })],
}); });
}); });
}); });

View File

@@ -244,7 +244,6 @@ async def load(model: InferenceModel) -> InferenceModel:
async def idle_shutdown_task() -> None: async def idle_shutdown_task() -> None:
while True: while True:
log.debug("Checking for inactivity...")
if ( if (
last_called is not None last_called is not None
and not active_requests and not active_requests

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 201, "android.injected.version.code" => 203,
"android.injected.version.name" => "1.135.0", "android.injected.version.name" => "1.135.2",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -346,6 +346,7 @@
}; };
F0B57D372DF764BD00DC5BCC = { F0B57D372DF764BD00DC5BCC = {
CreatedOnToolsVersion = 16.4; CreatedOnToolsVersion = 16.4;
ProvisioningStyle = Automatic;
}; };
FAC6F88F2D287C890078CB2F = { FAC6F88F2D287C890078CB2F = {
CreatedOnToolsVersion = 16.0; CreatedOnToolsVersion = 16.0;
@@ -648,7 +649,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -792,7 +793,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -822,7 +823,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -856,7 +857,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -864,7 +865,7 @@
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widget; INFOPLIST_KEY_CFBundleDisplayName = Widget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -899,7 +900,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -907,7 +908,7 @@
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widget; INFOPLIST_KEY_CFBundleDisplayName = Widget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -939,7 +940,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -947,7 +948,7 @@
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widget; INFOPLIST_KEY_CFBundleDisplayName = Widget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -978,7 +979,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -1022,7 +1023,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -1063,7 +1064,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;

View File

@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.134.0</string> <string>1.135.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>208</string> <string>210</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true /> <true />
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
@@ -115,8 +115,8 @@
</dict> </dict>
<key>NSBonjourServices</key> <key>NSBonjourServices</key>
<array> <array>
<string>_googlecast._tcp</string> <string>_googlecast._tcp</string>
<string>_CC1AD845._googlecast._tcp</string> <string>_CC1AD845._googlecast._tcp</string>
</array> </array>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string> <string>We need to access the camera to let you take beautiful video using this app</string>
@@ -168,5 +168,8 @@
<true /> <true />
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>We need to use FaceID to allow access to your locked folder</string> <string>We need to use FaceID to allow access to your locked folder</string>
<key>NSLocalNetworkUsageDescription</key>
<string>We need local network permission to connect to the local server using IP address and
allow the casting feature to work</string>
</dict> </dict>
</plist> </plist>

View File

@@ -43,7 +43,7 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
"Choose an album to show images from" "Choose an album to show images from"
} }
@Parameter(title: "Album", default: NO_ALBUM) @Parameter(title: "Album")
var album: Album? var album: Album?
@Parameter(title: "Show Album Name", default: false) @Parameter(title: "Show Album Name", default: false)

View File

@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
) )
increment_version_number( increment_version_number(
version_number: "1.135.0" version_number: "1.135.2"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,

View File

@@ -10,3 +10,5 @@ enum TextSearchType {
} }
enum AssetVisibilityEnum { timeline, hidden, archive, locked } enum AssetVisibilityEnum { timeline, hidden, archive, locked }
enum SortUserBy { id }

View File

@@ -1,18 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:openapi/api.dart';
abstract interface class ISyncStreamRepository implements IDatabaseRepository {
Future<void> updateUsersV1(Iterable<SyncUserV1> data);
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data);
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data);
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data);
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data);
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data);
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data);
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data);
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data);
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data);
}

View File

@@ -1,22 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserRepository implements IDatabaseRepository {
Future<bool> insert(UserDto user);
Future<UserDto?> getByUserId(String id);
Future<List<UserDto?>> getByUserIds(List<String> ids);
Future<List<UserDto>> getAll({SortUserBy? sortBy});
Future<bool> updateAll(List<UserDto> users);
Future<UserDto> update(UserDto user);
Future<void> delete(List<String> ids);
Future<void> deleteAll();
}
enum SortUserBy { id }

View File

@@ -1,15 +0,0 @@
import 'dart:typed_data';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserApiRepository {
Future<UserDto?> getMyUser();
Future<List<UserDto>> getAll();
/// Saves the [data] in the server and uses it as the current users profile image
Future<String> createProfileImage({
required String name,
required Uint8List data,
});
}

View File

@@ -1,11 +1,5 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
abstract interface class IPersonApiRepository {
Future<List<Person>> getAll();
Future<Person> update(String id, {String? name});
}
class Person { class Person {
Person({ Person({
required this.id, required this.id,

View File

@@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -10,12 +10,12 @@ class SyncStreamService {
final Logger _logger = Logger('SyncStreamService'); final Logger _logger = Logger('SyncStreamService');
final ISyncApiRepository _syncApiRepository; final ISyncApiRepository _syncApiRepository;
final ISyncStreamRepository _syncStreamRepository; final SyncStreamRepository _syncStreamRepository;
final bool Function()? _cancelChecker; final bool Function()? _cancelChecker;
SyncStreamService({ SyncStreamService({
required ISyncApiRepository syncApiRepository, required ISyncApiRepository syncApiRepository,
required ISyncStreamRepository syncStreamRepository, required SyncStreamRepository syncStreamRepository,
bool Function()? cancelChecker, bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository, }) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository, _syncStreamRepository = syncStreamRepository,

View File

@@ -1,24 +1,24 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class UserService { class UserService {
final Logger _log = Logger("UserService"); final Logger _log = Logger("UserService");
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final IUserApiRepository _userApiRepository; final UserApiRepository _userApiRepository;
final StoreService _storeService; final StoreService _storeService;
UserService({ UserService({
required IUserRepository userRepository, required IsarUserRepository isarUserRepository,
required IUserApiRepository userApiRepository, required UserApiRepository userApiRepository,
required StoreService storeService, required StoreService storeService,
}) : _userRepository = userRepository, }) : _isarUserRepository = isarUserRepository,
_userApiRepository = userApiRepository, _userApiRepository = userApiRepository,
_storeService = storeService; _storeService = storeService;
@@ -38,7 +38,7 @@ class UserService {
final user = await _userApiRepository.getMyUser(); final user = await _userApiRepository.getMyUser();
if (user == null) return null; if (user == null) return null;
await _storeService.put(StoreKey.currentUser, user); await _storeService.put(StoreKey.currentUser, user);
await _userRepository.update(user); await _isarUserRepository.update(user);
return user; return user;
} }
@@ -50,7 +50,7 @@ class UserService {
); );
final updatedUser = getMyUser().copyWith(profileImagePath: path); final updatedUser = getMyUser().copyWith(profileImagePath: path);
await _storeService.put(StoreKey.currentUser, updatedUser); await _storeService.put(StoreKey.currentUser, updatedUser);
await _userRepository.update(updatedUser); await _isarUserRepository.update(updatedUser);
return path; return path;
} catch (e) { } catch (e) {
_log.warning("Failed to upload profile image", e); _log.warning("Failed to upload profile image", e);
@@ -59,10 +59,10 @@ class UserService {
} }
Future<List<UserDto>> getAll() async { Future<List<UserDto>> getAll() async {
return await _userRepository.getAll(); return await _isarUserRepository.getAll();
} }
Future<void> deleteAll() { Future<void> deleteAll() {
return _userRepository.deleteAll(); return _isarUserRepository.deleteAll();
} }
} }

View File

@@ -1,5 +1,4 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
@@ -10,14 +9,12 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AssetVisibility; import 'package:openapi/api.dart' as api show AssetVisibility;
import 'package:openapi/api.dart' hide AssetVisibility; import 'package:openapi/api.dart' hide AssetVisibility;
class DriftSyncStreamRepository extends DriftDatabaseRepository class SyncStreamRepository extends DriftDatabaseRepository {
implements ISyncStreamRepository {
final Logger _logger = Logger('DriftSyncStreamRepository'); final Logger _logger = Logger('DriftSyncStreamRepository');
final Drift _db; final Drift _db;
DriftSyncStreamRepository(super.db) : _db = db; SyncStreamRepository(super.db) : _db = db;
@override
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async { Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -34,7 +31,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateUsersV1(Iterable<SyncUserV1> data) async { Future<void> updateUsersV1(Iterable<SyncUserV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -57,7 +53,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async { Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -77,7 +72,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async { Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -101,7 +95,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async { Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
try { try {
await _deleteAssetsV1(data); await _deleteAssetsV1(data);
@@ -111,7 +104,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async { Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async {
try { try {
await _updateAssetsV1(data); await _updateAssetsV1(data);
@@ -121,7 +113,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async { Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
try { try {
await _deleteAssetsV1(data); await _deleteAssetsV1(data);
@@ -131,7 +122,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async { Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async {
try { try {
await _updateAssetsV1(data); await _updateAssetsV1(data);
@@ -141,7 +131,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async { Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
try { try {
await _updateAssetExifV1(data); await _updateAssetExifV1(data);
@@ -151,7 +140,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async { Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
try { try {
await _updateAssetExifV1(data); await _updateAssetExifV1(data);

View File

@@ -1,30 +1,26 @@
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarUserRepository extends IsarDatabaseRepository class IsarUserRepository extends IsarDatabaseRepository {
implements IUserRepository {
final Isar _db; final Isar _db;
const IsarUserRepository(super.db) : _db = db; const IsarUserRepository(super.db) : _db = db;
@override
Future<void> delete(List<String> ids) async { Future<void> delete(List<String> ids) async {
await transaction(() async { await transaction(() async {
await _db.users.deleteAllById(ids); await _db.users.deleteAllById(ids);
}); });
} }
@override
Future<void> deleteAll() async { Future<void> deleteAll() async {
await transaction(() async { await transaction(() async {
await _db.users.clear(); await _db.users.clear();
}); });
} }
@override
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async { Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
return (await _db.users return (await _db.users
.where() .where()
@@ -39,17 +35,14 @@ class IsarUserRepository extends IsarDatabaseRepository
.toList(); .toList();
} }
@override
Future<UserDto?> getByUserId(String id) async { Future<UserDto?> getByUserId(String id) async {
return (await _db.users.getById(id))?.toDto(); return (await _db.users.getById(id))?.toDto();
} }
@override
Future<List<UserDto?>> getByUserIds(List<String> ids) async { Future<List<UserDto?>> getByUserIds(List<String> ids) async {
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList(); return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
} }
@override
Future<bool> insert(UserDto user) async { Future<bool> insert(UserDto user) async {
await transaction(() async { await transaction(() async {
await _db.users.put(entity.User.fromDto(user)); await _db.users.put(entity.User.fromDto(user));
@@ -57,7 +50,6 @@ class IsarUserRepository extends IsarDatabaseRepository
return true; return true;
} }
@override
Future<UserDto> update(UserDto user) async { Future<UserDto> update(UserDto user) async {
await transaction(() async { await transaction(() async {
await _db.users.put(entity.User.fromDto(user)); await _db.users.put(entity.User.fromDto(user));
@@ -65,7 +57,6 @@ class IsarUserRepository extends IsarDatabaseRepository
return user; return user;
} }
@override
Future<bool> updateAll(List<UserDto> users) async { Future<bool> updateAll(List<UserDto> users) async {
await transaction(() async { await transaction(() async {
await _db.users.putAll(users.map(entity.User.fromDto).toList()); await _db.users.putAll(users.map(entity.User.fromDto).toList());

View File

@@ -1,17 +1,15 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/repositories/api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/api.repository.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class UserApiRepository extends ApiRepository implements IUserApiRepository { class UserApiRepository extends ApiRepository {
final UsersApi _api; final UsersApi _api;
const UserApiRepository(this._api); const UserApiRepository(this._api);
@override
Future<UserDto?> getMyUser() async { Future<UserDto?> getMyUser() async {
final (adminDto, preferenceDto) = final (adminDto, preferenceDto) =
await (_api.getMyUser(), _api.getMyPreferences()).wait; await (_api.getMyUser(), _api.getMyPreferences()).wait;
@@ -20,7 +18,6 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
return UserConverter.fromAdminDto(adminDto, preferenceDto); return UserConverter.fromAdminDto(adminDto, preferenceDto);
} }
@override
Future<String> createProfileImage({ Future<String> createProfileImage({
required String name, required String name,
required Uint8List data, required Uint8List data,
@@ -33,7 +30,6 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
return res.profileImagePath; return res.profileImagePath;
} }
@override
Future<List<UserDto>> getAll() async { Future<List<UserDto>> getAll() async {
final dto = await checkNull(_api.searchUsers()); final dto = await checkNull(_api.searchUsers());
return dto.map(UserConverter.fromSimpleUserDto).toList(); return dto.map(UserConverter.fromSimpleUserDto).toList();

View File

@@ -1,16 +0,0 @@
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});
}

View File

@@ -1,42 +0,0 @@
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/album.entity.dart';
abstract interface class IAlbumApiRepository {
Future<Album> get(String id);
Future<List<Album>> getAll({bool? shared});
Future<Album> create(
String name, {
required Iterable<String> assetIds,
Iterable<String> sharedUserIds = const [],
});
Future<Album> update(
String albumId, {
String? name,
String? thumbnailAssetId,
String? description,
bool? activityEnabled,
SortOrder? sortOrder,
});
Future<void> delete(String albumId);
Future<({List<String> added, List<String> duplicates})> addAssets(
String albumId,
Iterable<String> assetIds,
);
Future<({List<String> removed, List<String> failed})> removeAssets(
String albumId,
Iterable<String> assetIds,
);
Future<Album> addUsers(
String albumId,
Iterable<String> userIds,
);
Future<void> removeUser(String albumId, {required String userId});
}

View File

@@ -1,21 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IAlbumMediaRepository {
Future<List<Album>> getAll();
Future<List<String>> getAssetIds(String albumId);
Future<int> getAssetCount(String albumId);
Future<List<Asset>> getAssets(
String albumId, {
int start = 0,
int end = 0x7fffffffffffffff,
DateTime? modifiedFrom,
DateTime? modifiedUntil,
bool orderByModificationDate = false,
});
Future<Album> get(String id);
}

View File

@@ -1,6 +0,0 @@
import 'package:immich_mobile/models/auth/biometric_status.model.dart';
abstract interface class IBiometricRepository {
Future<BiometricStatus> getStatus();
Future<bool> authenticate(String? message);
}

View File

@@ -1,15 +0,0 @@
import 'package:background_downloader/background_downloader.dart';
abstract interface class IDownloadRepository {
void Function(TaskStatusUpdate)? onImageDownloadStatus;
void Function(TaskStatusUpdate)? onVideoDownloadStatus;
void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;
Future<List<TaskRecord>> getLiveVideoTasks();
Future<List<bool>> downloadAll(List<DownloadTask> tasks);
Future<bool> cancel(String id);
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);
}

View File

@@ -1,4 +0,0 @@
abstract interface class INetworkRepository {
Future<String?> getWifiName();
Future<String?> getWifiIp();
}

View File

@@ -1,8 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract class IPartnerRepository {
Future<List<UserDto>> getSharedWith();
Future<List<UserDto>> getSharedBy();
Stream<List<UserDto>> watchSharedWith();
Stream<List<UserDto>> watchSharedBy();
}

View File

@@ -1,13 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IPartnerApiRepository {
Future<List<UserDto>> getAll(Direction direction);
Future<UserDto> create(String id);
Future<UserDto> update(String id, {required bool inTimeline});
Future<void> delete(String id);
}
enum Direction {
sharedWithMe,
sharedByMe,
}

View File

@@ -1,5 +0,0 @@
abstract interface class ISecureStorageRepository {
Future<String?> read(String key);
Future<void> write(String key, String value);
Future<void> delete(String key);
}

View File

@@ -1,9 +0,0 @@
import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
abstract interface class ISessionAPIRepository {
Future<SessionCreateResponse> createSession(
String deviceName,
String deviceOS, {
int? duration,
});
}

View File

@@ -1,7 +0,0 @@
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
abstract interface class IShareHandlerRepository {
void Function(List<ShareIntentAttachment>)? onSharedMedia;
Future<void> init();
}

View File

@@ -1,39 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
abstract class ITimelineRepository {
Future<List<String>> getTimelineUserIds(String id);
Stream<List<String>> watchTimelineUsers(String id);
Stream<RenderList> watchArchiveTimeline(String userId);
Stream<RenderList> watchFavoriteTimeline(String userId);
Stream<RenderList> watchTrashTimeline(String userId);
Stream<RenderList> watchAlbumTimeline(
Album album,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchAllVideosTimeline(String userId);
Stream<RenderList> watchHomeTimeline(
String userId,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchMultiUsersTimeline(
List<String> userIds,
GroupAssetsBy groupAssetsBy,
);
Future<RenderList> getTimelineFromAssets(
List<Asset> assets,
GroupAssetsBy getGroupByOption,
);
Stream<RenderList> watchAssetSelectionTimeline(String userId);
Stream<RenderList> watchLockedTimeline(
String userId,
GroupAssetsBy groupAssetsBy,
);
}

View File

@@ -1,8 +1,8 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
class SearchLocationFilter { class SearchLocationFilter {
String? country; String? country;

View File

@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; 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/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/person.model.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/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/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';

View File

@@ -9,7 +9,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart';
@@ -108,7 +107,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
final AuthState _authState; final AuthState _authState;
final BackgroundService _backgroundService; final BackgroundService _backgroundService;
final GalleryPermissionNotifier _galleryPermissionNotifier; final GalleryPermissionNotifier _galleryPermissionNotifier;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final BackupAlbumService _backupAlbumService; final BackupAlbumService _backupAlbumService;
final Ref ref; final Ref ref;

View File

@@ -26,7 +26,7 @@ final syncApiRepositoryProvider = Provider(
); );
final syncStreamRepositoryProvider = Provider( final syncStreamRepositoryProvider = Provider(
(ref) => DriftSyncStreamRepository(ref.watch(driftProvider)), (ref) => SyncStreamRepository(ref.watch(driftProvider)),
); );
final localSyncServiceProvider = Provider( final localSyncServiceProvider = Provider(

View File

@@ -1,6 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
@@ -12,16 +10,16 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user.provider.g.dart'; part 'user.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
IUserRepository userRepository(Ref ref) => IsarUserRepository userRepository(Ref ref) =>
IsarUserRepository(ref.watch(isarProvider)); IsarUserRepository(ref.watch(isarProvider));
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
IUserApiRepository userApiRepository(Ref ref) => UserApiRepository userApiRepository(Ref ref) =>
UserApiRepository(ref.watch(apiServiceProvider).usersApi); UserApiRepository(ref.watch(apiServiceProvider).usersApi);
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
UserService userService(Ref ref) => UserService( UserService userService(Ref ref) => UserService(
userRepository: ref.watch(userRepositoryProvider), isarUserRepository: ref.watch(userRepositoryProvider),
userApiRepository: ref.watch(userApiRepositoryProvider), userApiRepository: ref.watch(userApiRepositoryProvider),
storeService: ref.watch(storeServiceProvider), storeService: ref.watch(storeServiceProvider),
); );

View File

@@ -6,11 +6,11 @@ part of 'user.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$userRepositoryHash() => r'1a2ac726bcc44397dcaecf449084fefd336696d4'; String _$userRepositoryHash() => r'538791a4ad126ed086c9db682c67fc5c654d54f3';
/// See also [userRepository]. /// See also [userRepository].
@ProviderFor(userRepository) @ProviderFor(userRepository)
final userRepositoryProvider = Provider<IUserRepository>.internal( final userRepositoryProvider = Provider<IsarUserRepository>.internal(
userRepository, userRepository,
name: r'userRepositoryProvider', name: r'userRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -22,12 +22,12 @@ final userRepositoryProvider = Provider<IUserRepository>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef UserRepositoryRef = ProviderRef<IUserRepository>; typedef UserRepositoryRef = ProviderRef<IsarUserRepository>;
String _$userApiRepositoryHash() => r'6b19f2c99fb83162a5ceb91adb8589eaae01bc92'; String _$userApiRepositoryHash() => r'8a7340ca4544c8c6b20225c65bff2abb9e96baa2';
/// See also [userApiRepository]. /// See also [userApiRepository].
@ProviderFor(userApiRepository) @ProviderFor(userApiRepository)
final userApiRepositoryProvider = Provider<IUserApiRepository>.internal( final userApiRepositoryProvider = Provider<UserApiRepository>.internal(
userApiRepository, userApiRepository,
name: r'userApiRepositoryProvider', name: r'userApiRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -39,8 +39,8 @@ final userApiRepositoryProvider = Provider<IUserApiRepository>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef UserApiRepositoryRef = ProviderRef<IUserApiRepository>; typedef UserApiRepositoryRef = ProviderRef<UserApiRepository>;
String _$userServiceHash() => r'4a0873357b7115b4d6bfa8e89b847c0b74ce0d93'; String _$userServiceHash() => r'181414dddc7891be6237e13d568c287a804228d1';
/// See also [userService]. /// See also [userService].
@ProviderFor(userService) @ProviderFor(userService)

View File

@@ -1,5 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/domain/models/person.model.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';

View File

@@ -1,6 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
@@ -10,20 +9,17 @@ final activityApiRepositoryProvider = Provider(
(ref) => ActivityApiRepository(ref.watch(apiServiceProvider).activitiesApi), (ref) => ActivityApiRepository(ref.watch(apiServiceProvider).activitiesApi),
); );
class ActivityApiRepository extends ApiRepository class ActivityApiRepository extends ApiRepository {
implements IActivityApiRepository {
final ActivitiesApi _api; final ActivitiesApi _api;
ActivityApiRepository(this._api); ActivityApiRepository(this._api);
@override
Future<List<Activity>> getAll(String albumId, {String? assetId}) async { Future<List<Activity>> getAll(String albumId, {String? assetId}) async {
final response = final response =
await checkNull(_api.getActivities(albumId, assetId: assetId)); await checkNull(_api.getActivities(albumId, assetId: assetId));
return response.map(_toActivity).toList(); return response.map(_toActivity).toList();
} }
@override
Future<Activity> create( Future<Activity> create(
String albumId, String albumId,
ActivityType type, { ActivityType type, {
@@ -42,12 +38,10 @@ class ActivityApiRepository extends ApiRepository
return _toActivity(response); return _toActivity(response);
} }
@override
Future<void> delete(String id) { Future<void> delete(String id) {
return checkNull(_api.deleteActivity(id)); return checkNull(_api.deleteActivity(id));
} }
@override
Future<ActivityStats> getStats(String albumId, {String? assetId}) async { Future<ActivityStats> getStats(String albumId, {String? assetId}) async {
final response = final response =
await checkNull(_api.getActivityStatistics(albumId, assetId: assetId)); await checkNull(_api.getActivityStatistics(albumId, assetId: assetId));

View File

@@ -5,7 +5,6 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.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/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -14,24 +13,21 @@ final albumApiRepositoryProvider = Provider(
(ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi), (ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
); );
class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository { class AlbumApiRepository extends ApiRepository {
final AlbumsApi _api; final AlbumsApi _api;
AlbumApiRepository(this._api); AlbumApiRepository(this._api);
@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
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(); return dtos.map(_toAlbum).toList();
} }
@override
Future<Album> create( Future<Album> create(
String name, { String name, {
required Iterable<String> assetIds, required Iterable<String> assetIds,
@@ -54,7 +50,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return _toAlbum(responseDto); return _toAlbum(responseDto);
} }
@override
Future<Album> update( Future<Album> update(
String albumId, { String albumId, {
String? name, String? name,
@@ -84,12 +79,10 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return _toAlbum(response); return _toAlbum(response);
} }
@override
Future<void> delete(String albumId) { Future<void> delete(String albumId) {
return _api.deleteAlbum(albumId); return _api.deleteAlbum(albumId);
} }
@override
Future<({List<String> added, List<String> duplicates})> addAssets( Future<({List<String> added, List<String> duplicates})> addAssets(
String albumId, String albumId,
Iterable<String> assetIds, Iterable<String> assetIds,
@@ -114,7 +107,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return (added: added, duplicates: duplicates); return (added: added, duplicates: duplicates);
} }
@override
Future<({List<String> removed, List<String> failed})> removeAssets( Future<({List<String> removed, List<String> failed})> removeAssets(
String albumId, String albumId,
Iterable<String> assetIds, Iterable<String> assetIds,
@@ -136,7 +128,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return (removed: removed, failed: failed); return (removed: removed, failed: failed);
} }
@override
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();
@@ -149,7 +140,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return _toAlbum(response); return _toAlbum(response);
} }
@override
Future<void> removeUser(String albumId, {required String userId}) { Future<void> removeUser(String albumId, {required String userId}) {
return _api.removeUserFromAlbum(albumId, userId); return _api.removeUserFromAlbum(albumId, userId);
} }

View File

@@ -4,14 +4,13 @@ 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:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:photo_manager/photo_manager.dart' hide AssetType; import 'package:photo_manager/photo_manager.dart' hide AssetType;
final albumMediaRepositoryProvider = final albumMediaRepositoryProvider =
Provider((ref) => const AlbumMediaRepository()); Provider((ref) => const AlbumMediaRepository());
class AlbumMediaRepository implements IAlbumMediaRepository { class AlbumMediaRepository {
const AlbumMediaRepository(); const AlbumMediaRepository();
bool get useCustomFilter => bool get useCustomFilter =>
@@ -41,7 +40,6 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
) )
: null; : null;
@override
Future<List<Album>> getAll() async { Future<List<Album>> getAll() async {
final filter = useCustomFilter final filter = useCustomFilter
? CustomFilter.sql(where: '${CustomColumns.base.width} > 0') ? CustomFilter.sql(where: '${CustomColumns.base.width} > 0')
@@ -52,7 +50,6 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
return assetPathEntities.map(_toAlbum).toList(); return assetPathEntities.map(_toAlbum).toList();
} }
@override
Future<List<String>> getAssetIds(String albumId) async { Future<List<String>> getAssetIds(String albumId) async {
final album = final album =
await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter()); await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter());
@@ -61,14 +58,12 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
return assets.map((e) => e.id).toList(); return assets.map((e) => e.id).toList();
} }
@override
Future<int> getAssetCount(String albumId) async { Future<int> getAssetCount(String albumId) async {
final album = final album =
await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter()); await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter());
return album.assetCountAsync; return album.assetCountAsync;
} }
@override
Future<List<Asset>> getAssets( Future<List<Asset>> getAssets(
String albumId, { String albumId, {
int start = 0, int start = 0,
@@ -97,12 +92,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
return assets.map(AssetMediaRepository.toAsset).toList().cast(); return assets.map(AssetMediaRepository.toAsset).toList().cast();
} }
@override Future<Album> get(String id) async {
Future<Album> get(
String id, {
DateTime? modifiedFrom,
DateTime? modifiedUntil,
}) async {
final assetPathEntity = await AssetPathEntity.fromId( final assetPathEntity = await AssetPathEntity.fromId(
id, id,
filterOption: _getAlbumFilter(containsPathModified: true), filterOption: _getAlbumFilter(containsPathModified: true),

View File

@@ -1,18 +1,16 @@
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/interfaces/biometric.interface.dart';
import 'package:immich_mobile/models/auth/biometric_status.model.dart'; import 'package:immich_mobile/models/auth/biometric_status.model.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
final biometricRepositoryProvider = final biometricRepositoryProvider =
Provider((ref) => BiometricRepository(LocalAuthentication())); Provider((ref) => BiometricRepository(LocalAuthentication()));
class BiometricRepository implements IBiometricRepository { class BiometricRepository {
final LocalAuthentication _localAuth; final LocalAuthentication _localAuth;
BiometricRepository(this._localAuth); BiometricRepository(this._localAuth);
@override
Future<BiometricStatus> getStatus() async { Future<BiometricStatus> getStatus() async {
final bool canAuthenticateWithBiometrics = final bool canAuthenticateWithBiometrics =
await _localAuth.canCheckBiometrics; await _localAuth.canCheckBiometrics;
@@ -26,7 +24,6 @@ class BiometricRepository implements IBiometricRepository {
); );
} }
@override
Future<bool> authenticate(String? message) async { Future<bool> authenticate(String? message) async {
return _localAuth.authenticate( return _localAuth.authenticate(
localizedReason: message ?? 'please_auth_to_access'.tr(), localizedReason: message ?? 'please_auth_to_access'.tr(),

View File

@@ -1,21 +1,16 @@
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/download.interface.dart';
import 'package:immich_mobile/utils/download.dart'; import 'package:immich_mobile/utils/download.dart';
final downloadRepositoryProvider = Provider((ref) => DownloadRepository()); final downloadRepositoryProvider = Provider((ref) => DownloadRepository());
class DownloadRepository implements IDownloadRepository { class DownloadRepository {
@override
void Function(TaskStatusUpdate)? onImageDownloadStatus; void Function(TaskStatusUpdate)? onImageDownloadStatus;
@override
void Function(TaskStatusUpdate)? onVideoDownloadStatus; void Function(TaskStatusUpdate)? onVideoDownloadStatus;
@override
void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus; void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus;
@override
void Function(TaskProgressUpdate)? onTaskProgress; void Function(TaskProgressUpdate)? onTaskProgress;
DownloadRepository() { DownloadRepository() {
@@ -38,22 +33,18 @@ class DownloadRepository implements IDownloadRepository {
); );
} }
@override
Future<List<bool>> downloadAll(List<DownloadTask> tasks) { Future<List<bool>> downloadAll(List<DownloadTask> tasks) {
return FileDownloader().enqueueAll(tasks); return FileDownloader().enqueueAll(tasks);
} }
@override
Future<void> deleteAllTrackingRecords() { Future<void> deleteAllTrackingRecords() {
return FileDownloader().database.deleteAllRecords(); return FileDownloader().database.deleteAllRecords();
} }
@override
Future<bool> cancel(String id) { Future<bool> cancel(String id) {
return FileDownloader().cancelTaskWithId(id); return FileDownloader().cancelTaskWithId(id);
} }
@override
Future<List<TaskRecord>> getLiveVideoTasks() { Future<List<TaskRecord>> getLiveVideoTasks() {
return FileDownloader().database.allRecordsWithStatus( return FileDownloader().database.allRecordsWithStatus(
TaskStatus.complete, TaskStatus.complete,
@@ -61,7 +52,6 @@ class DownloadRepository implements IDownloadRepository {
); );
} }
@override
Future<void> deleteRecordsWithIds(List<String> ids) { Future<void> deleteRecordsWithIds(List<String> ids) {
return FileDownloader().database.deleteRecordsWithIds(ids); return FileDownloader().database.deleteRecordsWithIds(ids);
} }

View File

@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/network.interface.dart';
import 'package:network_info_plus/network_info_plus.dart'; import 'package:network_info_plus/network_info_plus.dart';
final networkRepositoryProvider = Provider((_) { final networkRepositoryProvider = Provider((_) {
@@ -10,12 +9,11 @@ final networkRepositoryProvider = Provider((_) {
return NetworkRepository(networkInfo); return NetworkRepository(networkInfo);
}); });
class NetworkRepository implements INetworkRepository { class NetworkRepository {
final NetworkInfo _networkInfo; final NetworkInfo _networkInfo;
NetworkRepository(this._networkInfo); NetworkRepository(this._networkInfo);
@override
Future<String?> getWifiName() { Future<String?> getWifiName() {
if (Platform.isAndroid) { if (Platform.isAndroid) {
// remove quote around the return value on Android // remove quote around the return value on Android
@@ -30,7 +28,6 @@ class NetworkRepository implements INetworkRepository {
return _networkInfo.getWifiName(); return _networkInfo.getWifiName();
} }
@override
Future<String?> getWifiIp() { Future<String?> getWifiIp() {
return _networkInfo.getWifiIP(); return _networkInfo.getWifiIP();
} }

View File

@@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/repositories/database.repository.dart'; import 'package:immich_mobile/repositories/database.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
@@ -11,11 +10,9 @@ final partnerRepositoryProvider = Provider(
(ref) => PartnerRepository(ref.watch(dbProvider)), (ref) => PartnerRepository(ref.watch(dbProvider)),
); );
class PartnerRepository extends DatabaseRepository class PartnerRepository extends DatabaseRepository {
implements IPartnerRepository {
PartnerRepository(super.db); PartnerRepository(super.db);
@override
Future<List<UserDto>> getSharedBy() async { Future<List<UserDto>> getSharedBy() async {
return (await db.users return (await db.users
.filter() .filter()
@@ -26,7 +23,6 @@ class PartnerRepository extends DatabaseRepository
.toList(); .toList();
} }
@override
Future<List<UserDto>> getSharedWith() async { Future<List<UserDto>> getSharedWith() async {
return (await db.users return (await db.users
.filter() .filter()
@@ -37,13 +33,11 @@ class PartnerRepository extends DatabaseRepository
.toList(); .toList();
} }
@override
Stream<List<UserDto>> watchSharedBy() { Stream<List<UserDto>> watchSharedBy() {
return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch()) return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch())
.map((users) => users.map((u) => u.toDto()).toList()); .map((users) => users.map((u) => u.toDto()).toList());
} }
@override
Stream<List<UserDto>> watchSharedWith() { Stream<List<UserDto>> watchSharedWith() {
return (db.users return (db.users
.filter() .filter()

View File

@@ -1,24 +1,26 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
enum Direction {
sharedWithMe,
sharedByMe,
}
final partnerApiRepositoryProvider = Provider( final partnerApiRepositoryProvider = Provider(
(ref) => PartnerApiRepository( (ref) => PartnerApiRepository(
ref.watch(apiServiceProvider).partnersApi, ref.watch(apiServiceProvider).partnersApi,
), ),
); );
class PartnerApiRepository extends ApiRepository class PartnerApiRepository extends ApiRepository {
implements IPartnerApiRepository {
final PartnersApi _api; final PartnersApi _api;
PartnerApiRepository(this._api); PartnerApiRepository(this._api);
@override
Future<List<UserDto>> getAll(Direction direction) async { Future<List<UserDto>> getAll(Direction direction) async {
final response = await checkNull( final response = await checkNull(
_api.getPartners( _api.getPartners(
@@ -30,16 +32,13 @@ class PartnerApiRepository extends ApiRepository
return response.map(UserConverter.fromPartnerDto).toList(); return response.map(UserConverter.fromPartnerDto).toList();
} }
@override
Future<UserDto> create(String id) async { Future<UserDto> create(String id) async {
final dto = await checkNull(_api.createPartner(id)); final dto = await checkNull(_api.createPartner(id));
return UserConverter.fromPartnerDto(dto); return UserConverter.fromPartnerDto(dto);
} }
@override
Future<void> delete(String id) => _api.removePartner(id); Future<void> delete(String id) => _api.removePartner(id);
@override
Future<UserDto> update(String id, {required bool inTimeline}) async { Future<UserDto> update(String id, {required bool inTimeline}) async {
final dto = await checkNull( final dto = await checkNull(
_api.updatePartner( _api.updatePartner(

View File

@@ -1,5 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -8,19 +8,16 @@ final personApiRepositoryProvider = Provider(
(ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi), (ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi),
); );
class PersonApiRepository extends ApiRepository class PersonApiRepository extends ApiRepository {
implements IPersonApiRepository {
final PeopleApi _api; final PeopleApi _api;
PersonApiRepository(this._api); PersonApiRepository(this._api);
@override
Future<List<Person>> getAll() async { Future<List<Person>> getAll() async {
final dto = await checkNull(_api.getAllPeople()); final dto = await checkNull(_api.getAllPeople());
return dto.people.map(_toPerson).toList(); return dto.people.map(_toPerson).toList();
} }
@override
Future<Person> update(String id, {String? name}) async { Future<Person> update(String id, {String? name}) async {
final dto = await checkNull( final dto = await checkNull(
_api.updatePerson(id, PersonUpdateDto(name: name)), _api.updatePerson(id, PersonUpdateDto(name: name)),

View File

@@ -1,26 +1,22 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/secure_storage.interface.dart';
final secureStorageRepositoryProvider = final secureStorageRepositoryProvider =
Provider((ref) => SecureStorageRepository(const FlutterSecureStorage())); Provider((ref) => SecureStorageRepository(const FlutterSecureStorage()));
class SecureStorageRepository implements ISecureStorageRepository { class SecureStorageRepository {
final FlutterSecureStorage _secureStorage; final FlutterSecureStorage _secureStorage;
SecureStorageRepository(this._secureStorage); SecureStorageRepository(this._secureStorage);
@override
Future<String?> read(String key) { Future<String?> read(String key) {
return _secureStorage.read(key: key); return _secureStorage.read(key: key);
} }
@override
Future<void> write(String key, String value) { Future<void> write(String key, String value) {
return _secureStorage.write(key: key, value: value); return _secureStorage.write(key: key, value: value);
} }
@override
Future<void> delete(String key) { Future<void> delete(String key) {
return _secureStorage.delete(key: key); return _secureStorage.delete(key: key);
} }

View File

@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/sessions_api.interface.dart';
import 'package:immich_mobile/models/sessions/session_create_response.model.dart'; import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
@@ -11,13 +10,11 @@ final sessionsAPIRepositoryProvider = Provider(
), ),
); );
class SessionsAPIRepository extends ApiRepository class SessionsAPIRepository extends ApiRepository {
implements ISessionAPIRepository {
final SessionsApi _api; final SessionsApi _api;
SessionsAPIRepository(this._api); SessionsAPIRepository(this._api);
@override
Future<SessionCreateResponse> createSession( Future<SessionCreateResponse> createSession(
String deviceType, String deviceType,
String deviceOS, { String deviceOS, {

View File

@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/share_handler.interface.dart';
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
import 'package:share_handler/share_handler.dart'; import 'package:share_handler/share_handler.dart';
@@ -9,13 +8,11 @@ final shareHandlerRepositoryProvider = Provider(
(ref) => ShareHandlerRepository(), (ref) => ShareHandlerRepository(),
); );
class ShareHandlerRepository implements IShareHandlerRepository { class ShareHandlerRepository {
ShareHandlerRepository(); ShareHandlerRepository();
@override
void Function(List<ShareIntentAttachment> attachments)? onSharedMedia; void Function(List<ShareIntentAttachment> attachments)? onSharedMedia;
@override
Future<void> init() async { Future<void> init() async {
final handler = ShareHandlerPlatform.instance; final handler = ShareHandlerPlatform.instance;
final media = await handler.getInitialSharedMedia(); final media = await handler.getInitialSharedMedia();

View File

@@ -3,7 +3,6 @@ import 'package:immich_mobile/constants/enums.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/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/timeline.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/repositories/database.repository.dart'; import 'package:immich_mobile/repositories/database.repository.dart';
import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/utils/hash.dart';
@@ -13,11 +12,9 @@ import 'package:isar/isar.dart';
final timelineRepositoryProvider = final timelineRepositoryProvider =
Provider((ref) => TimelineRepository(ref.watch(dbProvider))); Provider((ref) => TimelineRepository(ref.watch(dbProvider)));
class TimelineRepository extends DatabaseRepository class TimelineRepository extends DatabaseRepository {
implements ITimelineRepository {
TimelineRepository(super.db); TimelineRepository(super.db);
@override
Future<List<String>> getTimelineUserIds(String id) { Future<List<String>> getTimelineUserIds(String id) {
return db.users return db.users
.filter() .filter()
@@ -28,7 +25,6 @@ class TimelineRepository extends DatabaseRepository
.findAll(); .findAll();
} }
@override
Stream<List<String>> watchTimelineUsers(String id) { Stream<List<String>> watchTimelineUsers(String id) {
return db.users return db.users
.filter() .filter()
@@ -39,7 +35,6 @@ class TimelineRepository extends DatabaseRepository
.watch(); .watch();
} }
@override
Stream<RenderList> watchArchiveTimeline(String userId) { Stream<RenderList> watchArchiveTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -52,7 +47,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchFavoriteTimeline(String userId) { Stream<RenderList> watchFavoriteTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -67,7 +61,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchAlbumTimeline( Stream<RenderList> watchAlbumTimeline(
Album album, Album album,
GroupAssetsBy groupAssetByOption, GroupAssetsBy groupAssetByOption,
@@ -86,7 +79,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(withSortedOption, groupAssetByOption); return _watchRenderList(withSortedOption, groupAssetByOption);
} }
@override
Stream<RenderList> watchTrashTimeline(String userId) { Stream<RenderList> watchTrashTimeline(String userId) {
final query = db.assets final query = db.assets
.filter() .filter()
@@ -97,7 +89,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchAllVideosTimeline(String userId) { Stream<RenderList> watchAllVideosTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -111,7 +102,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchHomeTimeline( Stream<RenderList> watchHomeTimeline(
String userId, String userId,
GroupAssetsBy groupAssetByOption, GroupAssetsBy groupAssetByOption,
@@ -128,7 +118,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, groupAssetByOption); return _watchRenderList(query, groupAssetByOption);
} }
@override
Stream<RenderList> watchMultiUsersTimeline( Stream<RenderList> watchMultiUsersTimeline(
List<String> userIds, List<String> userIds,
GroupAssetsBy groupAssetByOption, GroupAssetsBy groupAssetByOption,
@@ -145,7 +134,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, groupAssetByOption); return _watchRenderList(query, groupAssetByOption);
} }
@override
Future<RenderList> getTimelineFromAssets( Future<RenderList> getTimelineFromAssets(
List<Asset> assets, List<Asset> assets,
GroupAssetsBy getGroupByOption, GroupAssetsBy getGroupByOption,
@@ -153,7 +141,6 @@ class TimelineRepository extends DatabaseRepository
return RenderList.fromAssets(assets, getGroupByOption); return RenderList.fromAssets(assets, getGroupByOption);
} }
@override
Stream<RenderList> watchAssetSelectionTimeline(String userId) { Stream<RenderList> watchAssetSelectionTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -168,7 +155,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchLockedTimeline( Stream<RenderList> watchLockedTimeline(
String userId, String userId,
GroupAssetsBy getGroupByOption, GroupAssetsBy getGroupByOption,

View File

@@ -1,10 +1,10 @@
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/repositories/activity_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class ActivityService with ErrorLoggerMixin { class ActivityService with ErrorLoggerMixin {
final IActivityApiRepository _activityApiRepository; final ActivityApiRepository _activityApiRepository;
@override @override
final Logger logger = Logger("ActivityService"); final Logger logger = Logger("ActivityService");

View File

@@ -14,8 +14,6 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart';
@@ -51,8 +49,8 @@ class AlbumService {
final IAlbumRepository _albumRepository; final IAlbumRepository _albumRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IBackupAlbumRepository _backupAlbumRepository; final IBackupAlbumRepository _backupAlbumRepository;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IAlbumApiRepository _albumApiRepository; final AlbumApiRepository _albumApiRepository;
final Logger _log = Logger('AlbumService'); final Logger _log = Logger('AlbumService');
Completer<bool> _localCompleter = Completer()..complete(false); Completer<bool> _localCompleter = Completer()..complete(false);
Completer<bool> _remoteCompleter = Completer()..complete(false); Completer<bool> _remoteCompleter = Completer()..complete(false);

View File

@@ -5,11 +5,11 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart';
@@ -53,7 +53,7 @@ class AssetService {
final IAssetApiRepository _assetApiRepository; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository; final IExifInfoRepository _exifInfoRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final IETagRepository _etagRepository; final IETagRepository _etagRepository;
final IBackupAlbumRepository _backupRepository; final IBackupAlbumRepository _backupRepository;
final ApiService _apiService; final ApiService _apiService;
@@ -68,7 +68,7 @@ class AssetService {
this._assetApiRepository, this._assetApiRepository,
this._assetRepository, this._assetRepository,
this._exifInfoRepository, this._exifInfoRepository,
this._userRepository, this._isarUserRepository,
this._etagRepository, this._etagRepository,
this._backupRepository, this._backupRepository,
this._apiService, this._apiService,
@@ -85,7 +85,9 @@ class AssetService {
final syncedUserIds = await _etagRepository.getAllIds(); final syncedUserIds = await _etagRepository.getAllIds();
final List<UserDto> syncedUsers = syncedUserIds.isEmpty final List<UserDto> syncedUsers = syncedUserIds.isEmpty
? [] ? []
: (await _userRepository.getByUserIds(syncedUserIds)).nonNulls.toList(); : (await _isarUserRepository.getByUserIds(syncedUserIds))
.nonNulls
.toList();
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
final bool changes = await _syncService.syncRemoteAssetsToDb( final bool changes = await _syncService.syncRemoteAssetsToDb(
users: syncedUsers, users: syncedUsers,

View File

@@ -11,7 +11,6 @@ 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/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
@@ -52,7 +51,7 @@ class BackupService {
final Logger _log = Logger("BackupService"); final Logger _log = Logger("BackupService");
final AppSettingsService _appSetting; final AppSettingsService _appSetting;
final AlbumService _albumService; final AlbumService _albumService;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IAssetMediaRepository _assetMediaRepository; final IAssetMediaRepository _assetMediaRepository;

View File

@@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.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:immich_mobile/interfaces/download.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:immich_mobile/repositories/download.repository.dart';
@@ -23,7 +22,7 @@ final downloadServiceProvider = Provider(
); );
class DownloadService { class DownloadService {
final IDownloadRepository _downloadRepository; final DownloadRepository _downloadRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final Logger _log = Logger("DownloadService"); final Logger _log = Logger("DownloadService");
void Function(TaskStatusUpdate)? onImageDownloadStatus; void Function(TaskStatusUpdate)? onImageDownloadStatus;

View File

@@ -1,24 +1,24 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
class EntityService { class EntityService {
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
EntityService( EntityService(
this._assetRepository, this._assetRepository,
this._userRepository, this._isarUserRepository,
); );
Future<Album> fillAlbumWithDatabaseEntities(Album album) async { Future<Album> fillAlbumWithDatabaseEntities(Album album) async {
final ownerId = album.ownerId; final ownerId = album.ownerId;
if (ownerId != null) { if (ownerId != null) {
// replace owner with user from database // replace owner with user from database
final user = await _userRepository.getByUserId(ownerId); final user = await _isarUserRepository.getByUserId(ownerId);
album.owner.value = user == null ? null : User.fromDto(user); album.owner.value = user == null ? null : User.fromDto(user);
} }
final thumbnailAssetId = final thumbnailAssetId =
@@ -30,7 +30,7 @@ class EntityService {
} }
if (album.remoteUsers.isNotEmpty) { if (album.remoteUsers.isNotEmpty) {
// replace all users with users from database // replace all users with users from database
final users = await _userRepository final users = await _isarUserRepository
.getByUserIds(album.remoteUsers.map((user) => user.id).toList()); .getByUserIds(album.remoteUsers.map((user) => user.id).toList());
album.sharedUsers.clear(); album.sharedUsers.clear();
album.sharedUsers.addAll(users.nonNulls.map(User.fromDto)); album.sharedUsers.addAll(users.nonNulls.map(User.fromDto));

View File

@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/biometric.interface.dart';
import 'package:immich_mobile/models/auth/biometric_status.model.dart'; import 'package:immich_mobile/models/auth/biometric_status.model.dart';
import 'package:immich_mobile/repositories/biometric.repository.dart'; import 'package:immich_mobile/repositories/biometric.repository.dart';
@@ -10,9 +9,7 @@ final localAuthServiceProvider = Provider(
); );
class LocalAuthService { class LocalAuthService {
// final _log = Logger("LocalAuthService"); final BiometricRepository _biometricRepository;
final IBiometricRepository _biometricRepository;
LocalAuthService(this._biometricRepository); LocalAuthService(this._biometricRepository);

View File

@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/network.interface.dart';
import 'package:immich_mobile/repositories/network.repository.dart'; import 'package:immich_mobile/repositories/network.repository.dart';
import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/repositories/permission.repository.dart';
@@ -11,7 +10,7 @@ final networkServiceProvider = Provider((ref) {
}); });
class NetworkService { class NetworkService {
final INetworkRepository _repository; final NetworkRepository _repository;
final IPermissionRepository _permissionRepository; final IPermissionRepository _permissionRepository;
NetworkService(this._repository, this._permissionRepository); NetworkService(this._repository, this._permissionRepository);

View File

@@ -1,8 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/partner.repository.dart'; import 'package:immich_mobile/repositories/partner.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
@@ -17,14 +15,14 @@ final partnerServiceProvider = Provider(
); );
class PartnerService { class PartnerService {
final IPartnerApiRepository _partnerApiRepository; final PartnerApiRepository _partnerApiRepository;
final IPartnerRepository _partnerRepository; final PartnerRepository _partnerRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final Logger _log = Logger("PartnerService"); final Logger _log = Logger("PartnerService");
PartnerService( PartnerService(
this._partnerApiRepository, this._partnerApiRepository,
this._userRepository, this._isarUserRepository,
this._partnerRepository, this._partnerRepository,
); );
@@ -47,7 +45,8 @@ class PartnerService {
Future<bool> removePartner(UserDto partner) async { Future<bool> removePartner(UserDto partner) async {
try { try {
await _partnerApiRepository.delete(partner.id); await _partnerApiRepository.delete(partner.id);
await _userRepository.update(partner.copyWith(isPartnerSharedBy: false)); await _isarUserRepository
.update(partner.copyWith(isPartnerSharedBy: false));
} 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;
@@ -58,7 +57,8 @@ class PartnerService {
Future<bool> addPartner(UserDto partner) async { Future<bool> addPartner(UserDto partner) async {
try { try {
await _partnerApiRepository.create(partner.id); await _partnerApiRepository.create(partner.id);
await _userRepository.update(partner.copyWith(isPartnerSharedBy: true)); await _isarUserRepository
.update(partner.copyWith(isPartnerSharedBy: true));
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);
@@ -75,7 +75,7 @@ class PartnerService {
partner.id, partner.id,
inTimeline: inTimeline, inTimeline: inTimeline,
); );
await _userRepository await _isarUserRepository
.update(partner.copyWith(inTimeline: dto.inTimeline)); .update(partner.copyWith(inTimeline: dto.inTimeline));
return true; return true;
} catch (e) { } catch (e) {

View File

@@ -1,8 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/person.model.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/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/person_api.repository.dart'; import 'package:immich_mobile/repositories/person_api.repository.dart';
@@ -20,7 +20,7 @@ PersonService personService(Ref ref) => PersonService(
class PersonService { class PersonService {
final Logger _log = Logger("PersonService"); final Logger _log = Logger("PersonService");
final IPersonApiRepository _personApiRepository; final PersonApiRepository _personApiRepository;
final IAssetApiRepository _assetApiRepository; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;

View File

@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/secure_storage.interface.dart';
import 'package:immich_mobile/repositories/secure_storage.repository.dart'; import 'package:immich_mobile/repositories/secure_storage.repository.dart';
final secureStorageServiceProvider = Provider( final secureStorageServiceProvider = Provider(
@@ -9,9 +8,7 @@ final secureStorageServiceProvider = Provider(
); );
class SecureStorageService { class SecureStorageService {
// final _log = Logger("LocalAuthService"); final SecureStorageRepository _secureStorageRepository;
final ISecureStorageRepository _secureStorageRepository;
SecureStorageService(this._secureStorageRepository); SecureStorageService(this._secureStorageRepository);

View File

@@ -3,23 +3,20 @@ import 'dart:io';
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:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.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/etag.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart'; import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
@@ -63,17 +60,17 @@ final syncServiceProvider = Provider(
class SyncService { class SyncService {
final HashService _hashService; final HashService _hashService;
final EntityService _entityService; final EntityService _entityService;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IAlbumApiRepository _albumApiRepository; final AlbumApiRepository _albumApiRepository;
final IAlbumRepository _albumRepository; final IAlbumRepository _albumRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository; final IExifInfoRepository _exifInfoRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final UserService _userService; final UserService _userService;
final IPartnerRepository _partnerRepository; final PartnerRepository _partnerRepository;
final IETagRepository _eTagRepository; final IETagRepository _eTagRepository;
final IPartnerApiRepository _partnerApiRepository; final PartnerApiRepository _partnerApiRepository;
final IUserApiRepository _userApiRepository; final UserApiRepository _userApiRepository;
final AsyncMutex _lock = AsyncMutex(); final AsyncMutex _lock = AsyncMutex();
final Logger _log = Logger('SyncService'); final Logger _log = Logger('SyncService');
final AppSettingsService _appSettingsService; final AppSettingsService _appSettingsService;
@@ -88,7 +85,7 @@ class SyncService {
this._assetRepository, this._assetRepository,
this._exifInfoRepository, this._exifInfoRepository,
this._partnerRepository, this._partnerRepository,
this._userRepository, this._isarUserRepository,
this._userService, this._userService,
this._eTagRepository, this._eTagRepository,
this._appSettingsService, this._appSettingsService,
@@ -165,7 +162,7 @@ class SyncService {
/// Returns `true`if there were any changes /// Returns `true`if there were any changes
Future<bool> _syncUsersFromServer(List<UserDto> users) async { Future<bool> _syncUsersFromServer(List<UserDto> users) async {
users.sortBy((u) => u.id); users.sortBy((u) => u.id);
final dbUsers = await _userRepository.getAll(sortBy: SortUserBy.id); final dbUsers = await _isarUserRepository.getAll(sortBy: SortUserBy.id);
final List<String> toDelete = []; final List<String> toDelete = [];
final List<UserDto> toUpsert = []; final List<UserDto> toUpsert = [];
final changes = diffSortedListsSync( final changes = diffSortedListsSync(
@@ -186,9 +183,9 @@ class SyncService {
onlySecond: (UserDto b) => toDelete.add(b.id), onlySecond: (UserDto b) => toDelete.add(b.id),
); );
if (changes) { if (changes) {
await _userRepository.transaction(() async { await _isarUserRepository.transaction(() async {
await _userRepository.delete(toDelete); await _isarUserRepository.delete(toDelete);
await _userRepository.updateAll(toUpsert); await _isarUserRepository.updateAll(toUpsert);
}); });
} }
return changes; return changes;
@@ -448,7 +445,7 @@ class SyncService {
final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd); final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
await upsertAssetsWithExif(updated); await upsertAssetsWithExif(updated);
final assetsToLink = existingInDb + updated; final assetsToLink = existingInDb + updated;
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd); final usersToLink = await _isarUserRepository.getByUserIds(userIdsToAdd);
album.name = dto.name; album.name = dto.name;
album.description = dto.description; album.description = dto.description;

View File

@@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.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/interfaces/timeline.interface.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/timeline.repository.dart'; import 'package:immich_mobile/repositories/timeline.repository.dart';
@@ -18,7 +17,7 @@ final timelineServiceProvider = Provider<TimelineService>((ref) {
}); });
class TimelineService { class TimelineService {
final ITimelineRepository _timelineRepository; final TimelineRepository _timelineRepository;
final AppSettingsService _appSettingsService; final AppSettingsService _appSettingsService;
final UserService _userService; final UserService _userService;

View File

@@ -57,7 +57,13 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
} }
final shouldTruncate = version < 8 || version < targetVersion; final shouldTruncate = version < 8 || version < targetVersion;
if (shouldTruncate) { if (shouldTruncate) {
if (targetVersion == 12) {
await Store.put(StoreKey.version, targetVersion);
return;
}
await _migrateTo(db, targetVersion); await _migrateTo(db, targetVersion);
} }
} }

View File

@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; 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/domain/models/person.model.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/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.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';

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.135.0 - API version: 1.135.2
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 1.135.0+201 version: 1.135.2+203
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'

View File

@@ -4,9 +4,9 @@ import 'dart:async';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/domain/services/sync_stream.service.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import '../../fixtures/sync_stream.stub.dart'; import '../../fixtures/sync_stream.stub.dart';
@@ -30,7 +30,7 @@ class _MockCancellationWrapper extends Mock implements _CancellationWrapper {}
void main() { void main() {
late SyncStreamService sut; late SyncStreamService sut;
late ISyncStreamRepository mockSyncStreamRepo; late SyncStreamRepository mockSyncStreamRepo;
late ISyncApiRepository mockSyncApiRepo; late ISyncApiRepository mockSyncApiRepo;
late Function(List<SyncEvent>, Function()) handleEventsCallback; late Function(List<SyncEvent>, Function()) handleEventsCallback;
late _MockAbortCallbackWrapper mockAbortCallbackWrapper; late _MockAbortCallbackWrapper mockAbortCallbackWrapper;

View File

@@ -1,11 +1,11 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import '../../fixtures/user.stub.dart'; import '../../fixtures/user.stub.dart';
@@ -14,16 +14,16 @@ import '../service.mock.dart';
void main() { void main() {
late UserService sut; late UserService sut;
late IUserRepository mockUserRepo; late IsarUserRepository mockUserRepo;
late IUserApiRepository mockUserApiRepo; late UserApiRepository mockUserApiRepo;
late StoreService mockStoreService; late StoreService mockStoreService;
setUp(() { setUp(() {
mockUserRepo = MockUserRepository(); mockUserRepo = MockIsarUserRepository();
mockUserApiRepo = MockUserApiRepository(); mockUserApiRepo = MockUserApiRepository();
mockStoreService = MockStoreService(); mockStoreService = MockStoreService();
sut = UserService( sut = UserService(
userRepository: mockUserRepo, isarUserRepository: mockUserRepo,
userApiRepository: mockUserApiRepo, userApiRepository: mockUserApiRepo,
storeService: mockStoreService, storeService: mockStoreService,
); );

View File

@@ -5,21 +5,21 @@ import 'package:immich_mobile/domain/interfaces/log.interface.dart';
import 'package:immich_mobile/domain/interfaces/storage.interface.dart'; import 'package:immich_mobile/domain/interfaces/storage.interface.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
class MockStoreRepository extends Mock implements IStoreRepository {} class MockStoreRepository extends Mock implements IStoreRepository {}
class MockLogRepository extends Mock implements ILogRepository {} class MockLogRepository extends Mock implements ILogRepository {}
class MockUserRepository extends Mock implements IUserRepository {} class MockIsarUserRepository extends Mock implements IsarUserRepository {}
class MockDeviceAssetRepository extends Mock class MockDeviceAssetRepository extends Mock
implements IDeviceAssetRepository {} implements IDeviceAssetRepository {}
class MockSyncStreamRepository extends Mock implements ISyncStreamRepository {} class MockSyncStreamRepository extends Mock implements SyncStreamRepository {}
class MockLocalAlbumRepository extends Mock implements ILocalAlbumRepository {} class MockLocalAlbumRepository extends Mock implements ILocalAlbumRepository {}
@@ -28,6 +28,6 @@ class MockLocalAssetRepository extends Mock implements ILocalAssetRepository {}
class MockStorageRepository extends Mock implements IStorageRepository {} class MockStorageRepository extends Mock implements IStorageRepository {}
// API Repos // API Repos
class MockUserApiRepository extends Mock implements IUserApiRepository {} class MockUserApiRepository extends Mock implements UserApiRepository {}
class MockSyncApiRepository extends Mock implements ISyncApiRepository {} class MockSyncApiRepository extends Mock implements ISyncApiRepository {}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/domain/services/log.service.dart';
@@ -11,7 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -55,7 +55,7 @@ void main() {
final MockAlbumRepository albumRepository = MockAlbumRepository(); final MockAlbumRepository albumRepository = MockAlbumRepository();
final MockAssetRepository assetRepository = MockAssetRepository(); final MockAssetRepository assetRepository = MockAssetRepository();
final MockExifInfoRepository exifInfoRepository = MockExifInfoRepository(); final MockExifInfoRepository exifInfoRepository = MockExifInfoRepository();
final MockUserRepository userRepository = MockUserRepository(); final MockIsarUserRepository userRepository = MockIsarUserRepository();
final MockETagRepository eTagRepository = MockETagRepository(); final MockETagRepository eTagRepository = MockETagRepository();
final MockAlbumMediaRepository albumMediaRepository = final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository(); MockAlbumMediaRepository();

View File

@@ -1,7 +1,5 @@
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart';
@@ -11,8 +9,10 @@ import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart'; import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/album_api.repository.dart';
import 'package:immich_mobile/repositories/partner.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
class MockAlbumRepository extends Mock implements IAlbumRepository {} class MockAlbumRepository extends Mock implements IAlbumRepository {}
@@ -25,7 +25,7 @@ class MockExifInfoRepository extends Mock implements IExifInfoRepository {}
class MockETagRepository extends Mock implements IETagRepository {} class MockETagRepository extends Mock implements IETagRepository {}
class MockAlbumMediaRepository extends Mock implements IAlbumMediaRepository {} class MockAlbumMediaRepository extends Mock implements AlbumMediaRepository {}
class MockBackupAlbumRepository extends Mock class MockBackupAlbumRepository extends Mock
implements IBackupAlbumRepository {} implements IBackupAlbumRepository {}
@@ -36,15 +36,15 @@ class MockAssetMediaRepository extends Mock implements IAssetMediaRepository {}
class MockFileMediaRepository extends Mock implements IFileMediaRepository {} class MockFileMediaRepository extends Mock implements IFileMediaRepository {}
class MockAlbumApiRepository extends Mock implements IAlbumApiRepository {} class MockAlbumApiRepository extends Mock implements AlbumApiRepository {}
class MockAuthApiRepository extends Mock implements IAuthApiRepository {} class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
class MockAuthRepository extends Mock implements IAuthRepository {} class MockAuthRepository extends Mock implements IAuthRepository {}
class MockPartnerRepository extends Mock implements IPartnerRepository {} class MockPartnerRepository extends Mock implements PartnerRepository {}
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {} class MockPartnerApiRepository extends Mock implements PartnerApiRepository {}
class MockLocalFilesManagerRepository extends Mock class MockLocalFilesManagerRepository extends Mock
implements ILocalFilesManager {} implements ILocalFilesManager {}

View File

@@ -22,7 +22,7 @@ void main() {
late MockExifInfoRepository exifInfoRepository; late MockExifInfoRepository exifInfoRepository;
late MockETagRepository eTagRepository; late MockETagRepository eTagRepository;
late MockBackupAlbumRepository backupAlbumRepository; late MockBackupAlbumRepository backupAlbumRepository;
late MockUserRepository userRepository; late MockIsarUserRepository userRepository;
late MockAssetMediaRepository assetMediaRepository; late MockAssetMediaRepository assetMediaRepository;
late MockApiService apiService; late MockApiService apiService;
@@ -35,7 +35,7 @@ void main() {
assetRepository = MockAssetRepository(); assetRepository = MockAssetRepository();
assetApiRepository = MockAssetApiRepository(); assetApiRepository = MockAssetApiRepository();
exifInfoRepository = MockExifInfoRepository(); exifInfoRepository = MockExifInfoRepository();
userRepository = MockUserRepository(); userRepository = MockIsarUserRepository();
eTagRepository = MockETagRepository(); eTagRepository = MockETagRepository();
backupAlbumRepository = MockBackupAlbumRepository(); backupAlbumRepository = MockBackupAlbumRepository();
apiService = MockApiService(); apiService = MockApiService();

View File

@@ -12,11 +12,11 @@ import '../repository.mocks.dart';
void main() { void main() {
late EntityService sut; late EntityService sut;
late MockAssetRepository assetRepository; late MockAssetRepository assetRepository;
late MockUserRepository userRepository; late MockIsarUserRepository userRepository;
setUp(() { setUp(() {
assetRepository = MockAssetRepository(); assetRepository = MockAssetRepository();
userRepository = MockUserRepository(); userRepository = MockIsarUserRepository();
sut = EntityService(assetRepository, userRepository); sut = EntityService(assetRepository, userRepository);
}); });

View File

@@ -8503,7 +8503,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.135.0", "version": "1.135.2",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],

View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.2",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",

View File

@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.135.0 * 1.135.2
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */

View File

@@ -1,12 +1,12 @@
{ {
"name": "immich", "name": "immich",
"version": "1.135.0", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.135.0", "version": "1.135.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -42,7 +42,6 @@
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0", "i18n-iso-countries": "^7.6.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"joi": "^17.10.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kysely": "^0.28.0", "kysely": "^0.28.0",
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",
@@ -1349,21 +1348,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"license": "BSD-3-Clause"
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -5052,27 +5036,6 @@
"url": "https://ko-fi.com/killymxi" "url": "https://ko-fi.com/killymxi"
} }
}, },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
"license": "BSD-3-Clause"
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
"node_modules/@socket.io/component-emitter": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@@ -11333,19 +11296,6 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/joi": {
"version": "17.13.3",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.3.0",
"@hapi/topo": "^5.1.0",
"@sideway/address": "^4.1.5",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jose": { "node_modules/jose": {
"version": "6.0.10", "version": "6.0.10",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.135.0", "version": "1.135.2",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -68,7 +68,6 @@
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0", "i18n-iso-countries": "^7.6.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"joi": "^17.10.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kysely": "^0.28.0", "kysely": "^0.28.0",
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",

View File

@@ -224,7 +224,7 @@ limit
with with
"assets" as ( "assets" as (
select select
date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' as "timeBucket" date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' as "timeBucket"
from from
"assets" "assets"
where where
@@ -232,7 +232,7 @@ with
and "assets"."visibility" in ('archive', 'timeline') and "assets"."visibility" in ('archive', 'timeline')
) )
select select
"timeBucket"::date::text as "timeBucket", ("timeBucket" AT TIME ZONE 'UTC')::date::text as "timeBucket",
count(*) as "count" count(*) as "count"
from from
"assets" "assets"
@@ -300,7 +300,7 @@ with
where where
"assets"."deletedAt" is null "assets"."deletedAt" is null
and "assets"."visibility" in ('archive', 'timeline') and "assets"."visibility" in ('archive', 'timeline')
and date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' = $2 and date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' = $2
and not exists ( and not exists (
select select
from from

View File

@@ -12,6 +12,38 @@ delete from "person"
where where
"person"."id" in ($1) "person"."id" in ($1)
-- PersonRepository.getAllForUser
select
"person".*
from
"person"
inner join "asset_faces" on "asset_faces"."personId" = "person"."id"
inner join "assets" on "asset_faces"."assetId" = "assets"."id"
and "assets"."visibility" = 'timeline'
and "assets"."deletedAt" is null
where
"person"."ownerId" = $1
and "asset_faces"."deletedAt" is null
and "person"."isHidden" = $2
group by
"person"."id"
having
(
"person"."name" != $3
or count("asset_faces"."assetId") >= $4
)
order by
"person"."isHidden" asc,
"person"."isFavorite" desc,
NULLIF(person.name, '') is null asc,
count("asset_faces"."assetId") desc,
NULLIF(person.name, '') asc nulls last,
"person"."createdAt"
limit
$5
offset
$6
-- PersonRepository.getAllWithoutFaces -- PersonRepository.getAllWithoutFaces
select select
"person".* "person".*

View File

@@ -130,7 +130,7 @@ from
where where
"ownerId" = $1 "ownerId" = $1
and "updatedAt" < now() - interval '1 millisecond' and "updatedAt" < now() - interval '1 millisecond'
and "updateId" < $2 and "updateId" <= $2
and "updateId" >= $3 and "updateId" >= $3
order by order by
"updateId" asc "updateId" asc
@@ -274,7 +274,7 @@ from
where where
"assets"."ownerId" = $1 "assets"."ownerId" = $1
and "exif"."updatedAt" < now() - interval '1 millisecond' and "exif"."updatedAt" < now() - interval '1 millisecond'
and "exif"."updateId" < $2 and "exif"."updateId" <= $2
and "exif"."updateId" >= $3 and "exif"."updateId" >= $3
order by order by
"exif"."updateId" asc "exif"."updateId" asc
@@ -418,7 +418,7 @@ from
where where
"albumsId" = $1 "albumsId" = $1
and "updatedAt" < now() - interval '1 millisecond' and "updatedAt" < now() - interval '1 millisecond'
and "updateId" < $2 and "updateId" <= $2
and "updateId" >= $3 and "updateId" >= $3
order by order by
"updateId" asc "updateId" asc

View File

@@ -42,11 +42,6 @@ interface LivePhotoSearchOptions {
type: AssetType; type: AssetType;
} }
export enum TimeBucketSize {
DAY = 'DAY',
MONTH = 'MONTH',
}
interface AssetBuilderOptions { interface AssetBuilderOptions {
isFavorite?: boolean; isFavorite?: boolean;
isTrashed?: boolean; isTrashed?: boolean;
@@ -490,13 +485,13 @@ export class AssetRepository {
.execute(); .execute();
} }
@GenerateSql({ params: [{ size: TimeBucketSize.MONTH }] }) @GenerateSql({ params: [{}] })
async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> { async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
return this.db return this.db
.with('assets', (qb) => .with('assets', (qb) =>
qb qb
.selectFrom('assets') .selectFrom('assets')
.select(truncatedDate<Date>(TimeBucketSize.MONTH).as('timeBucket')) .select(truncatedDate<Date>().as('timeBucket'))
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility === undefined, withDefaultVisibility) .$if(options.visibility === undefined, withDefaultVisibility)
@@ -525,7 +520,7 @@ export class AssetRepository {
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)), .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
) )
.selectFrom('assets') .selectFrom('assets')
.select(sql<string>`"timeBucket"::date::text`.as('timeBucket')) .select(sql<string>`("timeBucket" AT TIME ZONE 'UTC')::date::text`.as('timeBucket'))
.select((eb) => eb.fn.countAll<number>().as('count')) .select((eb) => eb.fn.countAll<number>().as('count'))
.groupBy('timeBucket') .groupBy('timeBucket')
.orderBy('timeBucket', options.order ?? 'desc') .orderBy('timeBucket', options.order ?? 'desc')
@@ -576,7 +571,7 @@ export class AssetRepository {
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility == undefined, withDefaultVisibility) .$if(options.visibility == undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!)) .$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.where(truncatedDate(TimeBucketSize.MONTH), '=', timeBucket.replace(/^[+-]/, '')) .where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, ''))
.$if(!!options.albumId, (qb) => .$if(!!options.albumId, (qb) =>
qb.where((eb) => qb.where((eb) =>
eb.exists( eb.exists(

View File

@@ -119,8 +119,6 @@ export class DatabaseRepository {
await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db); await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db);
if (extension === DatabaseExtension.VECTORCHORD) { if (extension === DatabaseExtension.VECTORCHORD) {
const dbName = sql.id(await this.getDatabaseName()); const dbName = sql.id(await this.getDatabaseName());
await sql`ALTER DATABASE ${dbName} SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536'`.execute(this.db);
await sql`SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536'`.execute(this.db);
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db); await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
await sql`SET vchordrq.probes = 1`.execute(this.db); await sql`SET vchordrq.probes = 1`.execute(this.db);
} }
@@ -142,21 +140,29 @@ export class DatabaseRepository {
} }
targetVersion ??= availableVersion; targetVersion ??= availableVersion;
const isVectors = extension === DatabaseExtension.VECTORS;
let restartRequired = false; let restartRequired = false;
const diff = semver.diff(installedVersion, targetVersion); const diff = semver.diff(installedVersion, targetVersion);
if (!diff) {
return { restartRequired: false };
}
await Promise.all([
this.db.schema.dropIndex(VectorIndex.CLIP).ifExists().execute(),
this.db.schema.dropIndex(VectorIndex.FACE).ifExists().execute(),
]);
await this.db.transaction().execute(async (tx) => { await this.db.transaction().execute(async (tx) => {
await this.setSearchPath(tx); await this.setSearchPath(tx);
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx); await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx);
if (isVectors && (diff === 'major' || diff === 'minor')) { if (extension === DatabaseExtension.VECTORS && (diff === 'major' || diff === 'minor')) {
await sql`SELECT pgvectors_upgrade()`.execute(tx); await sql`SELECT pgvectors_upgrade()`.execute(tx);
restartRequired = true; restartRequired = true;
} }
}); });
if (diff && !restartRequired) { if (!restartRequired) {
await Promise.all([this.reindexVectors(VectorIndex.CLIP), this.reindexVectors(VectorIndex.FACE)]); await Promise.all([this.reindexVectors(VectorIndex.CLIP), this.reindexVectors(VectorIndex.FACE)]);
} }

View File

@@ -138,6 +138,7 @@ export class PersonRepository {
.stream(); .stream();
} }
@GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] })
async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) { async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) {
const items = await this.db const items = await this.db
.selectFrom('person') .selectFrom('person')
@@ -179,8 +180,9 @@ export class PersonRepository {
) )
.$if(!options?.closestFaceAssetId, (qb) => .$if(!options?.closestFaceAssetId, (qb) =>
qb qb
.orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc') .orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
.orderBy(sql`NULLIF(person.name, '') asc nulls last`) .orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast())
.orderBy('person.createdAt'), .orderBy('person.createdAt'),
) )
.$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false)) .$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false))

View File

@@ -111,7 +111,7 @@ export class SyncRepository {
.select(columns.syncAsset) .select(columns.syncAsset)
.where('ownerId', '=', partnerId) .where('ownerId', '=', partnerId)
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'")) .where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('updateId', '<', beforeUpdateId) .where('updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!)) .$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
.orderBy('updateId', 'asc') .orderBy('updateId', 'asc')
.stream(); .stream();
@@ -169,7 +169,7 @@ export class SyncRepository {
.innerJoin('assets', 'assets.id', 'exif.assetId') .innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', partnerId) .where('assets.ownerId', '=', partnerId)
.where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'")) .where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('exif.updateId', '<', beforeUpdateId) .where('exif.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!)) .$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!))
.orderBy('exif.updateId', 'asc') .orderBy('exif.updateId', 'asc')
.stream(); .stream();
@@ -273,7 +273,7 @@ export class SyncRepository {
.select(columns.syncAlbumUser) .select(columns.syncAlbumUser)
.where('albumsId', '=', albumId) .where('albumsId', '=', albumId)
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'")) .where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('updateId', '<', beforeUpdateId) .where('updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!)) .$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
.orderBy('updateId', 'asc') .orderBy('updateId', 'asc')
.stream(); .stream();

View File

@@ -0,0 +1,21 @@
import { Kysely, sql } from 'kysely';
export async function up(qb: Kysely<any>): Promise<void> {
type Conf = { db: string; guc: string[] };
const res = await sql<Conf>`select current_database() db, to_json(setconfig) guc from pg_db_role_setting`.execute(qb);
if (res.rows.length === 0) {
return;
}
const { db, guc } = res.rows[0];
await sql.raw(`alter database "${db}" reset all;`).execute(qb);
for (const parameter of guc) {
const [key, value] = parameter.split('=');
if (key === 'vchordrq.prewarm_dim') {
continue;
}
await sql.raw(`alter database "${db}" set ${key} to ${value};`).execute(qb);
}
}
export async function down(): Promise<void> {}

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