Compare commits

...

84 Commits

Author SHA1 Message Date
izzy
6e7854b5bb chore: sync SQL 2025-12-03 16:46:08 +00:00
izzy
5d5d421201 fix: path -> reportId as reportId 2025-12-03 15:24:30 +00:00
izzy
7a215c16ab fix: flip deletedAt filter 2025-12-02 14:23:56 +00:00
izzy
ae653f9bf5 chore: lint 2025-12-02 14:07:14 +00:00
izzy
73a17bb58e chore: generate SQL 2025-12-02 13:54:55 +00:00
izzy
e1a1662225 chore: more compliant csv 2025-12-02 13:33:13 +00:00
izzy
6e752bed77 fix: don't process trashed/deleted assets for integrity 2025-12-02 13:19:37 +00:00
izzy
64cc64dd56 refactor: move all new queries into integrity repository 2025-12-02 13:15:48 +00:00
izzy
6cfd1994c4 feat: ability to delete all reports (and corresponding objects) 2025-12-02 11:59:23 +00:00
izzy
806a2880ca feat: assetId, fileAssetId columns on integrity reports 2025-12-01 15:49:03 +00:00
izzy
042af30bef chore: use checksum configuration 2025-12-01 14:27:04 +00:00
izzy
06fcd54b9f feat: download csv report, download file, delete file 2025-12-01 14:20:38 +00:00
izzy
fec8923431 test: increase timeouts 2025-12-01 12:07:24 +00:00
izzy
db690bcf63 chore: generate SQL 2025-12-01 11:56:01 +00:00
izzy
1daf1b471f chore: lint 2025-12-01 11:51:49 +00:00
izzy
01f96de3e5 test: serialise the buffer over events 2025-12-01 11:20:34 +00:00
izzy
c4ac8d9f63 stash: incomplete checksum outdated test 2025-11-28 18:01:24 +00:00
izzy
0362d21945 test: take baseline, check for each issue, check refreshes work 2025-11-28 17:44:48 +00:00
izzy
4d7f7b80da feat: refresh missing & checksum 2025-11-28 17:44:37 +00:00
izzy
e447ba87c6 chore: sort i18n 2025-11-28 15:28:50 +00:00
izzy
2779fce7d0 feat: manually trigger integrity jobs
feat: update summary after job runs
2025-11-28 15:27:12 +00:00
izzy
13e9cf0ed9 stash: moving computers because pnpm is cooked 2025-11-28 12:50:30 +00:00
izzy
c50118e535 chore: remove old table comment 2025-11-28 12:10:41 +00:00
izzy
ca358f4dae feat: sub-pages for integrity reports 2025-11-28 11:40:53 +00:00
izzy
d3abed3414 feat: view integrity report in maintenance page (cherry picked) 2025-11-27 17:53:20 +00:00
izzy
0fdc7b4448 feat: draft controller entry
chore: lint & format
2025-11-27 17:23:54 +00:00
izzy
8db6132669 fix: add mock for asset repo. 2025-11-27 16:42:46 +00:00
izzy
03276de6b2 fix: add integrity report repository to service depends. 2025-11-27 16:34:28 +00:00
izzy
4462683739 chore: generate SQL queries 2025-11-27 16:19:34 +00:00
izzy
919eb839ef revert: override migration db url 2025-11-27 16:14:03 +00:00
izzy
251631948b fix: mock the new repository 2025-11-27 16:11:19 +00:00
izzy
93860238af feat: add config options & cron entries for checks 2025-11-27 16:05:26 +00:00
izzy
1744237aeb chore: open api 2025-11-27 15:40:44 +00:00
izzy
ef7d8e94fa feat: check orphaned file reports are not out of date 2025-11-27 15:40:14 +00:00
izzy
cc31b9c7f1 feat: clean up old reports of checksum or missing files
refactor: combine the stream query
2025-11-27 15:13:19 +00:00
izzy
929ad529f4 feat: add createdAt to integrity report table
refactor: rename checksum_fail to checksum_mismatched
2025-11-27 15:13:00 +00:00
izzy
1e941f3f88 feat: write integrity report to database 2025-11-27 12:53:04 +00:00
izzy
15503b150a chore: open api 2025-11-27 12:01:26 +00:00
izzy
3414210450 feat: checksum job 2025-11-27 12:00:35 +00:00
izzy
4a7120cdeb refactor: batched integrity checks 2025-11-26 17:36:28 +00:00
izzy
f77f43a83d stash: integrity checks 2025-11-26 15:45:58 +00:00
Jason Rasmussen
13104d49cd feat(web): shared link card tweaks (#24192) 2025-11-25 19:35:21 -06:00
Jason Rasmussen
2d5ec528d5 fix(web): user admin pages (#24185)
fix: user admin pages
2025-11-25 16:35:37 -05:00
Min Idzelis
5226898184 fix: update timeline-manager after archive actions (#24010)
* fix: update timeline-manager after archive actions

* Add locators to thumb icons
2025-11-25 15:06:29 -05:00
Luka Prebil Grintal
dd4169876c fix(ml): Upgrade ONNX Runtime to v1.22.1 to fix ROCm build failures (#24045)
* fix: update ONNX runtime version to 1.21.0 to fix the failing checksum of 1.20.1

* update patch

* update to 1.22.1

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-11-25 13:27:21 -05:00
renovate[bot]
8321c275b8 chore(deps): update dependency body-parser to v2.2.1 [security] (#24179)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 18:29:13 +01:00
renovate[bot]
3d6c26350a fix(deps): update typescript-projects (#24163)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-11-25 17:26:36 +00:00
Jason Rasmussen
db15e5e423 fix: duration extraction (#24178) 2025-11-25 10:26:25 -05:00
renovate[bot]
35d18da14a chore(deps): update node to v24 (major) (#24169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 15:40:48 +01:00
renovate[bot]
cb56a11f0b chore(deps): update dependency @types/archiver to v7 (#24166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 15:40:12 +01:00
Jason Rasmussen
104fa09f69 feat: queues (#24142) 2025-11-25 08:19:40 -05:00
Alex
66ae07ee39 fix: don't get OCR data in shared link (#24152) 2025-11-25 07:58:27 -05:00
Daniel Dietzler
939d2c8b27 chore: minor admin pages refactorings (#24160) 2025-11-25 07:57:30 -05:00
renovate[bot]
2801a6e672 chore(deps): update actions/download-artifact action to v6 (#24164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 13:46:34 +01:00
renovate[bot]
4742360469 chore(deps): update grafana/grafana docker tag to v12.3.0 (#24162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 13:45:50 +01:00
Daniel Dietzler
b56fa62b32 fix: revert "chore(deps): update dependency sharp to v0.34.5" (#24173) 2025-11-25 12:37:08 +00:00
renovate[bot]
ddbe485074 chore(deps): update dependency sharp to v0.34.5 (#24146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 11:56:11 +01:00
renovate[bot]
01310c6d86 chore(deps): update node.js to v24.11.1 (#24147)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 11:55:36 +01:00
Brandon Wees
512327ef69 feat: java in mise (#24154) 2025-11-24 23:18:44 -06:00
Daniel Dietzler
8755cd59fd chore: refactor svelte reactivity (#24072) 2025-11-24 18:57:46 -05:00
Min Idzelis
7694b342ed refactor(web): Extract asset grid layout component from TimelineDateGroup and split into AssetLayout and Month components (#23338)
* refactor(web): Extract asset grid layout component from TimelineDateGroup and split into AssetLayout and Month components

* chore: cleanup

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2025-11-24 23:09:46 +00:00
fabianbees
78553a0258 feat: separate camera and lens info in detail panel (#23670)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-11-24 16:30:15 +00:00
renovate[bot]
c1198b99b7 chore(deps): update dependency js-yaml to v4.1.1 [security] (#23901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 17:28:18 +01:00
renovate[bot]
8b7b9ee394 chore(deps): update dependency esbuild to ^0.25.0 [security] (#23903)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 17:27:46 +01:00
Min Idzelis
d6b39a464d feat: improve performance: don't sort timeline buckets from server (#24032) 2025-11-24 17:26:52 +01:00
Snowknight26
75d23fe135 fix(web): fix support & feedback modal wrapping (#24018)
* fix(web): fix support & feedback modal wrapping

* Fix reference
2025-11-24 10:24:02 -06:00
shenlong
c860809aa1 fix: getAspectRatio fallback to db width and height (#24131)
fix: getExif fallback to db width and height

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-11-24 10:23:17 -06:00
Daniel Dietzler
0498f6cb9d fix: albums page reactivity loops (#24046) 2025-11-24 17:14:24 +01:00
shenlong
24e5dabb51 fix: use proper updatedAt value in local assets (#24137)
* fix: incorrect updatedAt value in local assets

* add test

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-11-24 09:49:27 -06:00
Greg Lutostanski
aecf064ec9 fix(server): sanitize DB_URL for pg_dumpall to remove unknown query params (#23333)
Co-authored-by: Greg Lutostanski <greg.lutostanski@mobilityhouse.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-11-24 16:34:21 +01:00
Daniel Dietzler
57be3ff8c7 fix: add users to album (#24133) 2025-11-24 07:52:36 -05:00
Ujjwal Goel
99505f987e fix: use npm instead of pnpm and fix check:all (#24101)
* fix: use npm instead of pnpm and fix `check:all`

* fix: remove `--` from pnpm commands

* Remove `check:all` from the documentation section
2025-11-23 21:04:43 -06:00
Lukas
1e1c4ac9d2 feat(web): allow navigating the map with arrow keys (#24080) 2025-11-21 23:46:30 +01:00
Mees Frensel
d952b62053 feat(web): show detected faces in spherical photos (#23974) 2025-11-21 09:11:47 -06:00
Yaros
9f3eeed091 fix(mobile): first video memory on page doesn't play (#23906)
* fix(mobile): first video memory doesn't play

* refactor: moved logic to static method

* refactor: fix haptic feedback & empty check

* refactor: use DriftMemory on setMemory

* refactor: move video reset into if block
2025-11-21 09:11:30 -06:00
Brandon Wees
1dbc20fd77 fix: show archived assets in favorite page (#24052) 2025-11-21 09:09:16 -06:00
Joren Guillaume
ba8df712c4 fix: Use correct app store link (#24062) 2025-11-21 13:54:09 +01:00
renovate[bot]
741d838f56 chore(deps): update base-image to v202511181104 (major) (#24050)
chore(deps): update base-image to v202511181104

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 15:40:40 -06:00
Brandon Wees
ec2fa6e308 fix: disable animation "add to" action menu (#24040) 2025-11-20 11:54:15 -06:00
shenlong
b974ed5735 fix: do not clear hash on updated_at change (#24039)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-11-20 11:31:17 -06:00
Daniel Dietzler
78457d9b89 chore: add new language requests (#23991) 2025-11-20 08:58:18 -05:00
bo0tzz
5d043b435e fix: hardcoded build-cache pull for e2e tests (#24002) 2025-11-20 14:22:45 +01:00
Jason Rasmussen
9a403d5886 refactor(web): user delete websocket event (#24015) 2025-11-20 07:54:29 -05:00
Jason Rasmussen
1a31faf1a2 fix: effect loop (#24014) 2025-11-20 07:52:37 -05:00
196 changed files with 10083 additions and 3261 deletions

2
.github/.nvmrc vendored
View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -74,7 +74,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Download APK
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: release-apk-signed
github-token: ${{ steps.generate-token.outputs.token }}

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -1,4 +1,4 @@
FROM node:22.16.0-alpine3.20@sha256:2289fb1fba0f4633b08ec47b94a89c7e20b829fc5679f9b7b298eaa2f1ed8b7e AS core
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core
WORKDIR /usr/src/app
COPY package* pnpm* .pnpmfile.cjs ./

View File

@@ -20,7 +20,7 @@
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
@@ -69,6 +69,6 @@
"micromatch": "^4.0.8"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -95,7 +95,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:12.2.1-ubuntu@sha256:797530c642f7b41ba7848c44cfda5e361ef1f3391a98bed1e5d448c472b6826a
image: grafana/grafana:12.3.0-ubuntu@sha256:cee936306135e1925ab21dffa16f8a411535d16ab086bef2309339a8e74d62df
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -14,15 +14,15 @@ When contributing code through a pull request, please check the following:
- [ ] `pnpm run check:typescript` (check typescript)
- [ ] `pnpm test` (unit tests)
:::tip AIO
Run all web checks with `pnpm run check:all`
:::
## Documentation
- [ ] `pnpm run format` (formatting via Prettier)
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
:::tip AIO
Run all web checks with `pnpm run check:all`
:::
## Server Checks
- [ ] `pnpm run lint` (linting via ESLint)

View File

@@ -93,7 +93,7 @@ Information on the current workers can be found [here](/administration/jobs-work
All `DB_` variables must be provided to all Immich workers, including `api` and `microservices`.
`DB_URL` must be in the format `postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename`.
You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&sslmode=no-verify`.
You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&uselibpqcompat=true`. This allows both immich and `pg_dumpall` (the utility used for database backups) to [properly connect](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string#tcp-connections) to your database.
When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD` and `DB_DATABASE_NAME` database variables are ignored.

View File

@@ -57,6 +57,6 @@
"node": ">=20"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -7,6 +7,9 @@ services:
build:
context: ../
dockerfile: server/Dockerfile
cache_from:
- type=registry,ref=ghcr.io/immich-app/immich-server-build-cache:linux-amd64-cc099f297acd18c924b35ece3245215b53d106eb2518e3af6415931d055746cd-main
- type=registry,ref=ghcr.io/immich-app/immich-server-build-cache:linux-arm64-cc099f297acd18c924b35ece3245215b53d106eb2518e3af6415931d055746cd-main
args:
- BUILD_ID=1234567890
- BUILD_IMAGE=e2e

View File

@@ -26,7 +26,7 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"@types/oidc-provider": "^9.0.0",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
@@ -54,6 +54,6 @@
"vitest": "^3.0.0"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -1,9 +1,12 @@
import { LoginResponseDto } from '@immich/sdk';
import { IntegrityReportType, LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk';
import { readFile } from 'node:fs/promises';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, utils } from 'src/utils';
import { app, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
describe('/admin/maintenance', () => {
let cookie: string | undefined;
@@ -34,6 +37,188 @@ describe('/admin/maintenance', () => {
});
});
describe('POST /integrity/summary (& jobs)', async () => {
let baseline: Record<IntegrityReportType, number>;
beforeAll(async () => {
await utils.createAsset(admin.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(assetFilepath),
},
});
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/upload/${admin.userId}-bak`);
});
afterEach(async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.copyFolder(`/data/upload/${admin.userId}-bak`, `/data/upload/${admin.userId}`);
});
it.sequential('may report issues', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityOrphanFiles,
});
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
missing_file: 0,
orphan_file: expect.any(Number),
checksum_mismatch: 0,
});
baseline = body;
});
it.sequential('should detect an orphan file (job: check orphan files)', async () => {
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityOrphanFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
orphan_file: baseline.orphan_file + 1,
}),
);
});
it.sequential('should detect outdated orphan file reports (job: refresh orphan files)', async () => {
// these should not be detected:
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan2.png`);
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan3.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityOrphanFilesRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
orphan_file: baseline.orphan_file,
}),
);
});
it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 1,
checksum_mismatch: 0,
}),
);
});
it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFilesRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 0,
checksum_mismatch: 0,
}),
);
});
it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 1,
}),
);
});
it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatchRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask);
const { status, body } = await request(app)
.get('/admin/maintenance/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 0,
}),
);
});
});
// => enter maintenance mode
describe.sequential('POST /', () => {
@@ -83,8 +268,8 @@ describe('/admin/maintenance', () => {
return body.maintenanceMode;
},
{
interval: 5e2,
timeout: 1e4,
interval: 500,
timeout: 60_000,
},
)
.toBeTruthy();
@@ -162,8 +347,8 @@ describe('/admin/maintenance', () => {
return body.maintenanceMode;
},
{
interval: 5e2,
timeout: 1e4,
interval: 500,
timeout: 60_000,
},
)
.toBeFalsy();

View File

@@ -62,50 +62,60 @@ export const setupTimelineMockApiRoutes = async (
return route.continue();
});
await context.route('**/api/assets/**', async (route, request) => {
await context.route('**/api/assets/*', async (route, request) => {
const url = new URL(request.url());
const pathname = url.pathname;
const assetId = basename(pathname);
const asset = getAsset(timelineRestData, assetId);
return route.fulfill({
status: 200,
contentType: 'application/json',
json: asset,
});
});
await context.route('**/api/assets/*/ocr', async (route) => {
return route.fulfill({ status: 200, contentType: 'application/json', json: [] });
});
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
const match = request.url().match(pattern);
if (!match) {
const url = new URL(request.url());
const pathname = url.pathname;
const assetId = basename(pathname);
const asset = getAsset(timelineRestData, assetId);
return route.fulfill({
status: 200,
contentType: 'application/json',
json: asset,
});
if (!match?.groups) {
throw new Error(`Invalid URL for thumbnail endpoint: ${request.url()}`);
}
if (match.groups?.size === 'preview') {
if (match.groups.size === 'preview') {
if (!route.request().serviceWorker()) {
return route.continue();
}
const asset = getAsset(timelineRestData, match.groups?.assetId);
const asset = getAsset(timelineRestData, match.groups.assetId);
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg', ETag: 'abc123', 'Cache-Control': 'public, max-age=3600' },
body: await randomPreview(
match.groups?.assetId,
match.groups.assetId,
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
),
});
}
if (match.groups?.size === 'thumbnail') {
if (match.groups.size === 'thumbnail') {
if (!route.request().serviceWorker()) {
return route.continue();
}
const asset = getAsset(timelineRestData, match.groups?.assetId);
const asset = getAsset(timelineRestData, match.groups.assetId);
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg' },
body: await randomThumbnail(
match.groups?.assetId,
match.groups.assetId,
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
),
});
}
return route.continue();
});
await context.route('**/api/albums/**', async (route, request) => {
const pattern = /\/api\/albums\/(?<albumId>[^/?]+)/;
const match = request.url().match(pattern);

View File

@@ -6,13 +6,14 @@ import {
CheckExistingAssetsDto,
CreateAlbumDto,
CreateLibraryDto,
JobCreateDto,
MaintenanceAction,
MetadataSearchDto,
Permission,
PersonCreateDto,
QueueCommandDto,
QueueName,
QueuesResponseDto,
QueuesResponseLegacyDto,
SharedLinkCreateDto,
UpdateLibraryDto,
UserAdminCreateDto,
@@ -21,6 +22,7 @@ import {
checkExistingAssets,
createAlbum,
createApiKey,
createJob,
createLibrary,
createPartner,
createPerson,
@@ -52,9 +54,12 @@ import {
import { BrowserContext } from '@playwright/test';
import { exec, spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
import { createWriteStream, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
import { mkdtemp } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, resolve } from 'node:path';
import { dirname, join, resolve } from 'node:path';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { setTimeout as setAsyncTimeout } from 'node:timers/promises';
import { promisify } from 'node:util';
import pg from 'pg';
@@ -171,6 +176,7 @@ export const utils = {
'user',
'system_metadata',
'tag',
'integrity_report',
];
const sql: string[] = [];
@@ -481,6 +487,9 @@ export const utils = {
tagAssets: (accessToken: string, tagId: string, assetIds: string[]) =>
tagAssets({ id: tagId, bulkIdsDto: { ids: assetIds } }, { headers: asBearerAuth(accessToken) }),
createJob: async (accessToken: string, jobCreateDto: JobCreateDto) =>
createJob({ jobCreateDto }, { headers: asBearerAuth(accessToken) }),
queueCommand: async (accessToken: string, name: QueueName, queueCommandDto: QueueCommandDto) =>
runQueueCommandLegacy({ name, queueCommandDto }, { headers: asBearerAuth(accessToken) }),
@@ -559,18 +568,62 @@ export const utils = {
mkdirSync(`${testAssetDir}/temp`, { recursive: true });
},
putFile(source: string, dest: string) {
return executeCommand('docker', ['cp', source, `immich-e2e-server:${dest}`]).promise;
},
async putTextFile(contents: string, dest: string) {
const dir = await mkdtemp(join(tmpdir(), 'test-'));
const fn = join(dir, 'file');
await pipeline(Readable.from(contents), createWriteStream(fn));
return executeCommand('docker', ['cp', fn, `immich-e2e-server:${dest}`]).promise;
},
async move(source: string, dest: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise;
},
async copyFolder(source: string, dest: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'cp', '-r', source, dest]).promise;
},
async deleteFile(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', path]).promise;
},
async deleteFolder(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', '-r', path]).promise;
},
async truncateFolder(path: string) {
return executeCommand('docker', [
'exec',
'immich-e2e-server',
'find',
path,
'-type',
'f',
'-exec',
'truncate',
'-s',
'1',
'{}',
';',
]).promise;
},
resetAdminConfig: async (accessToken: string) => {
const defaultConfig = await getConfigDefaults({ headers: asBearerAuth(accessToken) });
await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) });
},
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseDto) => {
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseLegacyDto) => {
const queues = await getQueuesLegacy({ headers: asBearerAuth(accessToken) });
const jobCounts = queues[queue].jobCounts;
return !jobCounts.active && !jobCounts.waiting;
},
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseDto, ms?: number) => {
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseLegacyDto, ms?: number) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise<void>(async (resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000);

View File

@@ -611,6 +611,53 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
});
test('open /archive, favorite photo, unfavorite', async ({ page }) => {
const assetToFavorite = assets[0];
changes.assetArchivals.push(assetToFavorite.id);
await pageUtils.openArchivePage(page);
const favorite = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.isFavorite === undefined) {
return await route.continue();
}
const isFavorite = requestJson.isFavorite;
if (isFavorite) {
changes.assetFavorites.push(...requestJson.ids);
}
await route.fulfill({
status: 204,
});
});
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
await page.getByLabel('Favorite').click();
await expect(favorite).resolves.toEqual({
isFavorite: true,
ids: [assetToFavorite.id],
});
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1);
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
const unFavoriteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.isFavorite === undefined) {
return await route.continue();
}
changes.assetFavorites = changes.assetFavorites.filter((id) => !requestJson.ids.includes(id));
await route.fulfill({
status: 204,
});
});
await page.getByLabel('Remove from favorites').click();
await expect(unFavoriteRequest).resolves.toEqual({
isFavorite: false,
ids: [assetToFavorite.id],
});
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1);
await thumbnailUtils.expectThumbnailIsNotFavorite(page, assetToFavorite.id);
});
test('open album, archive photo, open album, unarchive', async ({ page }) => {
const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id);
@@ -633,8 +680,7 @@ test.describe('Timeline', () => {
visibility: 'archive',
ids: [assetToArchive.id],
});
console.log('Skipping assertion - TODO - fix that archiving in album doesnt add icon');
// await thumbnail.expectThumbnailIsArchive(page, assetToArchive.id);
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
await page.getByRole('link').getByText('Archive').click();
await timelineUtils.waitForTimelineLoad(page);
@@ -656,8 +702,7 @@ test.describe('Timeline', () => {
visibility: 'timeline',
ids: [assetToArchive.id],
});
console.log('Skipping assertion - TODO - fix bug with not removing asset from timeline-manager after unarchive');
// await expect(thumbnail.withAssetId(page, assetToArchive.id)).toHaveCount(0);
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
await pageUtils.openAlbumPage(page, album.id);
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
});
@@ -712,6 +757,50 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
});
test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
await pageUtils.openFavorites(page);
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
await page.getByLabel('Menu').click();
const archive = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.visibility !== 'archive') {
return await route.continue();
}
await route.fulfill({
status: 204,
});
changes.assetArchivals.push(...requestJson.ids);
});
await page.getByRole('menuitem').getByText('Archive').click();
await expect(archive).resolves.toEqual({
visibility: 'archive',
ids: [assetToArchive.id],
});
await page.getByRole('link').getByText('Archive').click();
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id);
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
const unarchiveRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.visibility !== 'timeline') {
return await route.continue();
}
changes.assetArchivals = changes.assetArchivals.filter((id) => !requestJson.ids.includes(id));
await route.fulfill({
status: 204,
});
});
await page.getByLabel('Unarchive').click();
await expect(unarchiveRequest).resolves.toEqual({
visibility: 'timeline',
ids: [assetToArchive.id],
});
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id);
});
test('Open album, favorite photo, open /favorites, remove favorite, Open album', async ({ page }) => {
const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id);

View File

@@ -105,20 +105,16 @@ export const thumbnailUtils = {
return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, collector));
},
async expectThumbnailIsFavorite(page: Page, assetId: string) {
await expect(
thumbnailUtils
.withAssetId(page, assetId)
.locator(
'path[d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"]',
),
).toHaveCount(1);
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(1);
},
async expectThumbnailIsNotFavorite(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(0);
},
async expectThumbnailIsArchive(page: Page, assetId: string) {
await expect(
thumbnailUtils
.withAssetId(page, assetId)
.locator('path[d="M20 21H4V10H6V19H18V10H20V21M3 3H21V9H3V3M5 5V7H19V5M10.5 11V14H8L12 18L16 14H13.5V11"]'),
).toHaveCount(1);
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(1);
},
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
},
async expectSelectedReadonly(page: Page, assetId: string) {
// todo - need a data attribute for selected
@@ -208,10 +204,18 @@ export const pageUtils = {
await page.goto(`/photos`);
await timelineUtils.waitForTimelineLoad(page);
},
async openFavorites(page: Page) {
await page.goto(`/favorites`);
await timelineUtils.waitForTimelineLoad(page);
},
async openAlbumPage(page: Page, albumId: string) {
await page.goto(`/albums/${albumId}`);
await timelineUtils.waitForTimelineLoad(page);
},
async openArchivePage(page: Page) {
await page.goto(`/archive`);
await timelineUtils.waitForTimelineLoad(page);
},
async deepLinkAlbumPage(page: Page, albumId: string, assetId: string) {
await page.goto(`/albums/${albumId}?at=${assetId}`);
await timelineUtils.waitForTimelineLoad(page);

View File

@@ -54,7 +54,7 @@ test.describe('User Administration', () => {
await page.getByRole('button', { name: 'Edit' }).click();
await expect(page.getByLabel('Admin User')).not.toBeChecked();
await page.getByText('Admin User').click();
await page.getByLabel('Admin User').click();
await expect(page.getByLabel('Admin User')).toBeChecked();
await page.getByRole('button', { name: 'Confirm' }).click();
@@ -83,7 +83,7 @@ test.describe('User Administration', () => {
await page.getByRole('button', { name: 'Edit' }).click();
await expect(page.getByLabel('Admin User')).toBeChecked();
await page.getByText('Admin User').click();
await page.getByLabel('Admin User').click();
await expect(page.getByLabel('Admin User')).not.toBeChecked();
await page.getByRole('button', { name: 'Confirm' }).click();

1
i18n/br.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -177,6 +177,16 @@
"machine_learning_smart_search_enabled": "Enable smart search",
"machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.",
"machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.",
"maintenance_integrity_checksum_mismatch": "Checksum Mismatch",
"maintenance_integrity_checksum_mismatch_job": "Check for checksum mismatches",
"maintenance_integrity_checksum_mismatch_refresh_job": "Refresh checksum mismatch reports",
"maintenance_integrity_missing_file": "Missing Files",
"maintenance_integrity_missing_file_job": "Check for missing files",
"maintenance_integrity_missing_file_refresh_job": "Refresh missing file reports",
"maintenance_integrity_orphan_file": "Orphan Files",
"maintenance_integrity_orphan_file_job": "Check for orphaned files",
"maintenance_integrity_orphan_file_refresh_job": "Refresh orphan file reports",
"maintenance_integrity_report": "Integrity Report",
"maintenance_settings": "Maintenance",
"maintenance_settings_description": "Put Immich into maintenance mode.",
"maintenance_start": "Start maintenance mode",
@@ -407,7 +417,6 @@
"user_restore_scheduled_removal": "Restore user - scheduled removal on {date, date, long}",
"user_settings": "User Settings",
"user_settings_description": "Manage user settings",
"user_successfully_removed": "User {email} has been successfully removed.",
"version_check_enabled_description": "Enable version check",
"version_check_implications": "The version check feature relies on periodic communication with github.com",
"version_check_settings": "Version Check",

1
i18n/eo.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/ga.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/gsw.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/gu.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/is.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/km.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/si.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/uz.json Normal file
View File

@@ -0,0 +1 @@
{}

1
i18n/yue_Hant.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -25,7 +25,7 @@ FROM builder-cpu AS builder-rknn
FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:1f7e92ca7e3a3785680473329ed1091fc99db3e90fcb3a1688f2933e870ed76b AS builder-rocm
# renovate: datasource=github-releases depName=Microsoft/onnxruntime
ARG ONNXRUNTIME_VERSION="v1.20.1"
ARG ONNXRUNTIME_VERSION="v1.22.1"
WORKDIR /code
RUN apt-get update && apt-get install -y --no-install-recommends wget git python3.10-venv

View File

@@ -1,13 +1,13 @@
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index d90a2a355..bb1a7de12 100644
index 2714e6f59..a69da76b4 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -295,7 +295,7 @@ if (onnxruntime_USE_ROCM)
@@ -338,7 +338,7 @@ if (onnxruntime_USE_ROCM)
if (ROCM_VERSION_DEV VERSION_LESS "6.2")
message(FATAL_ERROR "CMAKE_HIP_ARCHITECTURES is not set when ROCm version < 6.2")
else()
- set(CMAKE_HIP_ARCHITECTURES "gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx940;gfx941;gfx942;gfx1200;gfx1201")
+ set(CMAKE_HIP_ARCHITECTURES "gfx900;gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx1102;gfx940;gfx941;gfx942;gfx1200;gfx1201")
endif()
endif()
if (NOT CMAKE_HIP_ARCHITECTURES)
- set(CMAKE_HIP_ARCHITECTURES "gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx940;gfx941;gfx942;gfx1200;gfx1201")
+ set(CMAKE_HIP_ARCHITECTURES "gfx900;gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx1102;gfx940;gfx941;gfx942;gfx1200;gfx1201")
endif()
file(GLOB rocm_cmake_components ${onnxruntime_ROCM_HOME}/lib/cmake/*)

View File

@@ -1,11 +1,12 @@
experimental_monorepo_root = true
[tools]
node = "24.11.0"
node = "24.11.1"
flutter = "3.35.7"
pnpm = "10.20.0"
pnpm = "10.22.0"
terragrunt = "0.91.2"
opentofu = "1.10.6"
java = "25.0.1"
[tools."github:CQLabs/homebrew-dcm"]
version = "1.30.0"

View File

@@ -50,7 +50,7 @@ const double kUploadStatusCanceled = -2.0;
const int kMinMonthsToEnableScrubberSnap = 12;
const String kImmichAppStoreLink = "https://apps.apple.com/app/immich/id6449244941";
const String kImmichAppStoreLink = "https://apps.apple.com/app/immich/id1613945652";
const String kImmichPlayStoreLink = "https://play.google.com/store/apps/details?id=app.alextran.immich";
const String kImmichLatestRelease = "https://github.com/immich-app/immich/releases/latest";

View File

@@ -75,6 +75,20 @@ class AssetService {
isFlipped = false;
}
if (width == null || height == null) {
if (asset.hasRemote) {
final id = asset is LocalAsset ? asset.remoteId! : (asset as RemoteAsset).id;
final remoteAsset = await _remoteAssetRepository.get(id);
width = remoteAsset?.width?.toDouble();
height = remoteAsset?.height?.toDouble();
} else {
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).localId!;
final localAsset = await _localAssetRepository.get(id);
width = localAsset?.width?.toDouble();
height = localAsset?.height?.toDouble();
}
}
final orientedWidth = isFlipped ? height : width;
final orientedHeight = isFlipped ? width : height;
if (orientedWidth != null && orientedHeight != null && orientedHeight > 0) {

View File

@@ -363,14 +363,14 @@ extension on Iterable<PlatformAsset> {
}
}
extension on PlatformAsset {
extension PlatformToLocalAsset on PlatformAsset {
LocalAsset toLocalAsset() => LocalAsset(
id: id,
name: name,
checksum: null,
type: AssetType.values.elementAtOrNull(type) ?? AssetType.other,
createdAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(),
updatedAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(),
updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(),
width: width,
height: height,
durationInSeconds: durationInSeconds,

View File

@@ -261,7 +261,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
durationInSeconds: Value(asset.durationInSeconds),
id: asset.id,
orientation: Value(asset.orientation),
checksum: const Value(null),
isFavorite: Value(asset.isFavorite),
);
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(

View File

@@ -265,7 +265,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
row.deletedAt.isNull() &
row.isFavorite.equals(true) &
row.ownerId.equals(userId) &
row.visibility.equalsValue(AssetVisibility.timeline),
(row.visibility.equalsValue(AssetVisibility.timeline) | row.visibility.equalsValue(AssetVisibility.archive)),
groupBy: groupBy,
origin: TimelineOrigin.favorite,
);

View File

@@ -24,6 +24,16 @@ class DriftMemoryPage extends HookConsumerWidget {
const DriftMemoryPage({required this.memories, required this.memoryIndex, super.key});
static void setMemory(WidgetRef ref, DriftMemory memory) {
if (memory.assets.isNotEmpty) {
ref.read(currentAssetNotifier.notifier).setAsset(memory.assets.first);
if (memory.assets.first.isVideo) {
ref.read(videoPlaybackValueProvider.notifier).reset();
}
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentMemory = useState(memories[memoryIndex]);
@@ -202,6 +212,10 @@ class DriftMemoryPage extends HookConsumerWidget {
if (pageNumber < memories.length) {
currentMemoryIndex.value = pageNumber;
currentMemory.value = memories[pageNumber];
WidgetsBinding.instance.addPostFrameCallback((_) {
DriftMemoryPage.setMemory(ref, memories[pageNumber]);
});
}
currentAssetPage.value = 0;

View File

@@ -77,6 +77,7 @@ class AddActionButton extends ConsumerWidget {
color: context.themeData.scaffoldBackgroundColor,
position: _menuPosition(context),
items: items,
popUpAnimationStyle: AnimationStyle.noAnimation,
);
if (selected == null) {

View File

@@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/pages/drift_memory.page.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -31,16 +30,9 @@ class DriftMemoryLane extends ConsumerWidget {
overlayColor: WidgetStateProperty.all(Colors.white.withValues(alpha: 0.1)),
onTap: (index) {
ref.read(hapticFeedbackProvider.notifier).heavyImpact();
if (memories[index].assets.isNotEmpty) {
final asset = memories[index].assets[0];
ref.read(currentAssetNotifier.notifier).setAsset(asset);
if (asset.isVideo) {
ref.read(videoPlaybackValueProvider.notifier).reset();
}
DriftMemoryPage.setMemory(ref, memories[index]);
}
context.pushRoute(DriftMemoryRoute(memories: memories, memoryIndex: index));
},
children: memories

View File

@@ -22,14 +22,16 @@ import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/datetime_helpers.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:isar/isar.dart';
// ignore: import_rule_photo_manager
import 'package:photo_manager/photo_manager.dart';
const int targetVersion = 18;
const int targetVersion = 19;
Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
final hasVersion = Store.tryGet(StoreKey.version) != null;
@@ -78,6 +80,12 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
await Store.put(StoreKey.shouldResetSync, true);
}
if (version < 19 && Store.isBetaTimelineEnabled) {
if (!await _populateUpdatedAtTime(drift)) {
return;
}
}
if (targetVersion >= 12) {
await Store.put(StoreKey.version, targetVersion);
return;
@@ -221,6 +229,32 @@ Future<void> _migrateDeviceAsset(Isar db) async {
});
}
Future<bool> _populateUpdatedAtTime(Drift db) async {
try {
final nativeApi = NativeSyncApi();
final albums = await nativeApi.getAlbums();
for (final album in albums) {
final assets = await nativeApi.getAssetsForAlbum(album.id);
await db.batch((batch) async {
for (final asset in assets) {
batch.update(
db.localAssetEntity,
LocalAssetEntityCompanion(
updatedAt: Value(tryFromSecondsSinceEpoch(asset.updatedAt, isUtc: true) ?? DateTime.timestamp()),
),
where: (t) => t.id.equals(asset.id),
);
}
});
}
return true;
} catch (error) {
dPrint(() => "[MIGRATION] Error while populating updatedAt time: $error");
return false;
}
}
Future<void> migrateDeviceAssetToSqlite(Isar db, Drift drift) async {
try {
final isarDeviceAssets = await db.deviceAssetEntitys.where().findAll();

View File

@@ -137,8 +137,10 @@ Class | Method | HTTP request | Description
*DeprecatedApi* | [**getAllUserAssetsByDeviceId**](doc//DeprecatedApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
*DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user
*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate
@@ -159,6 +161,11 @@ Class | Method | HTTP request | Description
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
*MaintenanceAdminApi* | [**deleteIntegrityReport**](doc//MaintenanceAdminApi.md#deleteintegrityreport) | **DELETE** /admin/maintenance/integrity/report/{id} | Delete report entry and perform corresponding deletion action
*MaintenanceAdminApi* | [**getIntegrityReport**](doc//MaintenanceAdminApi.md#getintegrityreport) | **POST** /admin/maintenance/integrity/report | Get integrity report by type
*MaintenanceAdminApi* | [**getIntegrityReportCsv**](doc//MaintenanceAdminApi.md#getintegrityreportcsv) | **GET** /admin/maintenance/integrity/report/{type}/csv | Export integrity report by type as CSV
*MaintenanceAdminApi* | [**getIntegrityReportFile**](doc//MaintenanceAdminApi.md#getintegrityreportfile) | **GET** /admin/maintenance/integrity/report/{id}/file | Download the orphan/broken file if one exists
*MaintenanceAdminApi* | [**getIntegrityReportSummary**](doc//MaintenanceAdminApi.md#getintegrityreportsummary) | **GET** /admin/maintenance/integrity/summary | Get integrity report summary
*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode
*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers
@@ -198,6 +205,11 @@ Class | Method | HTTP request | Description
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue
*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue
*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs
*QueuesApi* | [**getQueues**](doc//QueuesApi.md#getqueues) | **GET** /queues | List all queues
*QueuesApi* | [**updateQueue**](doc//QueuesApi.md#updatequeue) | **PUT** /queues/{name} | Update a queue
*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | Retrieve assets by city
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | Retrieve explore data
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | Retrieve search suggestions
@@ -395,7 +407,9 @@ Class | Method | HTTP request | Description
- [FoldersResponse](doc//FoldersResponse.md)
- [FoldersUpdate](doc//FoldersUpdate.md)
- [ImageFormat](doc//ImageFormat.md)
- [IntegrityReportType](doc//IntegrityReportType.md)
- [JobCreateDto](doc//JobCreateDto.md)
- [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md)
- [LibraryResponseDto](doc//LibraryResponseDto.md)
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
@@ -408,6 +422,10 @@ Class | Method | HTTP request | Description
- [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md)
- [MaintenanceAction](doc//MaintenanceAction.md)
- [MaintenanceAuthDto](doc//MaintenanceAuthDto.md)
- [MaintenanceGetIntegrityReportDto](doc//MaintenanceGetIntegrityReportDto.md)
- [MaintenanceIntegrityReportDto](doc//MaintenanceIntegrityReportDto.md)
- [MaintenanceIntegrityReportResponseDto](doc//MaintenanceIntegrityReportResponseDto.md)
- [MaintenanceIntegrityReportSummaryResponseDto](doc//MaintenanceIntegrityReportSummaryResponseDto.md)
- [MaintenanceLoginDto](doc//MaintenanceLoginDto.md)
- [ManualJobName](doc//ManualJobName.md)
- [MapMarkerResponseDto](doc//MapMarkerResponseDto.md)
@@ -465,11 +483,16 @@ Class | Method | HTTP request | Description
- [PurchaseUpdate](doc//PurchaseUpdate.md)
- [QueueCommand](doc//QueueCommand.md)
- [QueueCommandDto](doc//QueueCommandDto.md)
- [QueueDeleteDto](doc//QueueDeleteDto.md)
- [QueueJobResponseDto](doc//QueueJobResponseDto.md)
- [QueueJobStatus](doc//QueueJobStatus.md)
- [QueueName](doc//QueueName.md)
- [QueueResponseDto](doc//QueueResponseDto.md)
- [QueueResponseLegacyDto](doc//QueueResponseLegacyDto.md)
- [QueueStatisticsDto](doc//QueueStatisticsDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [QueuesResponseDto](doc//QueuesResponseDto.md)
- [QueueStatusLegacyDto](doc//QueueStatusLegacyDto.md)
- [QueueUpdateDto](doc//QueueUpdateDto.md)
- [QueuesResponseLegacyDto](doc//QueuesResponseLegacyDto.md)
- [RandomSearchDto](doc//RandomSearchDto.md)
- [RatingsResponse](doc//RatingsResponse.md)
- [RatingsUpdate](doc//RatingsUpdate.md)
@@ -556,6 +579,9 @@ Class | Method | HTTP request | Description
- [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md)
- [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md)
- [SystemConfigImageDto](doc//SystemConfigImageDto.md)
- [SystemConfigIntegrityChecks](doc//SystemConfigIntegrityChecks.md)
- [SystemConfigIntegrityChecksumJob](doc//SystemConfigIntegrityChecksumJob.md)
- [SystemConfigIntegrityJob](doc//SystemConfigIntegrityJob.md)
- [SystemConfigJobDto](doc//SystemConfigJobDto.md)
- [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md)
- [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md)

View File

@@ -50,6 +50,7 @@ part 'api/notifications_admin_api.dart';
part 'api/partners_api.dart';
part 'api/people_api.dart';
part 'api/plugins_api.dart';
part 'api/queues_api.dart';
part 'api/search_api.dart';
part 'api/server_api.dart';
part 'api/sessions_api.dart';
@@ -153,7 +154,9 @@ part 'model/facial_recognition_config.dart';
part 'model/folders_response.dart';
part 'model/folders_update.dart';
part 'model/image_format.dart';
part 'model/integrity_report_type.dart';
part 'model/job_create_dto.dart';
part 'model/job_name.dart';
part 'model/job_settings_dto.dart';
part 'model/library_response_dto.dart';
part 'model/library_stats_response_dto.dart';
@@ -166,6 +169,10 @@ part 'model/logout_response_dto.dart';
part 'model/machine_learning_availability_checks_dto.dart';
part 'model/maintenance_action.dart';
part 'model/maintenance_auth_dto.dart';
part 'model/maintenance_get_integrity_report_dto.dart';
part 'model/maintenance_integrity_report_dto.dart';
part 'model/maintenance_integrity_report_response_dto.dart';
part 'model/maintenance_integrity_report_summary_response_dto.dart';
part 'model/maintenance_login_dto.dart';
part 'model/manual_job_name.dart';
part 'model/map_marker_response_dto.dart';
@@ -223,11 +230,16 @@ part 'model/purchase_response.dart';
part 'model/purchase_update.dart';
part 'model/queue_command.dart';
part 'model/queue_command_dto.dart';
part 'model/queue_delete_dto.dart';
part 'model/queue_job_response_dto.dart';
part 'model/queue_job_status.dart';
part 'model/queue_name.dart';
part 'model/queue_response_dto.dart';
part 'model/queue_response_legacy_dto.dart';
part 'model/queue_statistics_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/queues_response_dto.dart';
part 'model/queue_status_legacy_dto.dart';
part 'model/queue_update_dto.dart';
part 'model/queues_response_legacy_dto.dart';
part 'model/random_search_dto.dart';
part 'model/ratings_response.dart';
part 'model/ratings_update.dart';
@@ -314,6 +326,9 @@ part 'model/system_config_faces_dto.dart';
part 'model/system_config_generated_fullsize_image_dto.dart';
part 'model/system_config_generated_image_dto.dart';
part 'model/system_config_image_dto.dart';
part 'model/system_config_integrity_checks.dart';
part 'model/system_config_integrity_checksum_job.dart';
part 'model/system_config_integrity_job.dart';
part 'model/system_config_job_dto.dart';
part 'model/system_config_library_dto.dart';
part 'model/system_config_library_scan_dto.dart';

View File

@@ -248,6 +248,54 @@ class DeprecatedApi {
return null;
}
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getQueuesLegacyWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseLegacyDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto;
}
return null;
}
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
@@ -444,4 +492,65 @@ class DeprecatedApi {
}
return null;
}
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueCommandDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto;
}
return null;
}
}

View File

@@ -97,7 +97,7 @@ class JobsApi {
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseDto?> getQueuesLegacy() async {
Future<QueuesResponseLegacyDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -106,7 +106,7 @@ class JobsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseDto',) as QueuesResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto;
}
return null;
@@ -158,7 +158,7 @@ class JobsApi {
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -167,7 +167,7 @@ class JobsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto;
}
return null;

View File

@@ -16,6 +16,273 @@ class MaintenanceAdminApi {
final ApiClient apiClient;
/// Delete report entry and perform corresponding deletion action
///
/// ...
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteIntegrityReportWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/integrity/report/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Delete report entry and perform corresponding deletion action
///
/// ...
///
/// Parameters:
///
/// * [String] id (required):
Future<void> deleteIntegrityReport(String id,) async {
final response = await deleteIntegrityReportWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Get integrity report by type
///
/// ...
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [MaintenanceGetIntegrityReportDto] maintenanceGetIntegrityReportDto (required):
Future<Response> getIntegrityReportWithHttpInfo(MaintenanceGetIntegrityReportDto maintenanceGetIntegrityReportDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/integrity/report';
// ignore: prefer_final_locals
Object? postBody = maintenanceGetIntegrityReportDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get integrity report by type
///
/// ...
///
/// Parameters:
///
/// * [MaintenanceGetIntegrityReportDto] maintenanceGetIntegrityReportDto (required):
Future<MaintenanceIntegrityReportResponseDto?> getIntegrityReport(MaintenanceGetIntegrityReportDto maintenanceGetIntegrityReportDto,) async {
final response = await getIntegrityReportWithHttpInfo(maintenanceGetIntegrityReportDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceIntegrityReportResponseDto',) as MaintenanceIntegrityReportResponseDto;
}
return null;
}
/// Export integrity report by type as CSV
///
/// ...
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [IntegrityReportType] type (required):
Future<Response> getIntegrityReportCsvWithHttpInfo(IntegrityReportType type,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/integrity/report/{type}/csv'
.replaceAll('{type}', type.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Export integrity report by type as CSV
///
/// ...
///
/// Parameters:
///
/// * [IntegrityReportType] type (required):
Future<MultipartFile?> getIntegrityReportCsv(IntegrityReportType type,) async {
final response = await getIntegrityReportCsvWithHttpInfo(type,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
}
/// Download the orphan/broken file if one exists
///
/// ...
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> getIntegrityReportFileWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/integrity/report/{id}/file'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Download the orphan/broken file if one exists
///
/// ...
///
/// Parameters:
///
/// * [String] id (required):
Future<MultipartFile?> getIntegrityReportFile(String id,) async {
final response = await getIntegrityReportFileWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
}
/// Get integrity report summary
///
/// ...
///
/// Note: This method returns the HTTP [Response].
Future<Response> getIntegrityReportSummaryWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/integrity/summary';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get integrity report summary
///
/// ...
Future<MaintenanceIntegrityReportSummaryResponseDto?> getIntegrityReportSummary() async {
final response = await getIntegrityReportSummaryWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceIntegrityReportSummaryResponseDto',) as MaintenanceIntegrityReportSummaryResponseDto;
}
return null;
}
/// Log into maintenance mode
///
/// Login with maintenance token or cookie to receive current information and perform further actions.

308
mobile/openapi/lib/api/queues_api.dart generated Normal file
View File

@@ -0,0 +1,308 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueuesApi {
QueuesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Empty a queue
///
/// Removes all jobs from the specified queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueDeleteDto] queueDeleteDto (required):
Future<Response> emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}/jobs'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueDeleteDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Empty a queue
///
/// Removes all jobs from the specified queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueDeleteDto] queueDeleteDto (required):
Future<void> emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async {
final response = await emptyQueueWithHttpInfo(name, queueDeleteDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Retrieve a queue
///
/// Retrieves a specific queue by its name.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
Future<Response> getQueueWithHttpInfo(QueueName name,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve a queue
///
/// Retrieves a specific queue by its name.
///
/// Parameters:
///
/// * [QueueName] name (required):
Future<QueueResponseDto?> getQueue(QueueName name,) async {
final response = await getQueueWithHttpInfo(name,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
}
/// Retrieve queue jobs
///
/// Retrieves a list of queue jobs from the specified queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [List<QueueJobStatus>] status:
Future<Response> getQueueJobsWithHttpInfo(QueueName name, { List<QueueJobStatus>? status, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}/jobs'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (status != null) {
queryParams.addAll(_queryParams('multi', 'status', status));
}
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve queue jobs
///
/// Retrieves a list of queue jobs from the specified queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [List<QueueJobStatus>] status:
Future<List<QueueJobResponseDto>?> getQueueJobs(QueueName name, { List<QueueJobStatus>? status, }) async {
final response = await getQueueJobsWithHttpInfo(name, status: status, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<QueueJobResponseDto>') as List)
.cast<QueueJobResponseDto>()
.toList(growable: false);
}
return null;
}
/// List all queues
///
/// Retrieves a list of queues.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getQueuesWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/queues';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// List all queues
///
/// Retrieves a list of queues.
Future<List<QueueResponseDto>?> getQueues() async {
final response = await getQueuesWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<QueueResponseDto>') as List)
.cast<QueueResponseDto>()
.toList(growable: false);
}
return null;
}
/// Update a queue
///
/// Change the paused status of a specific queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueUpdateDto] queueUpdateDto (required):
Future<Response> updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Update a queue
///
/// Change the paused status of a specific queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueUpdateDto] queueUpdateDto (required):
Future<QueueResponseDto?> updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async {
final response = await updateQueueWithHttpInfo(name, queueUpdateDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
}
}

View File

@@ -356,8 +356,12 @@ class ApiClient {
return FoldersUpdate.fromJson(value);
case 'ImageFormat':
return ImageFormatTypeTransformer().decode(value);
case 'IntegrityReportType':
return IntegrityReportTypeTypeTransformer().decode(value);
case 'JobCreateDto':
return JobCreateDto.fromJson(value);
case 'JobName':
return JobNameTypeTransformer().decode(value);
case 'JobSettingsDto':
return JobSettingsDto.fromJson(value);
case 'LibraryResponseDto':
@@ -382,6 +386,14 @@ class ApiClient {
return MaintenanceActionTypeTransformer().decode(value);
case 'MaintenanceAuthDto':
return MaintenanceAuthDto.fromJson(value);
case 'MaintenanceGetIntegrityReportDto':
return MaintenanceGetIntegrityReportDto.fromJson(value);
case 'MaintenanceIntegrityReportDto':
return MaintenanceIntegrityReportDto.fromJson(value);
case 'MaintenanceIntegrityReportResponseDto':
return MaintenanceIntegrityReportResponseDto.fromJson(value);
case 'MaintenanceIntegrityReportSummaryResponseDto':
return MaintenanceIntegrityReportSummaryResponseDto.fromJson(value);
case 'MaintenanceLoginDto':
return MaintenanceLoginDto.fromJson(value);
case 'ManualJobName':
@@ -496,16 +508,26 @@ class ApiClient {
return QueueCommandTypeTransformer().decode(value);
case 'QueueCommandDto':
return QueueCommandDto.fromJson(value);
case 'QueueDeleteDto':
return QueueDeleteDto.fromJson(value);
case 'QueueJobResponseDto':
return QueueJobResponseDto.fromJson(value);
case 'QueueJobStatus':
return QueueJobStatusTypeTransformer().decode(value);
case 'QueueName':
return QueueNameTypeTransformer().decode(value);
case 'QueueResponseDto':
return QueueResponseDto.fromJson(value);
case 'QueueResponseLegacyDto':
return QueueResponseLegacyDto.fromJson(value);
case 'QueueStatisticsDto':
return QueueStatisticsDto.fromJson(value);
case 'QueueStatusDto':
return QueueStatusDto.fromJson(value);
case 'QueuesResponseDto':
return QueuesResponseDto.fromJson(value);
case 'QueueStatusLegacyDto':
return QueueStatusLegacyDto.fromJson(value);
case 'QueueUpdateDto':
return QueueUpdateDto.fromJson(value);
case 'QueuesResponseLegacyDto':
return QueuesResponseLegacyDto.fromJson(value);
case 'RandomSearchDto':
return RandomSearchDto.fromJson(value);
case 'RatingsResponse':
@@ -678,6 +700,12 @@ class ApiClient {
return SystemConfigGeneratedImageDto.fromJson(value);
case 'SystemConfigImageDto':
return SystemConfigImageDto.fromJson(value);
case 'SystemConfigIntegrityChecks':
return SystemConfigIntegrityChecks.fromJson(value);
case 'SystemConfigIntegrityChecksumJob':
return SystemConfigIntegrityChecksumJob.fromJson(value);
case 'SystemConfigIntegrityJob':
return SystemConfigIntegrityJob.fromJson(value);
case 'SystemConfigJobDto':
return SystemConfigJobDto.fromJson(value);
case 'SystemConfigLibraryDto':

View File

@@ -94,6 +94,12 @@ String parameterToString(dynamic value) {
if (value is ImageFormat) {
return ImageFormatTypeTransformer().encode(value).toString();
}
if (value is IntegrityReportType) {
return IntegrityReportTypeTypeTransformer().encode(value).toString();
}
if (value is JobName) {
return JobNameTypeTransformer().encode(value).toString();
}
if (value is LogLevel) {
return LogLevelTypeTransformer().encode(value).toString();
}
@@ -133,6 +139,9 @@ String parameterToString(dynamic value) {
if (value is QueueCommand) {
return QueueCommandTypeTransformer().encode(value).toString();
}
if (value is QueueJobStatus) {
return QueueJobStatusTypeTransformer().encode(value).toString();
}
if (value is QueueName) {
return QueueNameTypeTransformer().encode(value).toString();
}

View File

@@ -0,0 +1,88 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class IntegrityReportType {
/// Instantiate a new enum with the provided [value].
const IntegrityReportType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const orphanFile = IntegrityReportType._(r'orphan_file');
static const missingFile = IntegrityReportType._(r'missing_file');
static const checksumMismatch = IntegrityReportType._(r'checksum_mismatch');
/// List of all possible values in this [enum][IntegrityReportType].
static const values = <IntegrityReportType>[
orphanFile,
missingFile,
checksumMismatch,
];
static IntegrityReportType? fromJson(dynamic value) => IntegrityReportTypeTypeTransformer().decode(value);
static List<IntegrityReportType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <IntegrityReportType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = IntegrityReportType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [IntegrityReportType] to String,
/// and [decode] dynamic data back to [IntegrityReportType].
class IntegrityReportTypeTypeTransformer {
factory IntegrityReportTypeTypeTransformer() => _instance ??= const IntegrityReportTypeTypeTransformer._();
const IntegrityReportTypeTypeTransformer._();
String encode(IntegrityReportType data) => data.value;
/// Decodes a [dynamic value][data] to a IntegrityReportType.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
IntegrityReportType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'orphan_file': return IntegrityReportType.orphanFile;
case r'missing_file': return IntegrityReportType.missingFile;
case r'checksum_mismatch': return IntegrityReportType.checksumMismatch;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [IntegrityReportTypeTypeTransformer] instance.
static IntegrityReportTypeTypeTransformer? _instance;
}

271
mobile/openapi/lib/model/job_name.dart generated Normal file
View File

@@ -0,0 +1,271 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class JobName {
/// Instantiate a new enum with the provided [value].
const JobName._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const assetDelete = JobName._(r'AssetDelete');
static const assetDeleteCheck = JobName._(r'AssetDeleteCheck');
static const assetDetectFacesQueueAll = JobName._(r'AssetDetectFacesQueueAll');
static const assetDetectFaces = JobName._(r'AssetDetectFaces');
static const assetDetectDuplicatesQueueAll = JobName._(r'AssetDetectDuplicatesQueueAll');
static const assetDetectDuplicates = JobName._(r'AssetDetectDuplicates');
static const assetEncodeVideoQueueAll = JobName._(r'AssetEncodeVideoQueueAll');
static const assetEncodeVideo = JobName._(r'AssetEncodeVideo');
static const assetEmptyTrash = JobName._(r'AssetEmptyTrash');
static const assetExtractMetadataQueueAll = JobName._(r'AssetExtractMetadataQueueAll');
static const assetExtractMetadata = JobName._(r'AssetExtractMetadata');
static const assetFileMigration = JobName._(r'AssetFileMigration');
static const assetGenerateThumbnailsQueueAll = JobName._(r'AssetGenerateThumbnailsQueueAll');
static const assetGenerateThumbnails = JobName._(r'AssetGenerateThumbnails');
static const auditLogCleanup = JobName._(r'AuditLogCleanup');
static const auditTableCleanup = JobName._(r'AuditTableCleanup');
static const databaseBackup = JobName._(r'DatabaseBackup');
static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll');
static const facialRecognition = JobName._(r'FacialRecognition');
static const fileDelete = JobName._(r'FileDelete');
static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll');
static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck');
static const libraryDelete = JobName._(r'LibraryDelete');
static const libraryRemoveAsset = JobName._(r'LibraryRemoveAsset');
static const libraryScanAssetsQueueAll = JobName._(r'LibraryScanAssetsQueueAll');
static const librarySyncAssets = JobName._(r'LibrarySyncAssets');
static const librarySyncFilesQueueAll = JobName._(r'LibrarySyncFilesQueueAll');
static const librarySyncFiles = JobName._(r'LibrarySyncFiles');
static const libraryScanQueueAll = JobName._(r'LibraryScanQueueAll');
static const memoryCleanup = JobName._(r'MemoryCleanup');
static const memoryGenerate = JobName._(r'MemoryGenerate');
static const notificationsCleanup = JobName._(r'NotificationsCleanup');
static const notifyUserSignup = JobName._(r'NotifyUserSignup');
static const notifyAlbumInvite = JobName._(r'NotifyAlbumInvite');
static const notifyAlbumUpdate = JobName._(r'NotifyAlbumUpdate');
static const userDelete = JobName._(r'UserDelete');
static const userDeleteCheck = JobName._(r'UserDeleteCheck');
static const userSyncUsage = JobName._(r'UserSyncUsage');
static const personCleanup = JobName._(r'PersonCleanup');
static const personFileMigration = JobName._(r'PersonFileMigration');
static const personGenerateThumbnail = JobName._(r'PersonGenerateThumbnail');
static const sessionCleanup = JobName._(r'SessionCleanup');
static const sendMail = JobName._(r'SendMail');
static const sidecarQueueAll = JobName._(r'SidecarQueueAll');
static const sidecarCheck = JobName._(r'SidecarCheck');
static const sidecarWrite = JobName._(r'SidecarWrite');
static const smartSearchQueueAll = JobName._(r'SmartSearchQueueAll');
static const smartSearch = JobName._(r'SmartSearch');
static const storageTemplateMigration = JobName._(r'StorageTemplateMigration');
static const storageTemplateMigrationSingle = JobName._(r'StorageTemplateMigrationSingle');
static const tagCleanup = JobName._(r'TagCleanup');
static const versionCheck = JobName._(r'VersionCheck');
static const ocrQueueAll = JobName._(r'OcrQueueAll');
static const ocr = JobName._(r'Ocr');
static const workflowRun = JobName._(r'WorkflowRun');
static const integrityOrphanedFilesQueueAll = JobName._(r'IntegrityOrphanedFilesQueueAll');
static const integrityOrphanedFiles = JobName._(r'IntegrityOrphanedFiles');
static const integrityOrphanedRefresh = JobName._(r'IntegrityOrphanedRefresh');
static const integrityMissingFilesQueueAll = JobName._(r'IntegrityMissingFilesQueueAll');
static const integrityMissingFiles = JobName._(r'IntegrityMissingFiles');
static const integrityMissingFilesRefresh = JobName._(r'IntegrityMissingFilesRefresh');
static const integrityChecksumFiles = JobName._(r'IntegrityChecksumFiles');
static const integrityChecksumFilesRefresh = JobName._(r'IntegrityChecksumFilesRefresh');
static const integrityReportDelete = JobName._(r'IntegrityReportDelete');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
assetDelete,
assetDeleteCheck,
assetDetectFacesQueueAll,
assetDetectFaces,
assetDetectDuplicatesQueueAll,
assetDetectDuplicates,
assetEncodeVideoQueueAll,
assetEncodeVideo,
assetEmptyTrash,
assetExtractMetadataQueueAll,
assetExtractMetadata,
assetFileMigration,
assetGenerateThumbnailsQueueAll,
assetGenerateThumbnails,
auditLogCleanup,
auditTableCleanup,
databaseBackup,
facialRecognitionQueueAll,
facialRecognition,
fileDelete,
fileMigrationQueueAll,
libraryDeleteCheck,
libraryDelete,
libraryRemoveAsset,
libraryScanAssetsQueueAll,
librarySyncAssets,
librarySyncFilesQueueAll,
librarySyncFiles,
libraryScanQueueAll,
memoryCleanup,
memoryGenerate,
notificationsCleanup,
notifyUserSignup,
notifyAlbumInvite,
notifyAlbumUpdate,
userDelete,
userDeleteCheck,
userSyncUsage,
personCleanup,
personFileMigration,
personGenerateThumbnail,
sessionCleanup,
sendMail,
sidecarQueueAll,
sidecarCheck,
sidecarWrite,
smartSearchQueueAll,
smartSearch,
storageTemplateMigration,
storageTemplateMigrationSingle,
tagCleanup,
versionCheck,
ocrQueueAll,
ocr,
workflowRun,
integrityOrphanedFilesQueueAll,
integrityOrphanedFiles,
integrityOrphanedRefresh,
integrityMissingFilesQueueAll,
integrityMissingFiles,
integrityMissingFilesRefresh,
integrityChecksumFiles,
integrityChecksumFilesRefresh,
integrityReportDelete,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
static List<JobName> listFromJson(dynamic json, {bool growable = false,}) {
final result = <JobName>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = JobName.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [JobName] to String,
/// and [decode] dynamic data back to [JobName].
class JobNameTypeTransformer {
factory JobNameTypeTransformer() => _instance ??= const JobNameTypeTransformer._();
const JobNameTypeTransformer._();
String encode(JobName data) => data.value;
/// Decodes a [dynamic value][data] to a JobName.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
JobName? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'AssetDelete': return JobName.assetDelete;
case r'AssetDeleteCheck': return JobName.assetDeleteCheck;
case r'AssetDetectFacesQueueAll': return JobName.assetDetectFacesQueueAll;
case r'AssetDetectFaces': return JobName.assetDetectFaces;
case r'AssetDetectDuplicatesQueueAll': return JobName.assetDetectDuplicatesQueueAll;
case r'AssetDetectDuplicates': return JobName.assetDetectDuplicates;
case r'AssetEncodeVideoQueueAll': return JobName.assetEncodeVideoQueueAll;
case r'AssetEncodeVideo': return JobName.assetEncodeVideo;
case r'AssetEmptyTrash': return JobName.assetEmptyTrash;
case r'AssetExtractMetadataQueueAll': return JobName.assetExtractMetadataQueueAll;
case r'AssetExtractMetadata': return JobName.assetExtractMetadata;
case r'AssetFileMigration': return JobName.assetFileMigration;
case r'AssetGenerateThumbnailsQueueAll': return JobName.assetGenerateThumbnailsQueueAll;
case r'AssetGenerateThumbnails': return JobName.assetGenerateThumbnails;
case r'AuditLogCleanup': return JobName.auditLogCleanup;
case r'AuditTableCleanup': return JobName.auditTableCleanup;
case r'DatabaseBackup': return JobName.databaseBackup;
case r'FacialRecognitionQueueAll': return JobName.facialRecognitionQueueAll;
case r'FacialRecognition': return JobName.facialRecognition;
case r'FileDelete': return JobName.fileDelete;
case r'FileMigrationQueueAll': return JobName.fileMigrationQueueAll;
case r'LibraryDeleteCheck': return JobName.libraryDeleteCheck;
case r'LibraryDelete': return JobName.libraryDelete;
case r'LibraryRemoveAsset': return JobName.libraryRemoveAsset;
case r'LibraryScanAssetsQueueAll': return JobName.libraryScanAssetsQueueAll;
case r'LibrarySyncAssets': return JobName.librarySyncAssets;
case r'LibrarySyncFilesQueueAll': return JobName.librarySyncFilesQueueAll;
case r'LibrarySyncFiles': return JobName.librarySyncFiles;
case r'LibraryScanQueueAll': return JobName.libraryScanQueueAll;
case r'MemoryCleanup': return JobName.memoryCleanup;
case r'MemoryGenerate': return JobName.memoryGenerate;
case r'NotificationsCleanup': return JobName.notificationsCleanup;
case r'NotifyUserSignup': return JobName.notifyUserSignup;
case r'NotifyAlbumInvite': return JobName.notifyAlbumInvite;
case r'NotifyAlbumUpdate': return JobName.notifyAlbumUpdate;
case r'UserDelete': return JobName.userDelete;
case r'UserDeleteCheck': return JobName.userDeleteCheck;
case r'UserSyncUsage': return JobName.userSyncUsage;
case r'PersonCleanup': return JobName.personCleanup;
case r'PersonFileMigration': return JobName.personFileMigration;
case r'PersonGenerateThumbnail': return JobName.personGenerateThumbnail;
case r'SessionCleanup': return JobName.sessionCleanup;
case r'SendMail': return JobName.sendMail;
case r'SidecarQueueAll': return JobName.sidecarQueueAll;
case r'SidecarCheck': return JobName.sidecarCheck;
case r'SidecarWrite': return JobName.sidecarWrite;
case r'SmartSearchQueueAll': return JobName.smartSearchQueueAll;
case r'SmartSearch': return JobName.smartSearch;
case r'StorageTemplateMigration': return JobName.storageTemplateMigration;
case r'StorageTemplateMigrationSingle': return JobName.storageTemplateMigrationSingle;
case r'TagCleanup': return JobName.tagCleanup;
case r'VersionCheck': return JobName.versionCheck;
case r'OcrQueueAll': return JobName.ocrQueueAll;
case r'Ocr': return JobName.ocr;
case r'WorkflowRun': return JobName.workflowRun;
case r'IntegrityOrphanedFilesQueueAll': return JobName.integrityOrphanedFilesQueueAll;
case r'IntegrityOrphanedFiles': return JobName.integrityOrphanedFiles;
case r'IntegrityOrphanedRefresh': return JobName.integrityOrphanedRefresh;
case r'IntegrityMissingFilesQueueAll': return JobName.integrityMissingFilesQueueAll;
case r'IntegrityMissingFiles': return JobName.integrityMissingFiles;
case r'IntegrityMissingFilesRefresh': return JobName.integrityMissingFilesRefresh;
case r'IntegrityChecksumFiles': return JobName.integrityChecksumFiles;
case r'IntegrityChecksumFilesRefresh': return JobName.integrityChecksumFilesRefresh;
case r'IntegrityReportDelete': return JobName.integrityReportDelete;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [JobNameTypeTransformer] instance.
static JobNameTypeTransformer? _instance;
}

View File

@@ -0,0 +1,99 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MaintenanceGetIntegrityReportDto {
/// Returns a new [MaintenanceGetIntegrityReportDto] instance.
MaintenanceGetIntegrityReportDto({
required this.type,
});
IntegrityReportType type;
@override
bool operator ==(Object other) => identical(this, other) || other is MaintenanceGetIntegrityReportDto &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(type.hashCode);
@override
String toString() => 'MaintenanceGetIntegrityReportDto[type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'type'] = this.type;
return json;
}
/// Returns a new [MaintenanceGetIntegrityReportDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MaintenanceGetIntegrityReportDto? fromJson(dynamic value) {
upgradeDto(value, "MaintenanceGetIntegrityReportDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return MaintenanceGetIntegrityReportDto(
type: IntegrityReportType.fromJson(json[r'type'])!,
);
}
return null;
}
static List<MaintenanceGetIntegrityReportDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceGetIntegrityReportDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MaintenanceGetIntegrityReportDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MaintenanceGetIntegrityReportDto> mapFromJson(dynamic json) {
final map = <String, MaintenanceGetIntegrityReportDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MaintenanceGetIntegrityReportDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MaintenanceGetIntegrityReportDto-objects as value to a dart map
static Map<String, List<MaintenanceGetIntegrityReportDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MaintenanceGetIntegrityReportDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MaintenanceGetIntegrityReportDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'type',
};
}

View File

@@ -0,0 +1,115 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MaintenanceIntegrityReportDto {
/// Returns a new [MaintenanceIntegrityReportDto] instance.
MaintenanceIntegrityReportDto({
required this.id,
required this.path,
required this.type,
});
String id;
String path;
IntegrityReportType type;
@override
bool operator ==(Object other) => identical(this, other) || other is MaintenanceIntegrityReportDto &&
other.id == id &&
other.path == path &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(path.hashCode) +
(type.hashCode);
@override
String toString() => 'MaintenanceIntegrityReportDto[id=$id, path=$path, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'id'] = this.id;
json[r'path'] = this.path;
json[r'type'] = this.type;
return json;
}
/// Returns a new [MaintenanceIntegrityReportDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MaintenanceIntegrityReportDto? fromJson(dynamic value) {
upgradeDto(value, "MaintenanceIntegrityReportDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return MaintenanceIntegrityReportDto(
id: mapValueOfType<String>(json, r'id')!,
path: mapValueOfType<String>(json, r'path')!,
type: IntegrityReportType.fromJson(json[r'type'])!,
);
}
return null;
}
static List<MaintenanceIntegrityReportDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceIntegrityReportDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MaintenanceIntegrityReportDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MaintenanceIntegrityReportDto> mapFromJson(dynamic json) {
final map = <String, MaintenanceIntegrityReportDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MaintenanceIntegrityReportDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MaintenanceIntegrityReportDto-objects as value to a dart map
static Map<String, List<MaintenanceIntegrityReportDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MaintenanceIntegrityReportDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MaintenanceIntegrityReportDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'id',
'path',
'type',
};
}

View File

@@ -0,0 +1,99 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MaintenanceIntegrityReportResponseDto {
/// Returns a new [MaintenanceIntegrityReportResponseDto] instance.
MaintenanceIntegrityReportResponseDto({
this.items = const [],
});
List<MaintenanceIntegrityReportDto> items;
@override
bool operator ==(Object other) => identical(this, other) || other is MaintenanceIntegrityReportResponseDto &&
_deepEquality.equals(other.items, items);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(items.hashCode);
@override
String toString() => 'MaintenanceIntegrityReportResponseDto[items=$items]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'items'] = this.items;
return json;
}
/// Returns a new [MaintenanceIntegrityReportResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MaintenanceIntegrityReportResponseDto? fromJson(dynamic value) {
upgradeDto(value, "MaintenanceIntegrityReportResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return MaintenanceIntegrityReportResponseDto(
items: MaintenanceIntegrityReportDto.listFromJson(json[r'items']),
);
}
return null;
}
static List<MaintenanceIntegrityReportResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceIntegrityReportResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MaintenanceIntegrityReportResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MaintenanceIntegrityReportResponseDto> mapFromJson(dynamic json) {
final map = <String, MaintenanceIntegrityReportResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MaintenanceIntegrityReportResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MaintenanceIntegrityReportResponseDto-objects as value to a dart map
static Map<String, List<MaintenanceIntegrityReportResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MaintenanceIntegrityReportResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MaintenanceIntegrityReportResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'items',
};
}

View File

@@ -0,0 +1,115 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MaintenanceIntegrityReportSummaryResponseDto {
/// Returns a new [MaintenanceIntegrityReportSummaryResponseDto] instance.
MaintenanceIntegrityReportSummaryResponseDto({
required this.checksumMismatch,
required this.missingFile,
required this.orphanFile,
});
int checksumMismatch;
int missingFile;
int orphanFile;
@override
bool operator ==(Object other) => identical(this, other) || other is MaintenanceIntegrityReportSummaryResponseDto &&
other.checksumMismatch == checksumMismatch &&
other.missingFile == missingFile &&
other.orphanFile == orphanFile;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(checksumMismatch.hashCode) +
(missingFile.hashCode) +
(orphanFile.hashCode);
@override
String toString() => 'MaintenanceIntegrityReportSummaryResponseDto[checksumMismatch=$checksumMismatch, missingFile=$missingFile, orphanFile=$orphanFile]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'checksum_mismatch'] = this.checksumMismatch;
json[r'missing_file'] = this.missingFile;
json[r'orphan_file'] = this.orphanFile;
return json;
}
/// Returns a new [MaintenanceIntegrityReportSummaryResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MaintenanceIntegrityReportSummaryResponseDto? fromJson(dynamic value) {
upgradeDto(value, "MaintenanceIntegrityReportSummaryResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return MaintenanceIntegrityReportSummaryResponseDto(
checksumMismatch: mapValueOfType<int>(json, r'checksum_mismatch')!,
missingFile: mapValueOfType<int>(json, r'missing_file')!,
orphanFile: mapValueOfType<int>(json, r'orphan_file')!,
);
}
return null;
}
static List<MaintenanceIntegrityReportSummaryResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceIntegrityReportSummaryResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MaintenanceIntegrityReportSummaryResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MaintenanceIntegrityReportSummaryResponseDto> mapFromJson(dynamic json) {
final map = <String, MaintenanceIntegrityReportSummaryResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MaintenanceIntegrityReportSummaryResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MaintenanceIntegrityReportSummaryResponseDto-objects as value to a dart map
static Map<String, List<MaintenanceIntegrityReportSummaryResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MaintenanceIntegrityReportSummaryResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MaintenanceIntegrityReportSummaryResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'checksum_mismatch',
'missing_file',
'orphan_file',
};
}

View File

@@ -29,6 +29,15 @@ class ManualJobName {
static const memoryCleanup = ManualJobName._(r'memory-cleanup');
static const memoryCreate = ManualJobName._(r'memory-create');
static const backupDatabase = ManualJobName._(r'backup-database');
static const integrityMissingFiles = ManualJobName._(r'integrity-missing-files');
static const integrityOrphanFiles = ManualJobName._(r'integrity-orphan-files');
static const integrityChecksumMismatch = ManualJobName._(r'integrity-checksum-mismatch');
static const integrityMissingFilesRefresh = ManualJobName._(r'integrity-missing-files-refresh');
static const integrityOrphanFilesRefresh = ManualJobName._(r'integrity-orphan-files-refresh');
static const integrityChecksumMismatchRefresh = ManualJobName._(r'integrity-checksum-mismatch-refresh');
static const integrityMissingFilesDeleteAll = ManualJobName._(r'integrity-missing-files-delete-all');
static const integrityOrphanFilesDeleteAll = ManualJobName._(r'integrity-orphan-files-delete-all');
static const integrityChecksumMismatchDeleteAll = ManualJobName._(r'integrity-checksum-mismatch-delete-all');
/// List of all possible values in this [enum][ManualJobName].
static const values = <ManualJobName>[
@@ -38,6 +47,15 @@ class ManualJobName {
memoryCleanup,
memoryCreate,
backupDatabase,
integrityMissingFiles,
integrityOrphanFiles,
integrityChecksumMismatch,
integrityMissingFilesRefresh,
integrityOrphanFilesRefresh,
integrityChecksumMismatchRefresh,
integrityMissingFilesDeleteAll,
integrityOrphanFilesDeleteAll,
integrityChecksumMismatchDeleteAll,
];
static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value);
@@ -82,6 +100,15 @@ class ManualJobNameTypeTransformer {
case r'memory-cleanup': return ManualJobName.memoryCleanup;
case r'memory-create': return ManualJobName.memoryCreate;
case r'backup-database': return ManualJobName.backupDatabase;
case r'integrity-missing-files': return ManualJobName.integrityMissingFiles;
case r'integrity-orphan-files': return ManualJobName.integrityOrphanFiles;
case r'integrity-checksum-mismatch': return ManualJobName.integrityChecksumMismatch;
case r'integrity-missing-files-refresh': return ManualJobName.integrityMissingFilesRefresh;
case r'integrity-orphan-files-refresh': return ManualJobName.integrityOrphanFilesRefresh;
case r'integrity-checksum-mismatch-refresh': return ManualJobName.integrityChecksumMismatchRefresh;
case r'integrity-missing-files-delete-all': return ManualJobName.integrityMissingFilesDeleteAll;
case r'integrity-orphan-files-delete-all': return ManualJobName.integrityOrphanFilesDeleteAll;
case r'integrity-checksum-mismatch-delete-all': return ManualJobName.integrityChecksumMismatchDeleteAll;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@@ -152,6 +152,12 @@ class Permission {
static const userProfileImagePeriodRead = Permission._(r'userProfileImage.read');
static const userProfileImagePeriodUpdate = Permission._(r'userProfileImage.update');
static const userProfileImagePeriodDelete = Permission._(r'userProfileImage.delete');
static const queuePeriodRead = Permission._(r'queue.read');
static const queuePeriodUpdate = Permission._(r'queue.update');
static const queueJobPeriodCreate = Permission._(r'queueJob.create');
static const queueJobPeriodRead = Permission._(r'queueJob.read');
static const queueJobPeriodUpdate = Permission._(r'queueJob.update');
static const queueJobPeriodDelete = Permission._(r'queueJob.delete');
static const workflowPeriodCreate = Permission._(r'workflow.create');
static const workflowPeriodRead = Permission._(r'workflow.read');
static const workflowPeriodUpdate = Permission._(r'workflow.update');
@@ -294,6 +300,12 @@ class Permission {
userProfileImagePeriodRead,
userProfileImagePeriodUpdate,
userProfileImagePeriodDelete,
queuePeriodRead,
queuePeriodUpdate,
queueJobPeriodCreate,
queueJobPeriodRead,
queueJobPeriodUpdate,
queueJobPeriodDelete,
workflowPeriodCreate,
workflowPeriodRead,
workflowPeriodUpdate,
@@ -471,6 +483,12 @@ class PermissionTypeTransformer {
case r'userProfileImage.read': return Permission.userProfileImagePeriodRead;
case r'userProfileImage.update': return Permission.userProfileImagePeriodUpdate;
case r'userProfileImage.delete': return Permission.userProfileImagePeriodDelete;
case r'queue.read': return Permission.queuePeriodRead;
case r'queue.update': return Permission.queuePeriodUpdate;
case r'queueJob.create': return Permission.queueJobPeriodCreate;
case r'queueJob.read': return Permission.queueJobPeriodRead;
case r'queueJob.update': return Permission.queueJobPeriodUpdate;
case r'queueJob.delete': return Permission.queueJobPeriodDelete;
case r'workflow.create': return Permission.workflowPeriodCreate;
case r'workflow.read': return Permission.workflowPeriodRead;
case r'workflow.update': return Permission.workflowPeriodUpdate;

View File

@@ -0,0 +1,109 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueDeleteDto {
/// Returns a new [QueueDeleteDto] instance.
QueueDeleteDto({
this.failed,
});
/// If true, will also remove failed jobs from the queue.
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? failed;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueDeleteDto &&
other.failed == failed;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(failed == null ? 0 : failed!.hashCode);
@override
String toString() => 'QueueDeleteDto[failed=$failed]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.failed != null) {
json[r'failed'] = this.failed;
} else {
// json[r'failed'] = null;
}
return json;
}
/// Returns a new [QueueDeleteDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueDeleteDto? fromJson(dynamic value) {
upgradeDto(value, "QueueDeleteDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueDeleteDto(
failed: mapValueOfType<bool>(json, r'failed'),
);
}
return null;
}
static List<QueueDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueDeleteDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueDeleteDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueDeleteDto> mapFromJson(dynamic json) {
final map = <String, QueueDeleteDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueDeleteDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueDeleteDto-objects as value to a dart map
static Map<String, List<QueueDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueDeleteDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueDeleteDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -0,0 +1,132 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueJobResponseDto {
/// Returns a new [QueueJobResponseDto] instance.
QueueJobResponseDto({
required this.data,
this.id,
required this.name,
required this.timestamp,
});
Object data;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? id;
JobName name;
int timestamp;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueJobResponseDto &&
other.data == data &&
other.id == id &&
other.name == name &&
other.timestamp == timestamp;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(data.hashCode) +
(id == null ? 0 : id!.hashCode) +
(name.hashCode) +
(timestamp.hashCode);
@override
String toString() => 'QueueJobResponseDto[data=$data, id=$id, name=$name, timestamp=$timestamp]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'data'] = this.data;
if (this.id != null) {
json[r'id'] = this.id;
} else {
// json[r'id'] = null;
}
json[r'name'] = this.name;
json[r'timestamp'] = this.timestamp;
return json;
}
/// Returns a new [QueueJobResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueJobResponseDto? fromJson(dynamic value) {
upgradeDto(value, "QueueJobResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueJobResponseDto(
data: mapValueOfType<Object>(json, r'data')!,
id: mapValueOfType<String>(json, r'id'),
name: JobName.fromJson(json[r'name'])!,
timestamp: mapValueOfType<int>(json, r'timestamp')!,
);
}
return null;
}
static List<QueueJobResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueJobResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueJobResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueJobResponseDto> mapFromJson(dynamic json) {
final map = <String, QueueJobResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueJobResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueJobResponseDto-objects as value to a dart map
static Map<String, List<QueueJobResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueJobResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueJobResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'data',
'name',
'timestamp',
};
}

View File

@@ -0,0 +1,97 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueJobStatus {
/// Instantiate a new enum with the provided [value].
const QueueJobStatus._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const active = QueueJobStatus._(r'active');
static const failed = QueueJobStatus._(r'failed');
static const completed = QueueJobStatus._(r'completed');
static const delayed = QueueJobStatus._(r'delayed');
static const waiting = QueueJobStatus._(r'waiting');
static const paused = QueueJobStatus._(r'paused');
/// List of all possible values in this [enum][QueueJobStatus].
static const values = <QueueJobStatus>[
active,
failed,
completed,
delayed,
waiting,
paused,
];
static QueueJobStatus? fromJson(dynamic value) => QueueJobStatusTypeTransformer().decode(value);
static List<QueueJobStatus> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueJobStatus>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueJobStatus.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [QueueJobStatus] to String,
/// and [decode] dynamic data back to [QueueJobStatus].
class QueueJobStatusTypeTransformer {
factory QueueJobStatusTypeTransformer() => _instance ??= const QueueJobStatusTypeTransformer._();
const QueueJobStatusTypeTransformer._();
String encode(QueueJobStatus data) => data.value;
/// Decodes a [dynamic value][data] to a QueueJobStatus.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
QueueJobStatus? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'active': return QueueJobStatus.active;
case r'failed': return QueueJobStatus.failed;
case r'completed': return QueueJobStatus.completed;
case r'delayed': return QueueJobStatus.delayed;
case r'waiting': return QueueJobStatus.waiting;
case r'paused': return QueueJobStatus.paused;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [QueueJobStatusTypeTransformer] instance.
static QueueJobStatusTypeTransformer? _instance;
}

View File

@@ -13,32 +13,38 @@ part of openapi.api;
class QueueResponseDto {
/// Returns a new [QueueResponseDto] instance.
QueueResponseDto({
required this.jobCounts,
required this.queueStatus,
required this.isPaused,
required this.name,
required this.statistics,
});
QueueStatisticsDto jobCounts;
bool isPaused;
QueueStatusDto queueStatus;
QueueName name;
QueueStatisticsDto statistics;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueResponseDto &&
other.jobCounts == jobCounts &&
other.queueStatus == queueStatus;
other.isPaused == isPaused &&
other.name == name &&
other.statistics == statistics;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(jobCounts.hashCode) +
(queueStatus.hashCode);
(isPaused.hashCode) +
(name.hashCode) +
(statistics.hashCode);
@override
String toString() => 'QueueResponseDto[jobCounts=$jobCounts, queueStatus=$queueStatus]';
String toString() => 'QueueResponseDto[isPaused=$isPaused, name=$name, statistics=$statistics]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'jobCounts'] = this.jobCounts;
json[r'queueStatus'] = this.queueStatus;
json[r'isPaused'] = this.isPaused;
json[r'name'] = this.name;
json[r'statistics'] = this.statistics;
return json;
}
@@ -51,8 +57,9 @@ class QueueResponseDto {
final json = value.cast<String, dynamic>();
return QueueResponseDto(
jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!,
queueStatus: QueueStatusDto.fromJson(json[r'queueStatus'])!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
name: QueueName.fromJson(json[r'name'])!,
statistics: QueueStatisticsDto.fromJson(json[r'statistics'])!,
);
}
return null;
@@ -100,8 +107,9 @@ class QueueResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'jobCounts',
'queueStatus',
'isPaused',
'name',
'statistics',
};
}

View File

@@ -0,0 +1,107 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueResponseLegacyDto {
/// Returns a new [QueueResponseLegacyDto] instance.
QueueResponseLegacyDto({
required this.jobCounts,
required this.queueStatus,
});
QueueStatisticsDto jobCounts;
QueueStatusLegacyDto queueStatus;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueResponseLegacyDto &&
other.jobCounts == jobCounts &&
other.queueStatus == queueStatus;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(jobCounts.hashCode) +
(queueStatus.hashCode);
@override
String toString() => 'QueueResponseLegacyDto[jobCounts=$jobCounts, queueStatus=$queueStatus]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'jobCounts'] = this.jobCounts;
json[r'queueStatus'] = this.queueStatus;
return json;
}
/// Returns a new [QueueResponseLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueResponseLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueueResponseLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueResponseLegacyDto(
jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!,
queueStatus: QueueStatusLegacyDto.fromJson(json[r'queueStatus'])!,
);
}
return null;
}
static List<QueueResponseLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueResponseLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueResponseLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueResponseLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueueResponseLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueResponseLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueResponseLegacyDto-objects as value to a dart map
static Map<String, List<QueueResponseLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueResponseLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueResponseLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'jobCounts',
'queueStatus',
};
}

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class QueueStatusDto {
/// Returns a new [QueueStatusDto] instance.
QueueStatusDto({
class QueueStatusLegacyDto {
/// Returns a new [QueueStatusLegacyDto] instance.
QueueStatusLegacyDto({
required this.isActive,
required this.isPaused,
});
@@ -22,7 +22,7 @@ class QueueStatusDto {
bool isPaused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueStatusDto &&
bool operator ==(Object other) => identical(this, other) || other is QueueStatusLegacyDto &&
other.isActive == isActive &&
other.isPaused == isPaused;
@@ -33,7 +33,7 @@ class QueueStatusDto {
(isPaused.hashCode);
@override
String toString() => 'QueueStatusDto[isActive=$isActive, isPaused=$isPaused]';
String toString() => 'QueueStatusLegacyDto[isActive=$isActive, isPaused=$isPaused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -42,15 +42,15 @@ class QueueStatusDto {
return json;
}
/// Returns a new [QueueStatusDto] instance and imports its values from
/// Returns a new [QueueStatusLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueStatusDto? fromJson(dynamic value) {
upgradeDto(value, "QueueStatusDto");
static QueueStatusLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueueStatusLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueStatusDto(
return QueueStatusLegacyDto(
isActive: mapValueOfType<bool>(json, r'isActive')!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
);
@@ -58,11 +58,11 @@ class QueueStatusDto {
return null;
}
static List<QueueStatusDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusDto>[];
static List<QueueStatusLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueStatusDto.fromJson(row);
final value = QueueStatusLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -71,12 +71,12 @@ class QueueStatusDto {
return result.toList(growable: growable);
}
static Map<String, QueueStatusDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusDto>{};
static Map<String, QueueStatusLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueStatusDto.fromJson(entry.value);
final value = QueueStatusLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -85,14 +85,14 @@ class QueueStatusDto {
return map;
}
// maps a json object with a list of QueueStatusDto-objects as value to a dart map
static Map<String, List<QueueStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusDto>>{};
// maps a json object with a list of QueueStatusLegacyDto-objects as value to a dart map
static Map<String, List<QueueStatusLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueStatusDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = QueueStatusLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

@@ -0,0 +1,108 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueUpdateDto {
/// Returns a new [QueueUpdateDto] instance.
QueueUpdateDto({
this.isPaused,
});
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isPaused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueUpdateDto &&
other.isPaused == isPaused;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(isPaused == null ? 0 : isPaused!.hashCode);
@override
String toString() => 'QueueUpdateDto[isPaused=$isPaused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.isPaused != null) {
json[r'isPaused'] = this.isPaused;
} else {
// json[r'isPaused'] = null;
}
return json;
}
/// Returns a new [QueueUpdateDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "QueueUpdateDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueUpdateDto(
isPaused: mapValueOfType<bool>(json, r'isPaused'),
);
}
return null;
}
static List<QueueUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueUpdateDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueUpdateDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueUpdateDto> mapFromJson(dynamic json) {
final map = <String, QueueUpdateDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueUpdateDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueUpdateDto-objects as value to a dart map
static Map<String, List<QueueUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueUpdateDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueUpdateDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class QueuesResponseDto {
/// Returns a new [QueuesResponseDto] instance.
QueuesResponseDto({
class QueuesResponseLegacyDto {
/// Returns a new [QueuesResponseLegacyDto] instance.
QueuesResponseLegacyDto({
required this.backgroundTask,
required this.backupDatabase,
required this.duplicateDetection,
@@ -32,42 +32,42 @@ class QueuesResponseDto {
required this.workflow,
});
QueueResponseDto backgroundTask;
QueueResponseLegacyDto backgroundTask;
QueueResponseDto backupDatabase;
QueueResponseLegacyDto backupDatabase;
QueueResponseDto duplicateDetection;
QueueResponseLegacyDto duplicateDetection;
QueueResponseDto faceDetection;
QueueResponseLegacyDto faceDetection;
QueueResponseDto facialRecognition;
QueueResponseLegacyDto facialRecognition;
QueueResponseDto library_;
QueueResponseLegacyDto library_;
QueueResponseDto metadataExtraction;
QueueResponseLegacyDto metadataExtraction;
QueueResponseDto migration;
QueueResponseLegacyDto migration;
QueueResponseDto notifications;
QueueResponseLegacyDto notifications;
QueueResponseDto ocr;
QueueResponseLegacyDto ocr;
QueueResponseDto search;
QueueResponseLegacyDto search;
QueueResponseDto sidecar;
QueueResponseLegacyDto sidecar;
QueueResponseDto smartSearch;
QueueResponseLegacyDto smartSearch;
QueueResponseDto storageTemplateMigration;
QueueResponseLegacyDto storageTemplateMigration;
QueueResponseDto thumbnailGeneration;
QueueResponseLegacyDto thumbnailGeneration;
QueueResponseDto videoConversion;
QueueResponseLegacyDto videoConversion;
QueueResponseDto workflow;
QueueResponseLegacyDto workflow;
@override
bool operator ==(Object other) => identical(this, other) || other is QueuesResponseDto &&
bool operator ==(Object other) => identical(this, other) || other is QueuesResponseLegacyDto &&
other.backgroundTask == backgroundTask &&
other.backupDatabase == backupDatabase &&
other.duplicateDetection == duplicateDetection &&
@@ -108,7 +108,7 @@ class QueuesResponseDto {
(workflow.hashCode);
@override
String toString() => 'QueuesResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -132,42 +132,42 @@ class QueuesResponseDto {
return json;
}
/// Returns a new [QueuesResponseDto] instance and imports its values from
/// Returns a new [QueuesResponseLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueuesResponseDto? fromJson(dynamic value) {
upgradeDto(value, "QueuesResponseDto");
static QueuesResponseLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueuesResponseLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueuesResponseDto(
backgroundTask: QueueResponseDto.fromJson(json[r'backgroundTask'])!,
backupDatabase: QueueResponseDto.fromJson(json[r'backupDatabase'])!,
duplicateDetection: QueueResponseDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: QueueResponseDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseDto.fromJson(json[r'facialRecognition'])!,
library_: QueueResponseDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseDto.fromJson(json[r'migration'])!,
notifications: QueueResponseDto.fromJson(json[r'notifications'])!,
ocr: QueueResponseDto.fromJson(json[r'ocr'])!,
search: QueueResponseDto.fromJson(json[r'search'])!,
sidecar: QueueResponseDto.fromJson(json[r'sidecar'])!,
smartSearch: QueueResponseDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: QueueResponseDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: QueueResponseDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: QueueResponseDto.fromJson(json[r'videoConversion'])!,
workflow: QueueResponseDto.fromJson(json[r'workflow'])!,
return QueuesResponseLegacyDto(
backgroundTask: QueueResponseLegacyDto.fromJson(json[r'backgroundTask'])!,
backupDatabase: QueueResponseLegacyDto.fromJson(json[r'backupDatabase'])!,
duplicateDetection: QueueResponseLegacyDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!,
library_: QueueResponseLegacyDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseLegacyDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseLegacyDto.fromJson(json[r'migration'])!,
notifications: QueueResponseLegacyDto.fromJson(json[r'notifications'])!,
ocr: QueueResponseLegacyDto.fromJson(json[r'ocr'])!,
search: QueueResponseLegacyDto.fromJson(json[r'search'])!,
sidecar: QueueResponseLegacyDto.fromJson(json[r'sidecar'])!,
smartSearch: QueueResponseLegacyDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: QueueResponseLegacyDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: QueueResponseLegacyDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: QueueResponseLegacyDto.fromJson(json[r'videoConversion'])!,
workflow: QueueResponseLegacyDto.fromJson(json[r'workflow'])!,
);
}
return null;
}
static List<QueuesResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueuesResponseDto>[];
static List<QueuesResponseLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueuesResponseLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueuesResponseDto.fromJson(row);
final value = QueuesResponseLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -176,12 +176,12 @@ class QueuesResponseDto {
return result.toList(growable: growable);
}
static Map<String, QueuesResponseDto> mapFromJson(dynamic json) {
final map = <String, QueuesResponseDto>{};
static Map<String, QueuesResponseLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueuesResponseLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueuesResponseDto.fromJson(entry.value);
final value = QueuesResponseLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -190,14 +190,14 @@ class QueuesResponseDto {
return map;
}
// maps a json object with a list of QueuesResponseDto-objects as value to a dart map
static Map<String, List<QueuesResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueuesResponseDto>>{};
// maps a json object with a list of QueuesResponseLegacyDto-objects as value to a dart map
static Map<String, List<QueuesResponseLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueuesResponseLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueuesResponseDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = QueuesResponseLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

@@ -16,6 +16,7 @@ class SystemConfigDto {
required this.backup,
required this.ffmpeg,
required this.image,
required this.integrityChecks,
required this.job,
required this.library_,
required this.logging,
@@ -42,6 +43,8 @@ class SystemConfigDto {
SystemConfigImageDto image;
SystemConfigIntegrityChecks integrityChecks;
SystemConfigJobDto job;
SystemConfigLibraryDto library_;
@@ -83,6 +86,7 @@ class SystemConfigDto {
other.backup == backup &&
other.ffmpeg == ffmpeg &&
other.image == image &&
other.integrityChecks == integrityChecks &&
other.job == job &&
other.library_ == library_ &&
other.logging == logging &&
@@ -108,6 +112,7 @@ class SystemConfigDto {
(backup.hashCode) +
(ffmpeg.hashCode) +
(image.hashCode) +
(integrityChecks.hashCode) +
(job.hashCode) +
(library_.hashCode) +
(logging.hashCode) +
@@ -128,13 +133,14 @@ class SystemConfigDto {
(user.hashCode);
@override
String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]';
String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, integrityChecks=$integrityChecks, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'backup'] = this.backup;
json[r'ffmpeg'] = this.ffmpeg;
json[r'image'] = this.image;
json[r'integrityChecks'] = this.integrityChecks;
json[r'job'] = this.job;
json[r'library'] = this.library_;
json[r'logging'] = this.logging;
@@ -168,6 +174,7 @@ class SystemConfigDto {
backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!,
ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!,
image: SystemConfigImageDto.fromJson(json[r'image'])!,
integrityChecks: SystemConfigIntegrityChecks.fromJson(json[r'integrityChecks'])!,
job: SystemConfigJobDto.fromJson(json[r'job'])!,
library_: SystemConfigLibraryDto.fromJson(json[r'library'])!,
logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!,
@@ -236,6 +243,7 @@ class SystemConfigDto {
'backup',
'ffmpeg',
'image',
'integrityChecks',
'job',
'library',
'logging',

View File

@@ -0,0 +1,115 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigIntegrityChecks {
/// Returns a new [SystemConfigIntegrityChecks] instance.
SystemConfigIntegrityChecks({
required this.checksumFiles,
required this.missingFiles,
required this.orphanedFiles,
});
SystemConfigIntegrityChecksumJob checksumFiles;
SystemConfigIntegrityJob missingFiles;
SystemConfigIntegrityJob orphanedFiles;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecks &&
other.checksumFiles == checksumFiles &&
other.missingFiles == missingFiles &&
other.orphanedFiles == orphanedFiles;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(checksumFiles.hashCode) +
(missingFiles.hashCode) +
(orphanedFiles.hashCode);
@override
String toString() => 'SystemConfigIntegrityChecks[checksumFiles=$checksumFiles, missingFiles=$missingFiles, orphanedFiles=$orphanedFiles]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'checksumFiles'] = this.checksumFiles;
json[r'missingFiles'] = this.missingFiles;
json[r'orphanedFiles'] = this.orphanedFiles;
return json;
}
/// Returns a new [SystemConfigIntegrityChecks] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigIntegrityChecks? fromJson(dynamic value) {
upgradeDto(value, "SystemConfigIntegrityChecks");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigIntegrityChecks(
checksumFiles: SystemConfigIntegrityChecksumJob.fromJson(json[r'checksumFiles'])!,
missingFiles: SystemConfigIntegrityJob.fromJson(json[r'missingFiles'])!,
orphanedFiles: SystemConfigIntegrityJob.fromJson(json[r'orphanedFiles'])!,
);
}
return null;
}
static List<SystemConfigIntegrityChecks> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityChecks>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigIntegrityChecks.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigIntegrityChecks> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityChecks>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigIntegrityChecks.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigIntegrityChecks-objects as value to a dart map
static Map<String, List<SystemConfigIntegrityChecks>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityChecks>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigIntegrityChecks.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'checksumFiles',
'missingFiles',
'orphanedFiles',
};
}

View File

@@ -0,0 +1,123 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigIntegrityChecksumJob {
/// Returns a new [SystemConfigIntegrityChecksumJob] instance.
SystemConfigIntegrityChecksumJob({
required this.cronExpression,
required this.enabled,
required this.percentageLimit,
required this.timeLimit,
});
String cronExpression;
bool enabled;
num percentageLimit;
num timeLimit;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecksumJob &&
other.cronExpression == cronExpression &&
other.enabled == enabled &&
other.percentageLimit == percentageLimit &&
other.timeLimit == timeLimit;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(cronExpression.hashCode) +
(enabled.hashCode) +
(percentageLimit.hashCode) +
(timeLimit.hashCode);
@override
String toString() => 'SystemConfigIntegrityChecksumJob[cronExpression=$cronExpression, enabled=$enabled, percentageLimit=$percentageLimit, timeLimit=$timeLimit]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'cronExpression'] = this.cronExpression;
json[r'enabled'] = this.enabled;
json[r'percentageLimit'] = this.percentageLimit;
json[r'timeLimit'] = this.timeLimit;
return json;
}
/// Returns a new [SystemConfigIntegrityChecksumJob] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigIntegrityChecksumJob? fromJson(dynamic value) {
upgradeDto(value, "SystemConfigIntegrityChecksumJob");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigIntegrityChecksumJob(
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
percentageLimit: num.parse('${json[r'percentageLimit']}'),
timeLimit: num.parse('${json[r'timeLimit']}'),
);
}
return null;
}
static List<SystemConfigIntegrityChecksumJob> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityChecksumJob>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigIntegrityChecksumJob.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigIntegrityChecksumJob> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityChecksumJob>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigIntegrityChecksumJob.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigIntegrityChecksumJob-objects as value to a dart map
static Map<String, List<SystemConfigIntegrityChecksumJob>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityChecksumJob>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigIntegrityChecksumJob.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'cronExpression',
'enabled',
'percentageLimit',
'timeLimit',
};
}

View File

@@ -0,0 +1,107 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigIntegrityJob {
/// Returns a new [SystemConfigIntegrityJob] instance.
SystemConfigIntegrityJob({
required this.cronExpression,
required this.enabled,
});
String cronExpression;
bool enabled;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityJob &&
other.cronExpression == cronExpression &&
other.enabled == enabled;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(cronExpression.hashCode) +
(enabled.hashCode);
@override
String toString() => 'SystemConfigIntegrityJob[cronExpression=$cronExpression, enabled=$enabled]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'cronExpression'] = this.cronExpression;
json[r'enabled'] = this.enabled;
return json;
}
/// Returns a new [SystemConfigIntegrityJob] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigIntegrityJob? fromJson(dynamic value) {
upgradeDto(value, "SystemConfigIntegrityJob");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigIntegrityJob(
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
);
}
return null;
}
static List<SystemConfigIntegrityJob> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityJob>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigIntegrityJob.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigIntegrityJob> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityJob>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigIntegrityJob.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigIntegrityJob-objects as value to a dart map
static Map<String, List<SystemConfigIntegrityJob>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityJob>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigIntegrityJob.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'cronExpression',
'enabled',
};
}

View File

@@ -0,0 +1,165 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/domain/services/asset.service.dart';
import 'package:mocktail/mocktail.dart';
import '../../infrastructure/repository.mock.dart';
import '../../test_utils.dart';
void main() {
late AssetService sut;
late MockRemoteAssetRepository mockRemoteAssetRepository;
late MockDriftLocalAssetRepository mockLocalAssetRepository;
setUp(() {
mockRemoteAssetRepository = MockRemoteAssetRepository();
mockLocalAssetRepository = MockDriftLocalAssetRepository();
sut = AssetService(
remoteAssetRepository: mockRemoteAssetRepository,
localAssetRepository: mockLocalAssetRepository,
);
});
group('getAspectRatio', () {
test('flips dimensions on Android for 90° and 270° orientations', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
addTearDown(() => debugDefaultTargetPlatformOverride = null);
for (final orientation in [90, 270]) {
final localAsset = TestUtils.createLocalAsset(
id: 'local-$orientation',
width: 1920,
height: 1080,
orientation: orientation,
);
final result = await sut.getAspectRatio(localAsset);
expect(result, 1080 / 1920, reason: 'Orientation $orientation should flip on Android');
}
});
test('does not flip dimensions on iOS regardless of orientation', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
addTearDown(() => debugDefaultTargetPlatformOverride = null);
for (final orientation in [0, 90, 270]) {
final localAsset = TestUtils.createLocalAsset(
id: 'local-$orientation',
width: 1920,
height: 1080,
orientation: orientation,
);
final result = await sut.getAspectRatio(localAsset);
expect(result, 1920 / 1080, reason: 'iOS should never flip dimensions');
}
});
test('fetches dimensions from remote repository when missing from asset', () async {
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: null, height: null);
final exif = const ExifInfo(orientation: '1');
final fetchedAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: 1920, height: 1080);
when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif);
when(() => mockRemoteAssetRepository.get('remote-1')).thenAnswer((_) async => fetchedAsset);
final result = await sut.getAspectRatio(remoteAsset);
expect(result, 1920 / 1080);
verify(() => mockRemoteAssetRepository.get('remote-1')).called(1);
});
test('fetches dimensions from local repository when missing from local asset', () async {
final localAsset = TestUtils.createLocalAsset(id: 'local-1', width: null, height: null, orientation: 0);
final fetchedAsset = TestUtils.createLocalAsset(id: 'local-1', width: 1920, height: 1080, orientation: 0);
when(() => mockLocalAssetRepository.get('local-1')).thenAnswer((_) async => fetchedAsset);
final result = await sut.getAspectRatio(localAsset);
expect(result, 1920 / 1080);
verify(() => mockLocalAssetRepository.get('local-1')).called(1);
});
test('returns 1.0 when dimensions are still unavailable after fetching', () async {
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: null, height: null);
final exif = const ExifInfo(orientation: '1');
when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif);
when(() => mockRemoteAssetRepository.get('remote-1')).thenAnswer((_) async => null);
final result = await sut.getAspectRatio(remoteAsset);
expect(result, 1.0);
});
test('returns 1.0 when height is zero', () async {
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: 1920, height: 0);
final exif = const ExifInfo(orientation: '1');
when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif);
final result = await sut.getAspectRatio(remoteAsset);
expect(result, 1.0);
});
test('handles local asset with remoteId and uses exif from remote', () async {
final localAsset = TestUtils.createLocalAsset(
id: 'local-1',
remoteId: 'remote-1',
width: 1920,
height: 1080,
orientation: 0,
);
final exif = const ExifInfo(orientation: '6');
when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif);
final result = await sut.getAspectRatio(localAsset);
expect(result, 1080 / 1920);
});
test('handles various flipped EXIF orientations correctly', () async {
final flippedOrientations = ['5', '6', '7', '8', '90', '-90'];
for (final orientation in flippedOrientations) {
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-$orientation', width: 1920, height: 1080);
final exif = ExifInfo(orientation: orientation);
when(() => mockRemoteAssetRepository.getExif('remote-$orientation')).thenAnswer((_) async => exif);
final result = await sut.getAspectRatio(remoteAsset);
expect(result, 1080 / 1920, reason: 'Orientation $orientation should flip dimensions');
}
});
test('handles various non-flipped EXIF orientations correctly', () async {
final nonFlippedOrientations = ['1', '2', '3', '4'];
for (final orientation in nonFlippedOrientations) {
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-$orientation', width: 1920, height: 1080);
final exif = ExifInfo(orientation: orientation);
when(() => mockRemoteAssetRepository.getExif('remote-$orientation')).thenAnswer((_) async => exif);
final result = await sut.getAspectRatio(remoteAsset);
expect(result, 1920 / 1080, reason: 'Orientation $orientation should NOT flip dimensions');
}
});
});
}

View File

@@ -54,12 +54,7 @@ void main() {
when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false);
when(() => mockNativeSyncApi.getMediaChanges()).thenAnswer(
(_) async => SyncDelta(
hasChanges: false,
updates: const [],
deletes: const [],
assetAlbums: const {},
),
(_) async => SyncDelta(hasChanges: false, updates: const [], deletes: const [], assetAlbums: const {}),
);
when(() => mockNativeSyncApi.getTrashedAssets()).thenAnswer((_) async => {});
when(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).thenAnswer((_) async {});
@@ -144,13 +139,19 @@ void main() {
});
final localAssetToTrash = LocalAssetStub.image2.copyWith(id: 'local-trash', checksum: 'checksum-trash');
when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {'album-a': [localAssetToTrash]});
when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer(
(_) async => {
'album-a': [localAssetToTrash],
},
);
final assetEntity = MockAssetEntity();
when(() => assetEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-trash');
when(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).thenAnswer((_) async => assetEntity);
await sut.processTrashedAssets({'album-a': [platformAsset]});
await sut.processTrashedAssets({
'album-a': [platformAsset],
});
verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).called(1);
verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1);
@@ -159,8 +160,7 @@ void main() {
verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1);
verify(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).called(1);
final moveArgs =
verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List<String>;
final moveArgs = verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List<String>;
expect(moveArgs, ['content://local-trash']);
final trashArgs =
verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single
@@ -187,4 +187,25 @@ void main() {
verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any()));
});
});
group('LocalSyncService - PlatformAsset conversion', () {
test('toLocalAsset uses correct updatedAt timestamp', () {
final platformAsset = PlatformAsset(
id: 'test-id',
name: 'test.jpg',
type: AssetType.image.index,
durationInSeconds: 0,
orientation: 0,
isFavorite: false,
createdAt: 1700000000,
updatedAt: 1732000000,
);
final localAsset = platformAsset.toLocalAsset();
expect(localAsset.createdAt.millisecondsSinceEpoch ~/ 1000, 1700000000);
expect(localAsset.updatedAt.millisecondsSinceEpoch ~/ 1000, 1732000000);
expect(localAsset.updatedAt, isNot(localAsset.createdAt));
});
});
}

View File

@@ -4,6 +4,7 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
@@ -35,6 +36,8 @@ class MockLocalAssetRepository extends Mock implements DriftLocalAssetRepository
class MockDriftLocalAssetRepository extends Mock implements DriftLocalAssetRepository {}
class MockRemoteAssetRepository extends Mock implements RemoteAssetRepository {}
class MockTrashedLocalAssetRepository extends Mock implements DriftTrashedLocalAssetRepository {}
class MockStorageRepository extends Mock implements StorageRepository {}

View File

@@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:fake_async/fake_async.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as domain;
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
@@ -116,4 +117,43 @@ abstract final class TestUtils {
}
return result;
}
static domain.RemoteAsset createRemoteAsset({required String id, int? width, int? height, String? ownerId}) {
return domain.RemoteAsset(
id: id,
checksum: 'checksum1',
ownerId: ownerId ?? 'owner1',
name: 'test.jpg',
type: domain.AssetType.image,
createdAt: DateTime(2024, 1, 1),
updatedAt: DateTime(2024, 1, 1),
durationInSeconds: 0,
isFavorite: false,
width: width,
height: height,
);
}
static domain.LocalAsset createLocalAsset({
required String id,
String? remoteId,
int? width,
int? height,
int orientation = 0,
}) {
return domain.LocalAsset(
id: id,
remoteId: remoteId,
checksum: 'checksum1',
name: 'test.jpg',
type: domain.AssetType.image,
createdAt: DateTime(2024, 1, 1),
updatedAt: DateTime(2024, 1, 1),
durationInSeconds: 0,
isFavorite: false,
width: width,
height: height,
orientation: orientation,
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -19,7 +19,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"typescript": "^5.3.3"
},
"repository": {
@@ -28,6 +28,6 @@
"directory": "open-api/typescript-sdk"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -43,6 +43,22 @@ export type ActivityStatisticsResponseDto = {
export type SetMaintenanceModeDto = {
action: MaintenanceAction;
};
export type MaintenanceGetIntegrityReportDto = {
"type": IntegrityReportType;
};
export type MaintenanceIntegrityReportDto = {
id: string;
path: string;
"type": IntegrityReportType;
};
export type MaintenanceIntegrityReportResponseDto = {
items: MaintenanceIntegrityReportDto[];
};
export type MaintenanceIntegrityReportSummaryResponseDto = {
checksum_mismatch: number;
missing_file: number;
orphan_file: number;
};
export type MaintenanceLoginDto = {
token?: string;
};
@@ -716,32 +732,32 @@ export type QueueStatisticsDto = {
paused: number;
waiting: number;
};
export type QueueStatusDto = {
export type QueueStatusLegacyDto = {
isActive: boolean;
isPaused: boolean;
};
export type QueueResponseDto = {
export type QueueResponseLegacyDto = {
jobCounts: QueueStatisticsDto;
queueStatus: QueueStatusDto;
queueStatus: QueueStatusLegacyDto;
};
export type QueuesResponseDto = {
backgroundTask: QueueResponseDto;
backupDatabase: QueueResponseDto;
duplicateDetection: QueueResponseDto;
faceDetection: QueueResponseDto;
facialRecognition: QueueResponseDto;
library: QueueResponseDto;
metadataExtraction: QueueResponseDto;
migration: QueueResponseDto;
notifications: QueueResponseDto;
ocr: QueueResponseDto;
search: QueueResponseDto;
sidecar: QueueResponseDto;
smartSearch: QueueResponseDto;
storageTemplateMigration: QueueResponseDto;
thumbnailGeneration: QueueResponseDto;
videoConversion: QueueResponseDto;
workflow: QueueResponseDto;
export type QueuesResponseLegacyDto = {
backgroundTask: QueueResponseLegacyDto;
backupDatabase: QueueResponseLegacyDto;
duplicateDetection: QueueResponseLegacyDto;
faceDetection: QueueResponseLegacyDto;
facialRecognition: QueueResponseLegacyDto;
library: QueueResponseLegacyDto;
metadataExtraction: QueueResponseLegacyDto;
migration: QueueResponseLegacyDto;
notifications: QueueResponseLegacyDto;
ocr: QueueResponseLegacyDto;
search: QueueResponseLegacyDto;
sidecar: QueueResponseLegacyDto;
smartSearch: QueueResponseLegacyDto;
storageTemplateMigration: QueueResponseLegacyDto;
thumbnailGeneration: QueueResponseLegacyDto;
videoConversion: QueueResponseLegacyDto;
workflow: QueueResponseLegacyDto;
};
export type JobCreateDto = {
name: ManualJobName;
@@ -966,6 +982,24 @@ export type PluginResponseDto = {
updatedAt: string;
version: string;
};
export type QueueResponseDto = {
isPaused: boolean;
name: QueueName;
statistics: QueueStatisticsDto;
};
export type QueueUpdateDto = {
isPaused?: boolean;
};
export type QueueDeleteDto = {
/** If true, will also remove failed jobs from the queue. */
failed?: boolean;
};
export type QueueJobResponseDto = {
data: object;
id?: string;
name: JobName;
timestamp: number;
};
export type SearchExploreItem = {
data: AssetResponseDto;
value: string;
@@ -1436,6 +1470,21 @@ export type SystemConfigImageDto = {
preview: SystemConfigGeneratedImageDto;
thumbnail: SystemConfigGeneratedImageDto;
};
export type SystemConfigIntegrityChecksumJob = {
cronExpression: string;
enabled: boolean;
percentageLimit: number;
timeLimit: number;
};
export type SystemConfigIntegrityJob = {
cronExpression: string;
enabled: boolean;
};
export type SystemConfigIntegrityChecks = {
checksumFiles: SystemConfigIntegrityChecksumJob;
missingFiles: SystemConfigIntegrityJob;
orphanedFiles: SystemConfigIntegrityJob;
};
export type JobSettingsDto = {
concurrency: number;
};
@@ -1588,6 +1637,7 @@ export type SystemConfigDto = {
backup: SystemConfigBackupsDto;
ffmpeg: SystemConfigFFmpegDto;
image: SystemConfigImageDto;
integrityChecks: SystemConfigIntegrityChecks;
job: SystemConfigJobDto;
library: SystemConfigLibraryDto;
logging: SystemConfigLoggingDto;
@@ -1844,6 +1894,69 @@ export function setMaintenanceMode({ setMaintenanceModeDto }: {
body: setMaintenanceModeDto
})));
}
/**
* Get integrity report by type
*/
export function getIntegrityReport({ maintenanceGetIntegrityReportDto }: {
maintenanceGetIntegrityReportDto: MaintenanceGetIntegrityReportDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: MaintenanceIntegrityReportResponseDto;
}>("/admin/maintenance/integrity/report", oazapfts.json({
...opts,
method: "POST",
body: maintenanceGetIntegrityReportDto
})));
}
/**
* Delete report entry and perform corresponding deletion action
*/
export function deleteIntegrityReport({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/admin/maintenance/integrity/report/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
}
/**
* Download the orphan/broken file if one exists
*/
export function getIntegrityReportFile({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/admin/maintenance/integrity/report/${encodeURIComponent(id)}/file`, {
...opts
}));
}
/**
* Export integrity report by type as CSV
*/
export function getIntegrityReportCsv({ $type }: {
$type: IntegrityReportType;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/admin/maintenance/integrity/report/${encodeURIComponent($type)}/csv`, {
...opts
}));
}
/**
* Get integrity report summary
*/
export function getIntegrityReportSummary(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: MaintenanceIntegrityReportSummaryResponseDto;
}>("/admin/maintenance/integrity/summary", {
...opts
}));
}
/**
* Log into maintenance mode
*/
@@ -2925,7 +3038,7 @@ export function reassignFacesById({ id, faceDto }: {
export function getQueuesLegacy(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueuesResponseDto;
data: QueuesResponseLegacyDto;
}>("/jobs", {
...opts
}));
@@ -2951,7 +3064,7 @@ export function runQueueCommandLegacy({ name, queueCommandDto }: {
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
data: QueueResponseLegacyDto;
}>(`/jobs/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "PUT",
@@ -3651,6 +3764,75 @@ export function getPlugin({ id }: {
...opts
}));
}
/**
* List all queues
*/
export function getQueues(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto[];
}>("/queues", {
...opts
}));
}
/**
* Retrieve a queue
*/
export function getQueue({ name }: {
name: QueueName;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
}>(`/queues/${encodeURIComponent(name)}`, {
...opts
}));
}
/**
* Update a queue
*/
export function updateQueue({ name, queueUpdateDto }: {
name: QueueName;
queueUpdateDto: QueueUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
}>(`/queues/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "PUT",
body: queueUpdateDto
})));
}
/**
* Empty a queue
*/
export function emptyQueue({ name, queueDeleteDto }: {
name: QueueName;
queueDeleteDto: QueueDeleteDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/queues/${encodeURIComponent(name)}/jobs`, oazapfts.json({
...opts,
method: "DELETE",
body: queueDeleteDto
})));
}
/**
* Retrieve queue jobs
*/
export function getQueueJobs({ name, status }: {
name: QueueName;
status?: QueueJobStatus[];
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueJobResponseDto[];
}>(`/queues/${encodeURIComponent(name)}/jobs${QS.query(QS.explode({
status
}))}`, {
...opts
}));
}
/**
* Retrieve assets by city
*/
@@ -5055,6 +5237,11 @@ export enum MaintenanceAction {
Start = "start",
End = "end"
}
export enum IntegrityReportType {
OrphanFile = "orphan_file",
MissingFile = "missing_file",
ChecksumMismatch = "checksum_mismatch"
}
export enum NotificationLevel {
Success = "success",
Error = "error",
@@ -5241,6 +5428,12 @@ export enum Permission {
UserProfileImageRead = "userProfileImage.read",
UserProfileImageUpdate = "userProfileImage.update",
UserProfileImageDelete = "userProfileImage.delete",
QueueRead = "queue.read",
QueueUpdate = "queue.update",
QueueJobCreate = "queueJob.create",
QueueJobRead = "queueJob.read",
QueueJobUpdate = "queueJob.update",
QueueJobDelete = "queueJob.delete",
WorkflowCreate = "workflow.create",
WorkflowRead = "workflow.read",
WorkflowUpdate = "workflow.update",
@@ -5285,7 +5478,16 @@ export enum ManualJobName {
UserCleanup = "user-cleanup",
MemoryCleanup = "memory-cleanup",
MemoryCreate = "memory-create",
BackupDatabase = "backup-database"
BackupDatabase = "backup-database",
IntegrityMissingFiles = "integrity-missing-files",
IntegrityOrphanFiles = "integrity-orphan-files",
IntegrityChecksumMismatch = "integrity-checksum-mismatch",
IntegrityMissingFilesRefresh = "integrity-missing-files-refresh",
IntegrityOrphanFilesRefresh = "integrity-orphan-files-refresh",
IntegrityChecksumMismatchRefresh = "integrity-checksum-mismatch-refresh",
IntegrityMissingFilesDeleteAll = "integrity-missing-files-delete-all",
IntegrityOrphanFilesDeleteAll = "integrity-orphan-files-delete-all",
IntegrityChecksumMismatchDeleteAll = "integrity-checksum-mismatch-delete-all"
}
export enum QueueName {
ThumbnailGeneration = "thumbnailGeneration",
@@ -5330,6 +5532,80 @@ export enum PluginContext {
Album = "album",
Person = "person"
}
export enum QueueJobStatus {
Active = "active",
Failed = "failed",
Completed = "completed",
Delayed = "delayed",
Waiting = "waiting",
Paused = "paused"
}
export enum JobName {
AssetDelete = "AssetDelete",
AssetDeleteCheck = "AssetDeleteCheck",
AssetDetectFacesQueueAll = "AssetDetectFacesQueueAll",
AssetDetectFaces = "AssetDetectFaces",
AssetDetectDuplicatesQueueAll = "AssetDetectDuplicatesQueueAll",
AssetDetectDuplicates = "AssetDetectDuplicates",
AssetEncodeVideoQueueAll = "AssetEncodeVideoQueueAll",
AssetEncodeVideo = "AssetEncodeVideo",
AssetEmptyTrash = "AssetEmptyTrash",
AssetExtractMetadataQueueAll = "AssetExtractMetadataQueueAll",
AssetExtractMetadata = "AssetExtractMetadata",
AssetFileMigration = "AssetFileMigration",
AssetGenerateThumbnailsQueueAll = "AssetGenerateThumbnailsQueueAll",
AssetGenerateThumbnails = "AssetGenerateThumbnails",
AuditLogCleanup = "AuditLogCleanup",
AuditTableCleanup = "AuditTableCleanup",
DatabaseBackup = "DatabaseBackup",
FacialRecognitionQueueAll = "FacialRecognitionQueueAll",
FacialRecognition = "FacialRecognition",
FileDelete = "FileDelete",
FileMigrationQueueAll = "FileMigrationQueueAll",
LibraryDeleteCheck = "LibraryDeleteCheck",
LibraryDelete = "LibraryDelete",
LibraryRemoveAsset = "LibraryRemoveAsset",
LibraryScanAssetsQueueAll = "LibraryScanAssetsQueueAll",
LibrarySyncAssets = "LibrarySyncAssets",
LibrarySyncFilesQueueAll = "LibrarySyncFilesQueueAll",
LibrarySyncFiles = "LibrarySyncFiles",
LibraryScanQueueAll = "LibraryScanQueueAll",
MemoryCleanup = "MemoryCleanup",
MemoryGenerate = "MemoryGenerate",
NotificationsCleanup = "NotificationsCleanup",
NotifyUserSignup = "NotifyUserSignup",
NotifyAlbumInvite = "NotifyAlbumInvite",
NotifyAlbumUpdate = "NotifyAlbumUpdate",
UserDelete = "UserDelete",
UserDeleteCheck = "UserDeleteCheck",
UserSyncUsage = "UserSyncUsage",
PersonCleanup = "PersonCleanup",
PersonFileMigration = "PersonFileMigration",
PersonGenerateThumbnail = "PersonGenerateThumbnail",
SessionCleanup = "SessionCleanup",
SendMail = "SendMail",
SidecarQueueAll = "SidecarQueueAll",
SidecarCheck = "SidecarCheck",
SidecarWrite = "SidecarWrite",
SmartSearchQueueAll = "SmartSearchQueueAll",
SmartSearch = "SmartSearch",
StorageTemplateMigration = "StorageTemplateMigration",
StorageTemplateMigrationSingle = "StorageTemplateMigrationSingle",
TagCleanup = "TagCleanup",
VersionCheck = "VersionCheck",
OcrQueueAll = "OcrQueueAll",
Ocr = "Ocr",
WorkflowRun = "WorkflowRun",
IntegrityOrphanedFilesQueueAll = "IntegrityOrphanedFilesQueueAll",
IntegrityOrphanedFiles = "IntegrityOrphanedFiles",
IntegrityOrphanedRefresh = "IntegrityOrphanedRefresh",
IntegrityMissingFilesQueueAll = "IntegrityMissingFilesQueueAll",
IntegrityMissingFiles = "IntegrityMissingFiles",
IntegrityMissingFilesRefresh = "IntegrityMissingFilesRefresh",
IntegrityChecksumFiles = "IntegrityChecksumFiles",
IntegrityChecksumFilesRefresh = "IntegrityChecksumFilesRefresh",
IntegrityReportDelete = "IntegrityReportDelete"
}
export enum SearchSuggestionType {
Country = "country",
State = "state",

View File

@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "Monorepo for Immich",
"private": true,
"packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd",
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c",
"engines": {
"pnpm": ">=10.0.0"
}

View File

@@ -1,436 +1,519 @@
{
"name": "js-pdk-template",
"name": "plugins",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "js-pdk-template",
"name": "plugins",
"version": "1.0.0",
"license": "BSD-3-Clause",
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.19.6",
"esbuild": "^0.27.0",
"typescript": "^5.3.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz",
"integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz",
"integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz",
"integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz",
"integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz",
"integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz",
"integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz",
"integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz",
"integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz",
"integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz",
"integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz",
"integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz",
"integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz",
"integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz",
"integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz",
"integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz",
"integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz",
"integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz",
"integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==",
"cpu": [
"x64"
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz",
"integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz",
"integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz",
"integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz",
"integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz",
"integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz",
"integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz",
"integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz",
"integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@extism/js-pdk": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.0.1.tgz",
"integrity": "sha512-YJWfHGeOuJnQw4V8NPNHvbSr6S8iDd2Ga6VEukwlRP7tu62ozTxIgokYw8i+rajD/16zz/gK0KYARBpm2qPAmQ==",
"dev": true
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz",
"integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==",
"dev": true,
"license": "BSD-Clause-3",
"dependencies": {
"urlpattern-polyfill": "^8.0.2"
}
},
"node_modules/esbuild": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
"integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.19.12",
"@esbuild/android-arm": "0.19.12",
"@esbuild/android-arm64": "0.19.12",
"@esbuild/android-x64": "0.19.12",
"@esbuild/darwin-arm64": "0.19.12",
"@esbuild/darwin-x64": "0.19.12",
"@esbuild/freebsd-arm64": "0.19.12",
"@esbuild/freebsd-x64": "0.19.12",
"@esbuild/linux-arm": "0.19.12",
"@esbuild/linux-arm64": "0.19.12",
"@esbuild/linux-ia32": "0.19.12",
"@esbuild/linux-loong64": "0.19.12",
"@esbuild/linux-mips64el": "0.19.12",
"@esbuild/linux-ppc64": "0.19.12",
"@esbuild/linux-riscv64": "0.19.12",
"@esbuild/linux-s390x": "0.19.12",
"@esbuild/linux-x64": "0.19.12",
"@esbuild/netbsd-x64": "0.19.12",
"@esbuild/openbsd-x64": "0.19.12",
"@esbuild/sunos-x64": "0.19.12",
"@esbuild/win32-arm64": "0.19.12",
"@esbuild/win32-ia32": "0.19.12",
"@esbuild/win32-x64": "0.19.12"
"@esbuild/aix-ppc64": "0.27.0",
"@esbuild/android-arm": "0.27.0",
"@esbuild/android-arm64": "0.27.0",
"@esbuild/android-x64": "0.27.0",
"@esbuild/darwin-arm64": "0.27.0",
"@esbuild/darwin-x64": "0.27.0",
"@esbuild/freebsd-arm64": "0.27.0",
"@esbuild/freebsd-x64": "0.27.0",
"@esbuild/linux-arm": "0.27.0",
"@esbuild/linux-arm64": "0.27.0",
"@esbuild/linux-ia32": "0.27.0",
"@esbuild/linux-loong64": "0.27.0",
"@esbuild/linux-mips64el": "0.27.0",
"@esbuild/linux-ppc64": "0.27.0",
"@esbuild/linux-riscv64": "0.27.0",
"@esbuild/linux-s390x": "0.27.0",
"@esbuild/linux-x64": "0.27.0",
"@esbuild/netbsd-arm64": "0.27.0",
"@esbuild/netbsd-x64": "0.27.0",
"@esbuild/openbsd-arm64": "0.27.0",
"@esbuild/openbsd-x64": "0.27.0",
"@esbuild/openharmony-arm64": "0.27.0",
"@esbuild/sunos-x64": "0.27.0",
"@esbuild/win32-arm64": "0.27.0",
"@esbuild/win32-ia32": "0.27.0",
"@esbuild/win32-x64": "0.27.0"
}
},
"node_modules/typescript": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -438,6 +521,13 @@
"engines": {
"node": ">=14.17"
}
},
"node_modules/urlpattern-polyfill": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz",
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -13,7 +13,7 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.19.6",
"esbuild": "^0.27.0",
"typescript": "^5.3.2"
}
}

3766
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -1,4 +1,4 @@
FROM ghcr.io/immich-app/base-server-dev:202511041104@sha256:7558931a4a71989e7fd9fa3e1ba6c28da15891867310edda8c58236171839f2f AS builder
FROM ghcr.io/immich-app/base-server-dev:202511181104@sha256:fd445b91d4db131aae71b143b647d2262818dac80946078ce231c79cb9acecba AS builder
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
CI=1 \
COREPACK_HOME=/tmp \
@@ -69,7 +69,7 @@ RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \
--mount=type=cache,id=mise-tools,target=/buildcache/mise \
cd plugins && mise run build
FROM ghcr.io/immich-app/base-server-prod:202511041104@sha256:57c0379977fd5521d83cdf661aecd1497c83a9a661ebafe0a5243a09fc1064cb
FROM ghcr.io/immich-app/base-server-prod:202511181104@sha256:1bc2b7cebc4fd3296dc33a5779411e1c7d854ea713066c1e024d54f45f176f89
WORKDIR /usr/src/app
ENV NODE_ENV=production \

View File

@@ -1,5 +1,5 @@
# dev build
FROM ghcr.io/immich-app/base-server-dev:202511041104@sha256:7558931a4a71989e7fd9fa3e1ba6c28da15891867310edda8c58236171839f2f AS dev
FROM ghcr.io/immich-app/base-server-dev:202511181104@sha256:fd445b91d4db131aae71b143b647d2262818dac80946078ce231c79cb9acecba AS dev
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
CI=1 \

View File

@@ -120,7 +120,7 @@
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.4",
"@swc/core": "^1.4.14",
"@types/archiver": "^6.0.0",
"@types/archiver": "^7.0.0",
"@types/async-lock": "^1.4.2",
"@types/bcrypt": "^6.0.0",
"@types/body-parser": "^1.19.6",
@@ -134,7 +134,7 @@
"@types/luxon": "^3.6.2",
"@types/mock-fs": "^4.13.1",
"@types/multer": "^2.0.0",
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"@types/nodemailer": "^7.0.0",
"@types/picomatch": "^4.0.0",
"@types/pngjs": "^6.0.5",
@@ -166,7 +166,7 @@
"vitest": "^3.0.0"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
},
"overrides": {
"sharp": "^0.34.4"

View File

@@ -46,6 +46,22 @@ export interface SystemConfig {
accelDecode: boolean;
tonemap: ToneMapping;
};
integrityChecks: {
missingFiles: {
enabled: boolean;
cronExpression: string;
};
orphanedFiles: {
enabled: boolean;
cronExpression: string;
};
checksumFiles: {
enabled: boolean;
cronExpression: string;
timeLimit: number;
percentageLimit: number;
};
};
job: Record<ConcurrentQueueName, { concurrency: number }>;
logging: {
enabled: boolean;
@@ -222,6 +238,22 @@ export const defaults = Object.freeze<SystemConfig>({
accel: TranscodeHardwareAcceleration.Disabled,
accelDecode: false,
},
integrityChecks: {
missingFiles: {
enabled: true,
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
},
orphanedFiles: {
enabled: true,
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
},
checksumFiles: {
enabled: true,
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
timeLimit: 60 * 60 * 1000, // 1 hour
percentageLimit: 1, // 100% of assets
},
},
job: {
[QueueName.BackgroundTask]: { concurrency: 5 },
[QueueName.SmartSearch]: { concurrency: 2 },

View File

@@ -163,6 +163,8 @@ export const endpointTags: Record<ApiTag, string> = {
'A person is a collection of faces, which can be favorited and named. A person can also be merged into another person. People are automatically created via the face recognition job.',
[ApiTag.Plugins]:
'A plugin is an installed module that makes filters and actions available for the workflow feature.',
[ApiTag.Queues]:
'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.',
[ApiTag.Search]:
'Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting.',
[ApiTag.Server]:

View File

@@ -20,6 +20,7 @@ import { OAuthController } from 'src/controllers/oauth.controller';
import { PartnerController } from 'src/controllers/partner.controller';
import { PersonController } from 'src/controllers/person.controller';
import { PluginController } from 'src/controllers/plugin.controller';
import { QueueController } from 'src/controllers/queue.controller';
import { SearchController } from 'src/controllers/search.controller';
import { ServerController } from 'src/controllers/server.controller';
import { SessionController } from 'src/controllers/session.controller';
@@ -59,6 +60,7 @@ export const controllers = [
PartnerController,
PersonController,
PluginController,
QueueController,
SearchController,
ServerController,
SessionController,

View File

@@ -1,10 +1,12 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { JobCreateDto } from 'src/dtos/job.dto';
import { QueueCommandDto, QueueNameParamDto, QueueResponseDto, QueuesResponseDto } from 'src/dtos/queue.dto';
import { QueueResponseLegacyDto, QueuesResponseLegacyDto } from 'src/dtos/queue-legacy.dto';
import { QueueCommandDto, QueueNameParamDto } from 'src/dtos/queue.dto';
import { ApiTag, Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { JobService } from 'src/services/job.service';
import { QueueService } from 'src/services/queue.service';
@@ -21,10 +23,10 @@ export class JobController {
@Endpoint({
summary: 'Retrieve queue counts and status',
description: 'Retrieve the counts of the current queue, as well as the current status.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'),
})
getQueuesLegacy(): Promise<QueuesResponseDto> {
return this.queueService.getAll();
getQueuesLegacy(@Auth() auth: AuthDto): Promise<QueuesResponseLegacyDto> {
return this.queueService.getAllLegacy(auth);
}
@Post()
@@ -46,9 +48,12 @@ export class JobController {
summary: 'Run jobs',
description:
'Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'),
})
runQueueCommandLegacy(@Param() { name }: QueueNameParamDto, @Body() dto: QueueCommandDto): Promise<QueueResponseDto> {
return this.queueService.runCommand(name, dto);
runQueueCommandLegacy(
@Param() { name }: QueueNameParamDto,
@Body() dto: QueueCommandDto,
): Promise<QueueResponseLegacyDto> {
return this.queueService.runCommandLegacy(name, dto);
}
}

View File

@@ -1,19 +1,32 @@
import { BadRequestException, Body, Controller, Post, Res } from '@nestjs/common';
import { BadRequestException, Body, Controller, Delete, Get, Next, Param, Post, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { NextFunction, Response } from 'express';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { MaintenanceAuthDto, MaintenanceLoginDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
import {
MaintenanceAuthDto,
MaintenanceGetIntegrityReportDto,
MaintenanceIntegrityReportResponseDto,
MaintenanceIntegrityReportSummaryResponseDto,
MaintenanceLoginDto,
SetMaintenanceModeDto,
} from 'src/dtos/maintenance.dto';
import { ApiTag, ImmichCookie, MaintenanceAction, Permission } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
import { Auth, Authenticated, FileResponse, GetLoginDetails } from 'src/middleware/auth.guard';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { LoginDetails } from 'src/services/auth.service';
import { MaintenanceService } from 'src/services/maintenance.service';
import { sendFile } from 'src/utils/file';
import { respondWithCookie } from 'src/utils/response';
import { IntegrityReportTypeParamDto, UUIDParamDto } from 'src/validation';
@ApiTags(ApiTag.Maintenance)
@Controller('admin/maintenance')
export class MaintenanceController {
constructor(private service: MaintenanceService) {}
constructor(
private logger: LoggingRepository,
private service: MaintenanceService,
) {}
@Post('login')
@Endpoint({
@@ -46,4 +59,69 @@ export class MaintenanceController {
});
}
}
@Get('integrity/summary')
@Endpoint({
summary: 'Get integrity report summary',
description: '...',
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
})
@Authenticated({ permission: Permission.Maintenance, admin: true })
getIntegrityReportSummary(): Promise<MaintenanceIntegrityReportSummaryResponseDto> {
return this.service.getIntegrityReportSummary();
}
@Post('integrity/report')
@Endpoint({
summary: 'Get integrity report by type',
description: '...',
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
})
@Authenticated({ permission: Permission.Maintenance, admin: true })
getIntegrityReport(@Body() dto: MaintenanceGetIntegrityReportDto): Promise<MaintenanceIntegrityReportResponseDto> {
return this.service.getIntegrityReport(dto);
}
@Delete('integrity/report/:id')
@Endpoint({
summary: 'Delete report entry and perform corresponding deletion action',
description: '...',
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
})
@Authenticated({ permission: Permission.Maintenance, admin: true })
async deleteIntegrityReport(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
await this.service.deleteIntegrityReport(auth, id);
}
@Get('integrity/report/:type/csv')
@Endpoint({
summary: 'Export integrity report by type as CSV',
description: '...',
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
})
@FileResponse()
@Authenticated({ permission: Permission.Maintenance, admin: true })
getIntegrityReportCsv(@Param() { type }: IntegrityReportTypeParamDto, @Res() res: Response): void {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Cache-Control', 'private, no-cache, no-transform');
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(`${Date.now()}-${type}.csv`)}"`);
this.service.getIntegrityReportCsv(type).pipe(res);
}
@Get('integrity/report/:id/file')
@Endpoint({
summary: 'Download the orphan/broken file if one exists',
description: '...',
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
})
@FileResponse()
@Authenticated({ permission: Permission.Maintenance, admin: true })
async getIntegrityReportFile(
@Param() { id }: UUIDParamDto,
@Res() res: Response,
@Next() next: NextFunction,
): Promise<void> {
await sendFile(res, next, () => this.service.getIntegrityReportFile(id), this.logger);
}
}

View File

@@ -0,0 +1,85 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import {
QueueDeleteDto,
QueueJobResponseDto,
QueueJobSearchDto,
QueueNameParamDto,
QueueResponseDto,
QueueUpdateDto,
} from 'src/dtos/queue.dto';
import { ApiTag, Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { QueueService } from 'src/services/queue.service';
@ApiTags(ApiTag.Queues)
@Controller('queues')
export class QueueController {
constructor(private service: QueueService) {}
@Get()
@Authenticated({ permission: Permission.QueueRead, admin: true })
@Endpoint({
summary: 'List all queues',
description: 'Retrieves a list of queues.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueues(@Auth() auth: AuthDto): Promise<QueueResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':name')
@Authenticated({ permission: Permission.QueueRead, admin: true })
@Endpoint({
summary: 'Retrieve a queue',
description: 'Retrieves a specific queue by its name.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto): Promise<QueueResponseDto> {
return this.service.get(auth, name);
}
@Put(':name')
@Authenticated({ permission: Permission.QueueUpdate, admin: true })
@Endpoint({
summary: 'Update a queue',
description: 'Change the paused status of a specific queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
updateQueue(
@Auth() auth: AuthDto,
@Param() { name }: QueueNameParamDto,
@Body() dto: QueueUpdateDto,
): Promise<QueueResponseDto> {
return this.service.update(auth, name, dto);
}
@Get(':name/jobs')
@Authenticated({ permission: Permission.QueueJobRead, admin: true })
@Endpoint({
summary: 'Retrieve queue jobs',
description: 'Retrieves a list of queue jobs from the specified queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueueJobs(
@Auth() auth: AuthDto,
@Param() { name }: QueueNameParamDto,
@Query() dto: QueueJobSearchDto,
): Promise<QueueJobResponseDto[]> {
return this.service.searchJobs(auth, name, dto);
}
@Delete(':name/jobs')
@Authenticated({ permission: Permission.QueueJobDelete, admin: true })
@HttpCode(HttpStatus.NO_CONTENT)
@Endpoint({
summary: 'Empty a queue',
description: 'Removes all jobs from the specified queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
emptyQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto, @Body() dto: QueueDeleteDto): Promise<void> {
return this.service.emptyQueue(auth, name, dto);
}
}

View File

@@ -1,4 +1,5 @@
import { MaintenanceAction } from 'src/enum';
import { ApiProperty } from '@nestjs/swagger';
import { IntegrityReportType, MaintenanceAction } from 'src/enum';
import { ValidateEnum, ValidateString } from 'src/validation';
export class SetMaintenanceModeDto {
@@ -14,3 +15,40 @@ export class MaintenanceLoginDto {
export class MaintenanceAuthDto {
username!: string;
}
export class MaintenanceIntegrityReportSummaryResponseDto {
@ApiProperty({ type: 'integer' })
[IntegrityReportType.ChecksumFail]!: number;
@ApiProperty({ type: 'integer' })
[IntegrityReportType.MissingFile]!: number;
@ApiProperty({ type: 'integer' })
[IntegrityReportType.OrphanFile]!: number;
}
export class MaintenanceGetIntegrityReportDto {
@ValidateEnum({ enum: IntegrityReportType, name: 'IntegrityReportType' })
type!: IntegrityReportType;
// todo: paginate
// @IsInt()
// @Min(1)
// @Type(() => Number)
// @Optional()
// page?: number;
}
export class MaintenanceDeleteIntegrityReportDto {
@ValidateEnum({ enum: IntegrityReportType, name: 'IntegrityReportType' })
type!: IntegrityReportType;
}
class MaintenanceIntegrityReportDto {
id!: string;
@ValidateEnum({ enum: IntegrityReportType, name: 'IntegrityReportType' })
type!: IntegrityReportType;
path!: string;
}
export class MaintenanceIntegrityReportResponseDto {
items!: MaintenanceIntegrityReportDto[];
}

View File

@@ -0,0 +1,89 @@
import { ApiProperty } from '@nestjs/swagger';
import { QueueResponseDto, QueueStatisticsDto } from 'src/dtos/queue.dto';
import { QueueName } from 'src/enum';
export class QueueStatusLegacyDto {
isActive!: boolean;
isPaused!: boolean;
}
export class QueueResponseLegacyDto {
@ApiProperty({ type: QueueStatusLegacyDto })
queueStatus!: QueueStatusLegacyDto;
@ApiProperty({ type: QueueStatisticsDto })
jobCounts!: QueueStatisticsDto;
}
export class QueuesResponseLegacyDto implements Record<QueueName, QueueResponseLegacyDto> {
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.ThumbnailGeneration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.MetadataExtraction]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.VideoConversion]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.SmartSearch]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.StorageTemplateMigration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Migration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.BackgroundTask]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Search]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.DuplicateDetection]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.FaceDetection]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.FacialRecognition]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Sidecar]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Library]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Notification]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.BackupDatabase]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Ocr]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Workflow]!: QueueResponseLegacyDto;
}
export const mapQueueLegacy = (response: QueueResponseDto): QueueResponseLegacyDto => {
return {
queueStatus: {
isPaused: response.isPaused,
isActive: response.statistics.active > 0,
},
jobCounts: response.statistics,
};
};
export const mapQueuesLegacy = (responses: QueueResponseDto[]): QueuesResponseLegacyDto => {
const legacy = new QueuesResponseLegacyDto();
for (const response of responses) {
legacy[response.name] = mapQueueLegacy(response);
}
return legacy;
};

View File

@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { QueueCommand, QueueName } from 'src/enum';
import { HistoryBuilder, Property } from 'src/decorators';
import { JobName, QueueCommand, QueueJobStatus, QueueName } from 'src/enum';
import { ValidateBoolean, ValidateEnum } from 'src/validation';
export class QueueNameParamDto {
@@ -15,6 +16,46 @@ export class QueueCommandDto {
force?: boolean; // TODO: this uses undefined as a third state, which should be refactored to be more explicit
}
export class QueueUpdateDto {
@ValidateBoolean({ optional: true })
isPaused?: boolean;
}
export class QueueDeleteDto {
@ValidateBoolean({ optional: true })
@Property({
description: 'If true, will also remove failed jobs from the queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
failed?: boolean;
}
export class QueueJobSearchDto {
@ValidateEnum({ enum: QueueJobStatus, name: 'QueueJobStatus', optional: true, each: true })
status?: QueueJobStatus[];
}
export class QueueJobResponseDto {
id?: string;
@ValidateEnum({ enum: JobName, name: 'JobName' })
name!: JobName;
data!: object;
@ApiProperty({ type: 'integer' })
timestamp!: number;
}
export class QueueResponseDto {
@ValidateEnum({ enum: QueueName, name: 'QueueName' })
name!: QueueName;
@ValidateBoolean()
isPaused!: boolean;
statistics!: QueueStatisticsDto;
}
export class QueueStatisticsDto {
@ApiProperty({ type: 'integer' })
active!: number;
@@ -29,69 +70,3 @@ export class QueueStatisticsDto {
@ApiProperty({ type: 'integer' })
paused!: number;
}
export class QueueStatusDto {
isActive!: boolean;
isPaused!: boolean;
}
export class QueueResponseDto {
@ApiProperty({ type: QueueStatisticsDto })
jobCounts!: QueueStatisticsDto;
@ApiProperty({ type: QueueStatusDto })
queueStatus!: QueueStatusDto;
}
export class QueuesResponseDto implements Record<QueueName, QueueResponseDto> {
@ApiProperty({ type: QueueResponseDto })
[QueueName.ThumbnailGeneration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.MetadataExtraction]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.VideoConversion]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.SmartSearch]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.StorageTemplateMigration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Migration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.BackgroundTask]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Search]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.DuplicateDetection]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.FaceDetection]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.FacialRecognition]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Sidecar]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Library]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Notification]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.BackupDatabase]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Ocr]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Workflow]!: QueueResponseDto;
}

View File

@@ -38,6 +38,7 @@ const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled;
const isOAuthOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled;
const isEmailNotificationEnabled = (config: SystemConfigSmtpDto) => config.enabled;
const isDatabaseBackupEnabled = (config: DatabaseBackupConfig) => config.enabled;
const isEnabledProperty = (config: { enabled: boolean }) => config.enabled;
export class DatabaseBackupConfig {
@ValidateBoolean()
@@ -145,6 +146,42 @@ export class SystemConfigFFmpegDto {
tonemap!: ToneMapping;
}
class SystemConfigIntegrityJob {
@ValidateBoolean()
enabled!: boolean;
@ValidateIf(isEnabledProperty)
@IsNotEmpty()
@IsCronExpression()
@IsString()
cronExpression!: string;
}
class SystemConfigIntegrityChecksumJob extends SystemConfigIntegrityJob {
@IsInt()
timeLimit!: number;
@IsNumber()
percentageLimit!: number;
}
class SystemConfigIntegrityChecks {
@Type(() => SystemConfigIntegrityJob)
@ValidateNested()
@IsObject()
missingFiles!: SystemConfigIntegrityJob;
@Type(() => SystemConfigIntegrityJob)
@ValidateNested()
@IsObject()
orphanedFiles!: SystemConfigIntegrityJob;
@Type(() => SystemConfigIntegrityChecksumJob)
@ValidateNested()
@IsObject()
checksumFiles!: SystemConfigIntegrityChecksumJob;
}
class JobSettingsDto {
@IsInt()
@IsPositive()
@@ -649,6 +686,11 @@ export class SystemConfigDto implements SystemConfig {
@IsObject()
ffmpeg!: SystemConfigFFmpegDto;
@Type(() => SystemConfigIntegrityChecks)
@ValidateNested()
@IsObject()
integrityChecks!: SystemConfigIntegrityChecks;
@Type(() => SystemConfigLoggingDto)
@ValidateNested()
@IsObject()

View File

@@ -248,6 +248,14 @@ export enum Permission {
UserProfileImageUpdate = 'userProfileImage.update',
UserProfileImageDelete = 'userProfileImage.delete',
QueueRead = 'queue.read',
QueueUpdate = 'queue.update',
QueueJobCreate = 'queueJob.create',
QueueJobRead = 'queueJob.read',
QueueJobUpdate = 'queueJob.update',
QueueJobDelete = 'queueJob.delete',
WorkflowCreate = 'workflow.create',
WorkflowRead = 'workflow.read',
WorkflowUpdate = 'workflow.update',
@@ -293,6 +301,7 @@ export enum SystemMetadataKey {
SystemFlags = 'system-flags',
VersionCheckState = 'version-check-state',
License = 'license',
IntegrityChecksumCheckpoint = 'integrity-checksum-checkpoint',
}
export enum UserMetadataKey {
@@ -336,6 +345,12 @@ export enum SourceType {
Manual = 'manual',
}
export enum IntegrityReportType {
OrphanFile = 'orphan_file',
MissingFile = 'missing_file',
ChecksumFail = 'checksum_mismatch',
}
export enum ManualJobName {
PersonCleanup = 'person-cleanup',
TagCleanup = 'tag-cleanup',
@@ -343,6 +358,15 @@ export enum ManualJobName {
MemoryCleanup = 'memory-cleanup',
MemoryCreate = 'memory-create',
BackupDatabase = 'backup-database',
IntegrityMissingFiles = `integrity-missing-files`,
IntegrityOrphanFiles = `integrity-orphan-files`,
IntegrityChecksumFiles = `integrity-checksum-mismatch`,
IntegrityMissingFilesRefresh = `integrity-missing-files-refresh`,
IntegrityOrphanFilesRefresh = `integrity-orphan-files-refresh`,
IntegrityChecksumFilesRefresh = `integrity-checksum-mismatch-refresh`,
IntegrityMissingFilesDeleteAll = `integrity-missing-files-delete-all`,
IntegrityOrphanFilesDeleteAll = `integrity-orphan-files-delete-all`,
IntegrityChecksumFilesDeleteAll = `integrity-checksum-mismatch-delete-all`,
}
export enum AssetPathType {
@@ -543,6 +567,15 @@ export enum QueueName {
Workflow = 'workflow',
}
export enum QueueJobStatus {
Active = 'active',
Failed = 'failed',
Complete = 'completed',
Delayed = 'delayed',
Waiting = 'waiting',
Paused = 'paused',
}
export enum JobName {
AssetDelete = 'AssetDelete',
AssetDeleteCheck = 'AssetDeleteCheck',
@@ -620,13 +653,28 @@ export enum JobName {
// Workflow
WorkflowRun = 'WorkflowRun',
// Integrity
IntegrityOrphanedFilesQueueAll = 'IntegrityOrphanedFilesQueueAll',
IntegrityOrphanedFiles = 'IntegrityOrphanedFiles',
IntegrityOrphanedFilesRefresh = 'IntegrityOrphanedRefresh',
IntegrityMissingFilesQueueAll = 'IntegrityMissingFilesQueueAll',
IntegrityMissingFiles = 'IntegrityMissingFiles',
IntegrityMissingFilesRefresh = 'IntegrityMissingFilesRefresh',
IntegrityChecksumFiles = 'IntegrityChecksumFiles',
IntegrityChecksumFilesRefresh = 'IntegrityChecksumFilesRefresh',
IntegrityReportDelete = 'IntegrityReportDelete',
}
export enum QueueCommand {
Start = 'start',
/** @deprecated Use `updateQueue` instead */
Pause = 'pause',
/** @deprecated Use `updateQueue` instead */
Resume = 'resume',
/** @deprecated Use `emptyQueue` instead */
Empty = 'empty',
/** @deprecated Use `emptyQueue` instead */
ClearFailed = 'clear-failed',
}
@@ -658,6 +706,7 @@ export enum DatabaseLock {
GetSystemConfig = 69,
BackupDatabase = 42,
MemoryCreation = 777,
IntegrityCheck = 67,
}
export enum MaintenanceAction {
@@ -823,6 +872,7 @@ export enum ApiTag {
Partners = 'Partners',
People = 'People',
Plugins = 'Plugins',
Queues = 'Queues',
Search = 'Search',
Server = 'Server',
Sessions = 'Sessions',

View File

@@ -0,0 +1,179 @@
-- NOTE: This file is auto generated by ./sql-generator
-- IntegrityRepository.getById
select
"integrity_report".*
from
"integrity_report"
where
"id" = $1
-- IntegrityRepository.getIntegrityReportSummary
select
count(*) filter (
where
"type" = $1
) as "checksum_mismatch",
count(*) filter (
where
"type" = $2
) as "missing_file",
count(*) filter (
where
"type" = $3
) as "orphan_file"
from
"integrity_report"
-- IntegrityRepository.getIntegrityReports
select
"id",
"type",
"path",
"assetId",
"fileAssetId"
from
"integrity_report"
where
"type" = $1
order by
"createdAt" desc
-- IntegrityRepository.getAssetPathsByPaths
select
"originalPath",
"encodedVideoPath"
from
"asset"
where
(
"originalPath" in $1
or "encodedVideoPath" in $2
)
-- IntegrityRepository.getAssetFilePathsByPaths
select
"path"
from
"asset_file"
where
"path" in $1
-- IntegrityRepository.getAssetCount
select
count(*) as "count"
from
"asset"
-- IntegrityRepository.streamAllAssetPaths
select
"id",
"type",
"path",
"assetId",
"fileAssetId"
from
"integrity_report"
where
"type" = $1
order by
"createdAt" desc
select
"originalPath",
"encodedVideoPath"
from
"asset"
-- IntegrityRepository.streamAllAssetFilePaths
select
"path"
from
"asset_file"
-- IntegrityRepository.streamAssetPaths
select
"allPaths"."path" as "path",
"allPaths"."assetId",
"allPaths"."fileAssetId",
"integrity_report"."id" as "reportId"
from
(
select
"asset"."originalPath" as "path",
"asset"."id" as "assetId",
null::uuid as "fileAssetId"
from
"asset"
where
"asset"."deletedAt" is null
union all
select
"asset"."encodedVideoPath" as "path",
"asset"."id" as "assetId",
null::uuid as "fileAssetId"
from
"asset"
where
"asset"."deletedAt" is null
and "asset"."encodedVideoPath" is not null
and "asset"."encodedVideoPath" != ''
union all
select
"path",
null::uuid as "assetId",
"asset_file"."id" as "fileAssetId"
from
"asset_file"
) as "allPaths"
left join "integrity_report" on "integrity_report"."type" = $1
and (
"integrity_report"."assetId" = "allPaths"."assetId"
or "integrity_report"."fileAssetId" = "allPaths"."fileAssetId"
)
-- IntegrityRepository.streamAssetChecksums
select
"asset"."originalPath",
"asset"."checksum",
"asset"."createdAt",
"asset"."id" as "assetId",
"integrity_report"."id" as "reportId"
from
"asset"
left join "integrity_report" on "integrity_report"."assetId" = "asset"."id"
and "integrity_report"."type" = $1
where
"createdAt" >= $2
and "createdAt" <= $3
order by
"createdAt" asc
-- IntegrityRepository.streamIntegrityReports
select
"integrity_report"."id" as "reportId",
"integrity_report"."path"
from
"integrity_report"
where
"integrity_report"."type" = $1
-- IntegrityRepository.streamIntegrityReportsByProperty
select
"id",
"path",
"assetId",
"fileAssetId"
from
"integrity_report"
where
"abcdefghi" is not null
-- IntegrityRepository.deleteById
delete from "integrity_report"
where
"id" = $1
-- IntegrityRepository.deleteByIds
delete from "integrity_report"
where
"id" in $1

View File

@@ -249,7 +249,7 @@ const getEnv = (): EnvData => {
prefix: 'immich_bull',
connection: { ...redisConfig },
defaultJobOptions: {
attempts: 3,
attempts: 1,
removeOnComplete: true,
removeOnFail: false,
},

View File

@@ -15,6 +15,7 @@ import { DownloadRepository } from 'src/repositories/download.repository';
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
import { EmailRepository } from 'src/repositories/email.repository';
import { EventRepository } from 'src/repositories/event.repository';
import { IntegrityRepository } from 'src/repositories/integrity.repository';
import { JobRepository } from 'src/repositories/job.repository';
import { LibraryRepository } from 'src/repositories/library.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
@@ -68,6 +69,7 @@ export const repositories = [
DuplicateRepository,
EmailRepository,
EventRepository,
IntegrityRepository,
JobRepository,
LibraryRepository,
LoggingRepository,

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