Compare commits

..

1 Commits

Author SHA1 Message Date
Zack Pollard
2c7a29b2f4 fix: panning interrupted while moving around the map 2025-06-19 12:05:40 +01:00
151 changed files with 925 additions and 677 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:./library}", "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:upload-devcontainer-volume}",
// 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,19 +51,14 @@ 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 {} +
# Change ownership for directories that exist run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \
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"; do "${IMMICH_WORKSPACE}/web/dist"
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:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/workspaces/immich/server/upload - ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload
- ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/workspaces/immich/server/upload/upload - ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
immich-web: immich-web:
@@ -29,9 +29,8 @@ 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'
POSTGRES_HOST_AUTH_METHOD: md5 volumes:
volumes: - ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
redis: redis:
env_file: !reset [] env_file: !reset []
@@ -43,6 +42,4 @@ volumes:
open_api_node_modules: open_api_node_modules:
server_node_modules: server_node_modules:
web_node_modules: web_node_modules:
upload1-devcontainer-volume: upload-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.3 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1
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.71", "version": "2.2.69",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.71", "version": "2.2.69",
"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.2", "version": "1.135.0",
"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.71", "version": "2.2.69",
"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.3-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-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.3-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-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.3-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
environment: environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME} POSTGRES_USER: ${DB_USERNAME}

View File

@@ -64,13 +64,7 @@ COMMIT;
### Updating VectorChord ### Updating VectorChord
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: 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;`.
```
ALTER EXTENSION vchord UPDATE;
REINDEX INDEX face_index;
REINDEX INDEX clip_index;
```
## Migrating to VectorChord ## Migrating to VectorChord
@@ -82,8 +76,6 @@ 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]
@@ -97,12 +89,8 @@ 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
@@ -135,20 +123,14 @@ 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,12 +1,4 @@
[ [
{
"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.2", "version": "1.135.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.2", "version": "1.135.0",
"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.71", "version": "2.2.69",
"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.2", "version": "1.135.0",
"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.2", "version": "1.135.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -14,11 +14,7 @@ describe('/people', () => {
let nameAlicePerson: PersonResponseDto; let nameAlicePerson: PersonResponseDto;
let nameBobPerson: PersonResponseDto; let nameBobPerson: PersonResponseDto;
let nameCharliePerson: PersonResponseDto; let nameCharliePerson: PersonResponseDto;
let nameNullPerson4Assets: PersonResponseDto; let nameNullPerson: PersonResponseDto;
let nameNullPerson3Assets: PersonResponseDto;
let nameNullPerson1Asset: PersonResponseDto;
let nameBillPersonFavourite: PersonResponseDto;
let nameFreddyPersonFavourite: PersonResponseDto;
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
@@ -31,11 +27,7 @@ describe('/people', () => {
nameCharliePerson, nameCharliePerson,
nameBobPerson, nameBobPerson,
nameAlicePerson, nameAlicePerson,
nameNullPerson4Assets, nameNullPerson,
nameNullPerson3Assets,
nameNullPerson1Asset,
nameBillPersonFavourite,
nameFreddyPersonFavourite,
] = await Promise.all([ ] = await Promise.all([
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: 'visible_person', name: 'visible_person',
@@ -60,26 +52,11 @@ 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 }),
@@ -87,27 +64,15 @@ 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 }), // 4 assets utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }),
// 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 4 assets // Null-named person
utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }), utils.createFace({ assetId: asset1.id, personId: nameNullPerson.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson4Assets.id }), utils.createFace({ assetId: asset2.id, personId: nameNullPerson.id }), // 2 assets
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 }),
]); ]);
}); });
@@ -122,19 +87,15 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 11, total: 7,
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({ name: 'hidden_person' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
expect.objectContaining({ name: 'hidden_person' }), // Should really be before the null names
], ],
}); });
}); });
@@ -144,21 +105,17 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body.hasNextPage).toBe(false); expect(body.hasNextPage).toBe(false);
expect(body.total).toBe(11); // All persons expect(body.total).toBe(7); // 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);
@@ -170,18 +127,14 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 11, total: 7,
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: '' }),
], ],
}); });
}); });
@@ -195,9 +148,9 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: true, hasNextPage: true,
total: 11, total: 7,
hidden: 1, hidden: 1,
people: [expect.objectContaining({ name: 'Alice' })], people: [expect.objectContaining({ name: 'visible_person' })],
}); });
}); });
}); });

View File

@@ -244,6 +244,7 @@ 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" => 203, "android.injected.version.code" => 201,
"android.injected.version.name" => "1.135.2", "android.injected.version.name" => "1.135.0",
} }
) )
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,7 +346,6 @@
}; };
F0B57D372DF764BD00DC5BCC = { F0B57D372DF764BD00DC5BCC = {
CreatedOnToolsVersion = 16.4; CreatedOnToolsVersion = 16.4;
ProvisioningStyle = Automatic;
}; };
FAC6F88F2D287C890078CB2F = { FAC6F88F2D287C890078CB2F = {
CreatedOnToolsVersion = 16.0; CreatedOnToolsVersion = 16.0;
@@ -649,7 +648,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 = 210; CURRENT_PROJECT_VERSION = 208;
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;
@@ -793,7 +792,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 = 210; CURRENT_PROJECT_VERSION = 208;
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;
@@ -823,7 +822,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 = 210; CURRENT_PROJECT_VERSION = 208;
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;
@@ -857,7 +856,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 = 210; CURRENT_PROJECT_VERSION = 1;
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;
@@ -865,7 +864,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 = 17.0; IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -900,7 +899,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 = 210; CURRENT_PROJECT_VERSION = 1;
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;
@@ -908,7 +907,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 = 17.0; IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -940,7 +939,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 = 210; CURRENT_PROJECT_VERSION = 1;
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;
@@ -948,7 +947,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 = 17.0; IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -979,7 +978,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 = 210; CURRENT_PROJECT_VERSION = 208;
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;
@@ -1023,7 +1022,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 = 210; CURRENT_PROJECT_VERSION = 208;
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;
@@ -1064,7 +1063,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 = 210; CURRENT_PROJECT_VERSION = 208;
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.135.1</string> <string>1.134.0</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>210</string> <string>208</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,8 +168,5 @@
<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") @Parameter(title: "Album", default: NO_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.2" version_number: "1.135.0"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,

View File

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

View File

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,22 @@
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

@@ -0,0 +1,15 @@
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,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 SyncStreamRepository _syncStreamRepository; final ISyncStreamRepository _syncStreamRepository;
final bool Function()? _cancelChecker; final bool Function()? _cancelChecker;
SyncStreamService({ SyncStreamService({
required ISyncApiRepository syncApiRepository, required ISyncApiRepository syncApiRepository,
required SyncStreamRepository syncStreamRepository, required ISyncStreamRepository 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 IsarUserRepository _isarUserRepository; final IUserRepository _userRepository;
final UserApiRepository _userApiRepository; final IUserApiRepository _userApiRepository;
final StoreService _storeService; final StoreService _storeService;
UserService({ UserService({
required IsarUserRepository isarUserRepository, required IUserRepository userRepository,
required UserApiRepository userApiRepository, required IUserApiRepository userApiRepository,
required StoreService storeService, required StoreService storeService,
}) : _isarUserRepository = isarUserRepository, }) : _userRepository = userRepository,
_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 _isarUserRepository.update(user); await _userRepository.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 _isarUserRepository.update(updatedUser); await _userRepository.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 _isarUserRepository.getAll(); return await _userRepository.getAll();
} }
Future<void> deleteAll() { Future<void> deleteAll() {
return _isarUserRepository.deleteAll(); return _userRepository.deleteAll();
} }
} }

View File

@@ -1,4 +1,5 @@
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';
@@ -9,12 +10,14 @@ 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 SyncStreamRepository extends DriftDatabaseRepository { class DriftSyncStreamRepository extends DriftDatabaseRepository
implements ISyncStreamRepository {
final Logger _logger = Logger('DriftSyncStreamRepository'); final Logger _logger = Logger('DriftSyncStreamRepository');
final Drift _db; final Drift _db;
SyncStreamRepository(super.db) : _db = db; DriftSyncStreamRepository(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) {
@@ -31,6 +34,7 @@ class SyncStreamRepository 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) {
@@ -53,6 +57,7 @@ class SyncStreamRepository 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) {
@@ -72,6 +77,7 @@ class SyncStreamRepository 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) {
@@ -95,6 +101,7 @@ class SyncStreamRepository 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);
@@ -104,6 +111,7 @@ class SyncStreamRepository 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);
@@ -113,6 +121,7 @@ class SyncStreamRepository 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);
@@ -122,6 +131,7 @@ class SyncStreamRepository 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);
@@ -131,6 +141,7 @@ class SyncStreamRepository 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);
@@ -140,6 +151,7 @@ class SyncStreamRepository 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,26 +1,30 @@
import 'package:immich_mobile/constants/enums.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/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()
@@ -35,14 +39,17 @@ 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));
@@ -50,6 +57,7 @@ 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));
@@ -57,6 +65,7 @@ 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,15 +1,17 @@
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 { class UserApiRepository extends ApiRepository implements IUserApiRepository {
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;
@@ -18,6 +20,7 @@ class UserApiRepository extends ApiRepository {
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,
@@ -30,6 +33,7 @@ class UserApiRepository extends ApiRepository {
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

@@ -0,0 +1,16 @@
import 'package:immich_mobile/models/activities/activity.model.dart';
abstract interface class IActivityApiRepository {
Future<List<Activity>> getAll(
String albumId, {
String? assetId,
});
Future<Activity> create(
String albumId,
ActivityType type, {
String? assetId,
String? comment,
});
Future<void> delete(String id);
Future<ActivityStats> getStats(String albumId, {String? assetId});
}

View File

@@ -0,0 +1,42 @@
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

@@ -0,0 +1,21 @@
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

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

View File

@@ -0,0 +1,15 @@
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

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

View File

@@ -0,0 +1,8 @@
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

@@ -0,0 +1,13 @@
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 +1,11 @@
// 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

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

View File

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,7 @@
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

@@ -0,0 +1,39 @@
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,6 +9,7 @@ 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';
@@ -107,7 +108,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 AlbumMediaRepository _albumMediaRepository; final IAlbumMediaRepository _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) => SyncStreamRepository(ref.watch(driftProvider)), (ref) => DriftSyncStreamRepository(ref.watch(driftProvider)),
); );
final localSyncServiceProvider = Provider( final localSyncServiceProvider = Provider(

View File

@@ -1,4 +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/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';
@@ -10,16 +12,16 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user.provider.g.dart'; part 'user.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
IsarUserRepository userRepository(Ref ref) => IUserRepository userRepository(Ref ref) =>
IsarUserRepository(ref.watch(isarProvider)); IsarUserRepository(ref.watch(isarProvider));
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
UserApiRepository userApiRepository(Ref ref) => IUserApiRepository 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(
isarUserRepository: ref.watch(userRepositoryProvider), userRepository: 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'538791a4ad126ed086c9db682c67fc5c654d54f3'; String _$userRepositoryHash() => r'1a2ac726bcc44397dcaecf449084fefd336696d4';
/// See also [userRepository]. /// See also [userRepository].
@ProviderFor(userRepository) @ProviderFor(userRepository)
final userRepositoryProvider = Provider<IsarUserRepository>.internal( final userRepositoryProvider = Provider<IUserRepository>.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<IsarUserRepository>.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<IsarUserRepository>; typedef UserRepositoryRef = ProviderRef<IUserRepository>;
String _$userApiRepositoryHash() => r'8a7340ca4544c8c6b20225c65bff2abb9e96baa2'; String _$userApiRepositoryHash() => r'6b19f2c99fb83162a5ceb91adb8589eaae01bc92';
/// See also [userApiRepository]. /// See also [userApiRepository].
@ProviderFor(userApiRepository) @ProviderFor(userApiRepository)
final userApiRepositoryProvider = Provider<UserApiRepository>.internal( final userApiRepositoryProvider = Provider<IUserApiRepository>.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<UserApiRepository>.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<UserApiRepository>; typedef UserApiRepositoryRef = ProviderRef<IUserApiRepository>;
String _$userServiceHash() => r'181414dddc7891be6237e13d568c287a804228d1'; String _$userServiceHash() => r'4a0873357b7115b4d6bfa8e89b847c0b74ce0d93';
/// 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/domain/models/person.model.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/services/person.service.dart'; import 'package:immich_mobile/services/person.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';

View File

@@ -1,5 +1,6 @@
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';
@@ -9,17 +10,20 @@ 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, {
@@ -38,10 +42,12 @@ 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,6 +5,7 @@ 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';
@@ -13,21 +14,24 @@ final albumApiRepositoryProvider = Provider(
(ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi), (ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
); );
class AlbumApiRepository extends ApiRepository { class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
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,
@@ -50,6 +54,7 @@ class AlbumApiRepository extends ApiRepository {
return _toAlbum(responseDto); return _toAlbum(responseDto);
} }
@override
Future<Album> update( Future<Album> update(
String albumId, { String albumId, {
String? name, String? name,
@@ -79,10 +84,12 @@ class AlbumApiRepository extends ApiRepository {
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,
@@ -107,6 +114,7 @@ class AlbumApiRepository extends ApiRepository {
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,
@@ -128,6 +136,7 @@ class AlbumApiRepository extends ApiRepository {
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();
@@ -140,6 +149,7 @@ class AlbumApiRepository extends ApiRepository {
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,13 +4,14 @@ 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 { class AlbumMediaRepository implements IAlbumMediaRepository {
const AlbumMediaRepository(); const AlbumMediaRepository();
bool get useCustomFilter => bool get useCustomFilter =>
@@ -40,6 +41,7 @@ class AlbumMediaRepository {
) )
: 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')
@@ -50,6 +52,7 @@ class AlbumMediaRepository {
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());
@@ -58,12 +61,14 @@ class AlbumMediaRepository {
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,
@@ -92,7 +97,12 @@ class AlbumMediaRepository {
return assets.map(AssetMediaRepository.toAsset).toList().cast(); return assets.map(AssetMediaRepository.toAsset).toList().cast();
} }
Future<Album> get(String id) async { @override
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,16 +1,18 @@
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 { class BiometricRepository implements IBiometricRepository {
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;
@@ -24,6 +26,7 @@ class BiometricRepository {
); );
} }
@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,16 +1,21 @@
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 { class DownloadRepository implements IDownloadRepository {
@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() {
@@ -33,18 +38,22 @@ class DownloadRepository {
); );
} }
@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,
@@ -52,6 +61,7 @@ class DownloadRepository {
); );
} }
@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,6 +1,7 @@
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((_) {
@@ -9,11 +10,12 @@ final networkRepositoryProvider = Provider((_) {
return NetworkRepository(networkInfo); return NetworkRepository(networkInfo);
}); });
class NetworkRepository { class NetworkRepository implements INetworkRepository {
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
@@ -28,6 +30,7 @@ class NetworkRepository {
return _networkInfo.getWifiName(); return _networkInfo.getWifiName();
} }
@override
Future<String?> getWifiIp() { Future<String?> getWifiIp() {
return _networkInfo.getWifiIP(); return _networkInfo.getWifiIP();
} }

View File

@@ -2,6 +2,7 @@ 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';
@@ -10,9 +11,11 @@ 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()
@@ -23,6 +26,7 @@ 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()
@@ -33,11 +37,13 @@ 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,26 +1,24 @@
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(
@@ -32,13 +30,16 @@ 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/domain/models/person.model.dart'; import 'package:immich_mobile/interfaces/person_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';
@@ -8,16 +8,19 @@ 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,22 +1,26 @@
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 { class SecureStorageRepository implements ISecureStorageRepository {
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,4 +1,5 @@
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';
@@ -10,11 +11,13 @@ 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,6 +1,7 @@
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';
@@ -8,11 +9,13 @@ final shareHandlerRepositoryProvider = Provider(
(ref) => ShareHandlerRepository(), (ref) => ShareHandlerRepository(),
); );
class ShareHandlerRepository { class ShareHandlerRepository implements IShareHandlerRepository {
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,6 +3,7 @@ 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';
@@ -12,9 +13,11 @@ 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()
@@ -25,6 +28,7 @@ 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()
@@ -35,6 +39,7 @@ 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()
@@ -47,6 +52,7 @@ 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()
@@ -61,6 +67,7 @@ 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,
@@ -79,6 +86,7 @@ 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()
@@ -89,6 +97,7 @@ 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()
@@ -102,6 +111,7 @@ 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,
@@ -118,6 +128,7 @@ 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,
@@ -134,6 +145,7 @@ 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,
@@ -141,6 +153,7 @@ 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()
@@ -155,6 +168,7 @@ 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 ActivityApiRepository _activityApiRepository; final IActivityApiRepository _activityApiRepository;
@override @override
final Logger logger = Logger("ActivityService"); final Logger logger = Logger("ActivityService");

View File

@@ -14,6 +14,8 @@ 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';
@@ -49,8 +51,8 @@ class AlbumService {
final IAlbumRepository _albumRepository; final IAlbumRepository _albumRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IBackupAlbumRepository _backupAlbumRepository; final IBackupAlbumRepository _backupAlbumRepository;
final AlbumMediaRepository _albumMediaRepository; final IAlbumMediaRepository _albumMediaRepository;
final AlbumApiRepository _albumApiRepository; final IAlbumApiRepository _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 IsarUserRepository _isarUserRepository; final IUserRepository _userRepository;
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._isarUserRepository, this._userRepository,
this._etagRepository, this._etagRepository,
this._backupRepository, this._backupRepository,
this._apiService, this._apiService,
@@ -85,9 +85,7 @@ 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 _isarUserRepository.getByUserIds(syncedUserIds)) : (await _userRepository.getByUserIds(syncedUserIds)).nonNulls.toList();
.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,6 +11,7 @@ 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';
@@ -51,7 +52,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 AlbumMediaRepository _albumMediaRepository; final IAlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IAssetMediaRepository _assetMediaRepository; final IAssetMediaRepository _assetMediaRepository;

View File

@@ -6,6 +6,7 @@ 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';
@@ -22,7 +23,7 @@ final downloadServiceProvider = Provider(
); );
class DownloadService { class DownloadService {
final DownloadRepository _downloadRepository; final IDownloadRepository _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 IsarUserRepository _isarUserRepository; final IUserRepository _userRepository;
EntityService( EntityService(
this._assetRepository, this._assetRepository,
this._isarUserRepository, this._userRepository,
); );
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 _isarUserRepository.getByUserId(ownerId); final user = await _userRepository.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 _isarUserRepository final users = await _userRepository
.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,4 +1,5 @@
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';
@@ -9,7 +10,9 @@ final localAuthServiceProvider = Provider(
); );
class LocalAuthService { class LocalAuthService {
final BiometricRepository _biometricRepository; // final _log = Logger("LocalAuthService");
final IBiometricRepository _biometricRepository;
LocalAuthService(this._biometricRepository); LocalAuthService(this._biometricRepository);

View File

@@ -1,4 +1,5 @@
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';
@@ -10,7 +11,7 @@ final networkServiceProvider = Provider((ref) {
}); });
class NetworkService { class NetworkService {
final NetworkRepository _repository; final INetworkRepository _repository;
final IPermissionRepository _permissionRepository; final IPermissionRepository _permissionRepository;
NetworkService(this._repository, this._permissionRepository); NetworkService(this._repository, this._permissionRepository);

View File

@@ -1,6 +1,8 @@
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/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/interfaces/partner.interface.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';
@@ -15,14 +17,14 @@ final partnerServiceProvider = Provider(
); );
class PartnerService { class PartnerService {
final PartnerApiRepository _partnerApiRepository; final IPartnerApiRepository _partnerApiRepository;
final PartnerRepository _partnerRepository; final IPartnerRepository _partnerRepository;
final IsarUserRepository _isarUserRepository; final IUserRepository _userRepository;
final Logger _log = Logger("PartnerService"); final Logger _log = Logger("PartnerService");
PartnerService( PartnerService(
this._partnerApiRepository, this._partnerApiRepository,
this._isarUserRepository, this._userRepository,
this._partnerRepository, this._partnerRepository,
); );
@@ -45,8 +47,7 @@ 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 _isarUserRepository await _userRepository.update(partner.copyWith(isPartnerSharedBy: false));
.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;
@@ -57,8 +58,7 @@ 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 _isarUserRepository await _userRepository.update(partner.copyWith(isPartnerSharedBy: true));
.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 _isarUserRepository await _userRepository
.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 PersonApiRepository _personApiRepository; final IPersonApiRepository _personApiRepository;
final IAssetApiRepository _assetApiRepository; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;

View File

@@ -1,4 +1,5 @@
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(
@@ -8,7 +9,9 @@ final secureStorageServiceProvider = Provider(
); );
class SecureStorageService { class SecureStorageService {
final SecureStorageRepository _secureStorageRepository; // final _log = Logger("LocalAuthService");
final ISecureStorageRepository _secureStorageRepository;
SecureStorageService(this._secureStorageRepository); SecureStorageService(this._secureStorageRepository);

View File

@@ -3,20 +3,23 @@ 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';
@@ -60,17 +63,17 @@ final syncServiceProvider = Provider(
class SyncService { class SyncService {
final HashService _hashService; final HashService _hashService;
final EntityService _entityService; final EntityService _entityService;
final AlbumMediaRepository _albumMediaRepository; final IAlbumMediaRepository _albumMediaRepository;
final AlbumApiRepository _albumApiRepository; final IAlbumApiRepository _albumApiRepository;
final IAlbumRepository _albumRepository; final IAlbumRepository _albumRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository; final IExifInfoRepository _exifInfoRepository;
final IsarUserRepository _isarUserRepository; final IUserRepository _userRepository;
final UserService _userService; final UserService _userService;
final PartnerRepository _partnerRepository; final IPartnerRepository _partnerRepository;
final IETagRepository _eTagRepository; final IETagRepository _eTagRepository;
final PartnerApiRepository _partnerApiRepository; final IPartnerApiRepository _partnerApiRepository;
final UserApiRepository _userApiRepository; final IUserApiRepository _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;
@@ -85,7 +88,7 @@ class SyncService {
this._assetRepository, this._assetRepository,
this._exifInfoRepository, this._exifInfoRepository,
this._partnerRepository, this._partnerRepository,
this._isarUserRepository, this._userRepository,
this._userService, this._userService,
this._eTagRepository, this._eTagRepository,
this._appSettingsService, this._appSettingsService,
@@ -162,7 +165,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 _isarUserRepository.getAll(sortBy: SortUserBy.id); final dbUsers = await _userRepository.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(
@@ -183,9 +186,9 @@ class SyncService {
onlySecond: (UserDto b) => toDelete.add(b.id), onlySecond: (UserDto b) => toDelete.add(b.id),
); );
if (changes) { if (changes) {
await _isarUserRepository.transaction(() async { await _userRepository.transaction(() async {
await _isarUserRepository.delete(toDelete); await _userRepository.delete(toDelete);
await _isarUserRepository.updateAll(toUpsert); await _userRepository.updateAll(toUpsert);
}); });
} }
return changes; return changes;
@@ -445,7 +448,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 _isarUserRepository.getByUserIds(userIdsToAdd); final usersToLink = await _userRepository.getByUserIds(userIdsToAdd);
album.name = dto.name; album.name = dto.name;
album.description = dto.description; album.description = dto.description;

View File

@@ -2,6 +2,7 @@ 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';
@@ -17,7 +18,7 @@ final timelineServiceProvider = Provider<TimelineService>((ref) {
}); });
class TimelineService { class TimelineService {
final TimelineRepository _timelineRepository; final ITimelineRepository _timelineRepository;
final AppSettingsService _appSettingsService; final AppSettingsService _appSettingsService;
final UserService _userService; final UserService _userService;

View File

@@ -57,13 +57,7 @@ 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.2 - API version: 1.135.0
- 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.2+203 version: 1.135.0+201
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 SyncStreamRepository mockSyncStreamRepo; late ISyncStreamRepository 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 IsarUserRepository mockUserRepo; late IUserRepository mockUserRepo;
late UserApiRepository mockUserApiRepo; late IUserApiRepository mockUserApiRepo;
late StoreService mockStoreService; late StoreService mockStoreService;
setUp(() { setUp(() {
mockUserRepo = MockIsarUserRepository(); mockUserRepo = MockUserRepository();
mockUserApiRepo = MockUserApiRepository(); mockUserApiRepo = MockUserApiRepository();
mockStoreService = MockStoreService(); mockStoreService = MockStoreService();
sut = UserService( sut = UserService(
isarUserRepository: mockUserRepo, userRepository: 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/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/domain/interfaces/user_api.interface.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 MockIsarUserRepository extends Mock implements IsarUserRepository {} class MockUserRepository extends Mock implements IUserRepository {}
class MockDeviceAssetRepository extends Mock class MockDeviceAssetRepository extends Mock
implements IDeviceAssetRepository {} implements IDeviceAssetRepository {}
class MockSyncStreamRepository extends Mock implements SyncStreamRepository {} class MockSyncStreamRepository extends Mock implements ISyncStreamRepository {}
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 UserApiRepository {} class MockUserApiRepository extends Mock implements IUserApiRepository {}
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/constants/enums.dart'; import 'package:immich_mobile/domain/interfaces/user.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/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/repositories/partner_api.repository.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.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 MockIsarUserRepository userRepository = MockIsarUserRepository(); final MockUserRepository userRepository = MockUserRepository();
final MockETagRepository eTagRepository = MockETagRepository(); final MockETagRepository eTagRepository = MockETagRepository();
final MockAlbumMediaRepository albumMediaRepository = final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository(); MockAlbumMediaRepository();

View File

@@ -1,5 +1,7 @@
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';
@@ -9,10 +11,8 @@ 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/repositories/partner_api.repository.dart'; import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.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 AlbumMediaRepository {} class MockAlbumMediaRepository extends Mock implements IAlbumMediaRepository {}
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 AlbumApiRepository {} class MockAlbumApiRepository extends Mock implements IAlbumApiRepository {}
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 PartnerRepository {} class MockPartnerRepository extends Mock implements IPartnerRepository {}
class MockPartnerApiRepository extends Mock implements PartnerApiRepository {} class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
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 MockIsarUserRepository userRepository; late MockUserRepository 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 = MockIsarUserRepository(); userRepository = MockUserRepository();
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 MockIsarUserRepository userRepository; late MockUserRepository userRepository;
setUp(() { setUp(() {
assetRepository = MockAssetRepository(); assetRepository = MockAssetRepository();
userRepository = MockIsarUserRepository(); userRepository = MockUserRepository();
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.2", "version": "1.135.0",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],

View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.2", "version": "1.135.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.2", "version": "1.135.0",
"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.2", "version": "1.135.0",
"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.2 * 1.135.0
* 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.2", "version": "1.135.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.135.2", "version": "1.135.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -42,6 +42,7 @@
"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",
@@ -1348,6 +1349,21 @@
"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",
@@ -5036,6 +5052,27 @@
"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",
@@ -11296,6 +11333,19 @@
"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.2", "version": "1.135.0",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -68,6 +68,7 @@
"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" AT TIME ZONE 'UTC')::date::text as "timeBucket", "timeBucket"::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,38 +12,6 @@ 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,6 +42,11 @@ 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;
@@ -485,13 +490,13 @@ export class AssetRepository {
.execute(); .execute();
} }
@GenerateSql({ params: [{}] }) @GenerateSql({ params: [{ size: TimeBucketSize.MONTH }] })
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>().as('timeBucket')) .select(truncatedDate<Date>(TimeBucketSize.MONTH).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)
@@ -520,7 +525,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" AT TIME ZONE 'UTC')::date::text`.as('timeBucket')) .select(sql<string>`"timeBucket"::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')
@@ -571,7 +576,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(), '=', timeBucket.replace(/^[+-]/, '')) .where(truncatedDate(TimeBucketSize.MONTH), '=', timeBucket.replace(/^[+-]/, ''))
.$if(!!options.albumId, (qb) => .$if(!!options.albumId, (qb) =>
qb.where((eb) => qb.where((eb) =>
eb.exists( eb.exists(

View File

@@ -119,6 +119,8 @@ 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);
} }
@@ -140,29 +142,21 @@ 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 (extension === DatabaseExtension.VECTORS && (diff === 'major' || diff === 'minor')) { if (isVectors && (diff === 'major' || diff === 'minor')) {
await sql`SELECT pgvectors_upgrade()`.execute(tx); await sql`SELECT pgvectors_upgrade()`.execute(tx);
restartRequired = true; restartRequired = true;
} }
}); });
if (!restartRequired) { if (diff && !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,7 +138,6 @@ 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')
@@ -180,9 +179,8 @@ 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, '')`, (om) => om.asc().nullsLast()) .orderBy(sql`NULLIF(person.name, '') asc nulls last`)
.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

@@ -1,21 +0,0 @@
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