Compare commits

...

42 Commits

Author SHA1 Message Date
martin
a20ff86591 Update web/src/lib/components/photos-page/asset-grid.svelte
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2024-05-27 21:02:08 +02:00
martin
43aa1e8162 Update web/src/lib/actions/focus.ts
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2024-05-27 21:02:01 +02:00
martabal
683f503647 feat: use shortcut to favorite and archive on asset-grid 2024-05-25 12:58:49 +02:00
Jason Rasmussen
9e71256191 chore(server): remove unused code (#9746) 2024-05-25 12:15:07 +02:00
Min Idzelis
d5cf8e4bfe refactor(server): move checkExistingAssets(), checkBulkUpdate() remove getAllAssets() (#9715)
* Refactor controller methods, non-breaking change

* Remove getAllAssets

* used imports

* sync:sql

* missing mock

* Removing remaining references

* chore: remove unused code

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-24 21:02:22 -04:00
Jason Rasmussen
95012dc19b fix: config error logging (#9738) 2024-05-24 16:44:50 -04:00
Lukas
f197f5d530 fix(server): use correct file extension for motion photo videos (#8659)
* fix(server): use mp4 file extension for motion photo videos in archive download

* always use mp4 for videos

* get file extension from originalPath

* remove console log

* store motion assets with mp4 extension

* add migration

* set originalFileName for live photo asset stubs

* leave down migration empty

* only set originalFileName for livePhotoStillAsset

* use separate stub

* shorter stub name
2024-05-24 16:38:18 -04:00
Jason Rasmussen
7168707395 refactor(server): remove unused code (#9737) 2024-05-24 16:37:29 -04:00
Snowknight26
847cb90038 fix(web): fix asset grid keyboard navigation (#9448)
* fix(web): fix asset grid keyboard navigation

* Ignore eslint rule

* Pass page up/down keys after focusing on grid

* Remove unneeded event listener, use existing class
2024-05-24 22:11:55 +02:00
bo0tzz
602f0a3499 fix(docs): Duplicate user key in example config.json (#9735)
related: #9734
2024-05-24 16:06:08 -04:00
Michel Heusschen
fdaa0e5413 fix(web): shared link isOwner check (#9729)
* fix(web): shared link isOwner check

* add e2e tests + update playwright

* fix formatting
2024-05-24 17:59:19 +00:00
Zack Pollard
39d2c4f37b chore: remove all deprecated endpoints/properties from server and mobile app (#9724)
* chore: remove deprecated title property from MemoryLaneResponseDto

* chore: remove deprecated webpPath and resizePath from MetadataSearchDto

* chore: remove deprecated sharedUserIds property from Album AddUsersDto

* chore: remove deprecated sharedUsers property from AlbumResponseDto

* chore: remove deprecated sharedWithUserIds property from CreateAlbumDto

* chore: remove deprecated isExternal and isReadOnly properties from AssetResponseDto

* chore: remove deprecated /server-info endpoint

* chore: bloody linters
2024-05-24 15:37:01 +01:00
Kedas
1f5d82e9d9 fix(mobile): respect SSL override during background sync (#9587) 2024-05-24 10:16:14 +01:00
Julian Collins
e98744f222 chore(docs): Russian readme update (#9691)
* Fix many typos, update features, add Activity and Star history sections

* Add clarity

* Add clarity
2024-05-24 10:14:07 +01:00
François-Xavier Payet
56ea07bcba fix(mobile): use correct Focus Node for latitude and longitude (#9699)
FIx focus node for longitude

Co-authored-by: François-Xavier Payet <fxpayet@salesapps.io>
2024-05-24 09:59:05 +01:00
Lukas
b3b258f32f fix(web): allow copying text in photo viewer (#9705)
* fix(web): allow copying text in photo viewer

* use default browser copy

* revert changes

* fix lint
2024-05-24 09:56:36 +01:00
Mert
69b5eb005f fix(server): use qsv format for hwmap (#9722)
use qsv format for hwmap
2024-05-24 09:50:28 +01:00
renovate[bot]
3f44a33eac chore(deps): update docker.io/redis:6.2-alpine docker digest to e31ca60 (#9717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 09:35:59 +01:00
renovate[bot]
b2a0422efb chore(deps): update redis:6.2-alpine docker digest to e31ca60 (#9718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 09:34:49 +01:00
Min Idzelis
562c43b6f5 test: reorder tests in asset.e2e-spec.ts (#9714)
* Reorder tests; make tests independent of ordering

* use it.each
2024-05-23 22:10:38 -04:00
Min Idzelis
4f21f6a2e1 feat: API operation replaceAsset, POST /api/asset/:id/file (#9684)
* impl and unit tests for replaceAsset

* Remove it.only

* Typo in generated spec +regen

* Remove unused dtos

* Dto removal fallout/bugfix

* fix - missed a line

* sql:generate

* Review comments

* Unused imports

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-23 20:26:22 -04:00
Lukas
76fdcc9863 fix(web): show api key copy button in Firefox (#9704) 2024-05-23 17:16:38 -04:00
Alex
57d94bce68 feat(web): deduplication UI (#9540) 2024-05-23 12:57:25 -05:00
martin
832d728940 refactor(web): svelte actions (#9701) 2024-05-23 12:56:48 -05:00
Michel Heusschen
8bfa6769a5 fix(web): hide detail panel for shared links with hidden metadata (#9700) 2024-05-23 12:39:06 -04:00
Jason Rasmussen
e7aa50425c test: sync open api spec (#9687)
test: sync spec file
2024-05-23 07:40:57 -04:00
Mert
a5e8b451b2 feat(server): qsv hardware decoding and tone-mapping (#9689)
* qsv hw decoding and tone-mapping

* fix vaapi

* add tests

* formatting

* handle device name without path
2024-05-23 03:58:29 +00:00
Jason Rasmussen
13cbdf6851 refactor(server): cli service (#9672) 2024-05-22 22:23:47 +02:00
Jason Rasmussen
967d195a05 chore(server): remove unused code (#9670) 2024-05-22 15:53:57 -04:00
Jason Rasmussen
8f37784eae refactor(server): /user profile endpoint (#9669)
* refactor(server): user profile endpoint

* chore: open api
2024-05-22 14:31:12 -04:00
Jason Rasmussen
ecd018a826 refactor(server): user info endpoint (#9668)
* refactor(server): user info endpoint

* chore: open api
2024-05-22 14:15:33 -04:00
Jason Rasmussen
202745f14b refactor(server): plural endpoints (#9667) 2024-05-22 13:24:57 -04:00
CodaBool
6a4c2e97c0 feat: add docker healthchecks to server and ml (#9583)
* add healthcheck

* format, import, IMMICH_PORT, and eslint change

* chore: clean up nodejs healthcheck

* fix ruff formating

* add healthcheck

* format, import, IMMICH_PORT, and eslint change

* chore: clean up nodejs healthcheck

* fix ruff formating

* add healthcheck to dockerfile

* poetry run ruff check --fix

* removed 2 of 3 console calls

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-22 16:54:29 +00:00
Jason Rasmussen
f6f82a5662 feat(web): s (#9663) 2024-05-22 09:33:37 -04:00
Michel Heusschen
ae21781442 fix(web): albums dark mode contrast + a11y issue (#9662) 2024-05-22 08:14:53 -04:00
Jason Rasmussen
06ce8247cc feat(server): user metadata (#9650)
* feat(server): user metadata

* add missing method to user mock

* update migration to include cascades

* update sql files

* test: fix e2e

* chore: clean up

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-05-22 08:13:36 -04:00
dependabot[bot]
a4887bfa7e chore(deps): bump ytanikin/PRConventionalCommits from 1.1.0 to 1.2.0 (#9661)
---
updated-dependencies:
- dependency-name: ytanikin/PRConventionalCommits
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 11:43:46 +01:00
renovate[bot]
27a02c75dc chore(deps): update dependency fastlane to v2.220.0 (#9653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-22 09:46:53 +00:00
Matthew Momjian
f8ee977b9e feat(server): healthchecks for PG and redis (#9590)
* HCs -> docker compose

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-05-22 09:28:12 +00:00
Zack Pollard
a3e7e8cc31 refactor: deprecate /server-info and replace with /server-info/storage (#9645) 2024-05-22 10:25:55 +01:00
Snowknight26
a341ab0050 refactor(web): refactor album selection modal and album summary component (#9658) 2024-05-22 00:15:28 -05:00
Lukas
61b850f0ce fix(web): emit updated date when pressing enter (#9640) 2024-05-21 16:58:57 +00:00
251 changed files with 4420 additions and 3020 deletions

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: PR Conventional Commit Validation
uses: ytanikin/PRConventionalCommits@1.1.0
uses: ytanikin/PRConventionalCommits@1.2.0
with:
task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]'
add_label: 'false'

View File

@@ -260,9 +260,18 @@ jobs:
name: OpenAPI Clients
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4
- name: Install server dependencies
run: npm --prefix=server ci
- name: Build the app
run: npm --prefix=server run build
- name: Run API generation
run: make open-api
- name: Find file changes
uses: tj-actions/verify-changed-files@v20
id: verify-changed-files
@@ -270,6 +279,8 @@ jobs:
files: |
mobile/openapi
open-api/typescript-sdk
open-api/immich-openapi-specs.json
- name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
run: |
@@ -332,7 +343,7 @@ jobs:
exit 1
- name: Run SQL generation
run: npm run sql:generate
run: npm run sync:sql
env:
DB_URL: postgres://postgres:postgres@localhost:5432/immich

View File

@@ -37,7 +37,7 @@ open-api-typescript:
cd ./open-api && bash ./bin/generate-open-api.sh typescript
sql:
npm --prefix server run sql:generate
npm --prefix server run sync:sql
attach-server:
docker exec -it docker_immich-server_1 sh

View File

@@ -81,7 +81,9 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
healthcheck:
test: redis-cli ping || exit 1
database:
container_name: immich_postgres
@@ -97,6 +99,11 @@ services:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
ports:
- 5432:5432
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
# set IMMICH_METRICS=true in .env to enable metrics

View File

@@ -12,12 +12,12 @@ services:
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
restart: always
ports:
- 2283:3001
depends_on:
- redis
- database
restart: always
immich-machine-learning:
container_name: immich_machine_learning
@@ -38,7 +38,9 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
@@ -55,7 +57,13 @@ services:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
ports:
- 5432:5432
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
restart: always
# set IMMICH_METRICS=true in .env to enable metrics
immich-prometheus:

View File

@@ -40,7 +40,9 @@ services:
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
image: docker.io/redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
@@ -53,8 +55,13 @@ services:
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
restart: always
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
restart: always
volumes:
model-cache:

View File

@@ -157,9 +157,6 @@ The default configuration looks like this:
"server": {
"externalDomain": "",
"loginPageMessage": ""
},
"user": {
"deleteDelay": 7
}
}
```

View File

@@ -27,7 +27,7 @@ services:
- 2283:3001
redis:
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0

26
e2e/package-lock.json generated
View File

@@ -11,7 +11,7 @@
"devDependencies": {
"@immich/cli": "file:../cli",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.41.2",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
"@types/node": "^20.11.17",
"@types/pg": "^8.11.0",
@@ -88,7 +88,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^20.12.12",
"@types/node": "^20.11.0",
"typescript": "^5.3.3"
}
},
@@ -971,12 +971,12 @@
}
},
"node_modules/@playwright/test": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz",
"integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==",
"version": "1.44.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz",
"integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==",
"dev": true,
"dependencies": {
"playwright": "1.44.0"
"playwright": "1.44.1"
},
"bin": {
"playwright": "cli.js"
@@ -4252,12 +4252,12 @@
}
},
"node_modules/playwright": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz",
"integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==",
"version": "1.44.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz",
"integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==",
"dev": true,
"dependencies": {
"playwright-core": "1.44.0"
"playwright-core": "1.44.1"
},
"bin": {
"playwright": "cli.js"
@@ -4270,9 +4270,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz",
"integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==",
"version": "1.44.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz",
"integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"

View File

@@ -21,7 +21,7 @@
"devDependencies": {
"@immich/cli": "file:../cli",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.41.2",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
"@types/node": "^20.11.17",
"@types/pg": "^8.11.0",

View File

@@ -14,7 +14,7 @@ import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
describe('/activity', () => {
describe('/activities', () => {
let admin: LoginResponseDto;
let nonOwner: LoginResponseDto;
let asset: AssetFileUploadResponseDto;
@@ -45,22 +45,24 @@ describe('/activity', () => {
await utils.resetDatabase(['activity']);
});
describe('GET /activity', () => {
describe('GET /activities', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/activity');
const { status, body } = await request(app).get('/activities');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require an albumId', async () => {
const { status, body } = await request(app).get('/activity').set('Authorization', `Bearer ${admin.accessToken}`);
const { status, body } = await request(app)
.get('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(400);
expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID'])));
});
it('should reject an invalid albumId', async () => {
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: uuidDto.invalid })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(400);
@@ -69,7 +71,7 @@ describe('/activity', () => {
it('should reject an invalid assetId', async () => {
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: uuidDto.notFound, assetId: uuidDto.invalid })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(400);
@@ -78,7 +80,7 @@ describe('/activity', () => {
it('should start off empty', async () => {
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual([]);
@@ -102,7 +104,7 @@ describe('/activity', () => {
]);
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(200);
@@ -121,7 +123,7 @@ describe('/activity', () => {
]);
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id, type: 'comment' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(200);
@@ -140,7 +142,7 @@ describe('/activity', () => {
]);
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id, type: 'like' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(200);
@@ -152,7 +154,7 @@ describe('/activity', () => {
const reaction = await createActivity({ albumId: album.id, type: ReactionType.Like });
const response1 = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id, userId: uuidDto.notFound })
.set('Authorization', `Bearer ${admin.accessToken}`);
@@ -160,7 +162,7 @@ describe('/activity', () => {
expect(response1.body.length).toBe(0);
const response2 = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id, userId: admin.userId })
.set('Authorization', `Bearer ${admin.accessToken}`);
@@ -180,7 +182,7 @@ describe('/activity', () => {
]);
const { status, body } = await request(app)
.get('/activity')
.get('/activities')
.query({ albumId: album.id, assetId: asset.id })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(200);
@@ -189,16 +191,16 @@ describe('/activity', () => {
});
});
describe('POST /activity', () => {
describe('POST /activities', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/activity');
const { status, body } = await request(app).post('/activities');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require an albumId', async () => {
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: uuidDto.invalid });
expect(status).toEqual(400);
@@ -207,7 +209,7 @@ describe('/activity', () => {
it('should require a comment when type is comment', async () => {
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: uuidDto.notFound, type: 'comment', comment: null });
expect(status).toEqual(400);
@@ -216,7 +218,7 @@ describe('/activity', () => {
it('should add a comment to an album', async () => {
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
albumId: album.id,
@@ -236,7 +238,7 @@ describe('/activity', () => {
it('should add a like to an album', async () => {
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: album.id, type: 'like' });
expect(status).toEqual(201);
@@ -253,7 +255,7 @@ describe('/activity', () => {
it('should return a 200 for a duplicate like on the album', async () => {
const reaction = await createActivity({ albumId: album.id, type: ReactionType.Like });
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: album.id, type: 'like' });
expect(status).toEqual(200);
@@ -267,7 +269,7 @@ describe('/activity', () => {
type: ReactionType.Like,
});
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: album.id, type: 'like' });
expect(status).toEqual(201);
@@ -276,7 +278,7 @@ describe('/activity', () => {
it('should add a comment to an asset', async () => {
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
albumId: album.id,
@@ -297,7 +299,7 @@ describe('/activity', () => {
it('should add a like to an asset', async () => {
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: album.id, assetId: asset.id, type: 'like' });
expect(status).toEqual(201);
@@ -319,7 +321,7 @@ describe('/activity', () => {
});
const { status, body } = await request(app)
.post('/activity')
.post('/activities')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: album.id, assetId: asset.id, type: 'like' });
expect(status).toEqual(200);
@@ -327,16 +329,16 @@ describe('/activity', () => {
});
});
describe('DELETE /activity/:id', () => {
describe('DELETE /activities/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete(`/activity/${uuidDto.notFound}`);
const { status, body } = await request(app).delete(`/activities/${uuidDto.notFound}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid uuid', async () => {
const { status, body } = await request(app)
.delete(`/activity/${uuidDto.invalid}`)
.delete(`/activities/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
@@ -349,7 +351,7 @@ describe('/activity', () => {
comment: 'This is a test comment',
});
const { status } = await request(app)
.delete(`/activity/${reaction.id}`)
.delete(`/activities/${reaction.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(204);
});
@@ -360,7 +362,7 @@ describe('/activity', () => {
type: ReactionType.Like,
});
const { status } = await request(app)
.delete(`/activity/${reaction.id}`)
.delete(`/activities/${reaction.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(204);
});
@@ -373,7 +375,7 @@ describe('/activity', () => {
});
const { status } = await request(app)
.delete(`/activity/${reaction.id}`)
.delete(`/activities/${reaction.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(204);
@@ -387,7 +389,7 @@ describe('/activity', () => {
});
const { status, body } = await request(app)
.delete(`/activity/${reaction.id}`)
.delete(`/activities/${reaction.id}`)
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
expect(status).toBe(400);
@@ -405,7 +407,7 @@ describe('/activity', () => {
);
const { status } = await request(app)
.delete(`/activity/${reaction.id}`)
.delete(`/activities/${reaction.id}`)
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
expect(status).toBe(204);

View File

@@ -23,7 +23,7 @@ const user2SharedUser = 'user2SharedUser';
const user2SharedLink = 'user2SharedLink';
const user2NotShared = 'user2NotShared';
describe('/album', () => {
describe('/albums', () => {
let admin: LoginResponseDto;
let user1: LoginResponseDto;
let user1Asset1: AssetFileUploadResponseDto;
@@ -110,16 +110,16 @@ describe('/album', () => {
await deleteUser({ id: user3.userId, deleteUserDto: {} }, { headers: asBearerAuth(admin.accessToken) });
});
describe('GET /album', () => {
describe('GET /albums', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/album');
const { status, body } = await request(app).get('/albums');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should reject an invalid shared param', async () => {
const { status, body } = await request(app)
.get('/album?shared=invalid')
.get('/albums?shared=invalid')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toEqual(400);
expect(body).toEqual(errorDto.badRequest(['shared must be a boolean value']));
@@ -127,7 +127,7 @@ describe('/album', () => {
it('should reject an invalid assetId param', async () => {
const { status, body } = await request(app)
.get('/album?assetId=invalid')
.get('/albums?assetId=invalid')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toEqual(400);
expect(body).toEqual(errorDto.badRequest(['assetId must be a UUID']));
@@ -135,7 +135,7 @@ describe('/album', () => {
it("should not show other users' favorites", async () => {
const { status, body } = await request(app)
.get(`/album/${user1Albums[0].id}?withoutAssets=false`)
.get(`/albums/${user1Albums[0].id}?withoutAssets=false`)
.set('Authorization', `Bearer ${user2.accessToken}`);
expect(status).toEqual(200);
expect(body).toEqual({
@@ -146,7 +146,7 @@ describe('/album', () => {
it('should not return shared albums with a deleted owner', async () => {
const { status, body } = await request(app)
.get('/album?shared=true')
.get('/albums?shared=true')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -178,7 +178,7 @@ describe('/album', () => {
});
it('should return the album collection including owned and shared', async () => {
const { status, body } = await request(app).get('/album').set('Authorization', `Bearer ${user1.accessToken}`);
const { status, body } = await request(app).get('/albums').set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(4);
expect(body).toEqual(
@@ -209,7 +209,7 @@ describe('/album', () => {
it('should return the album collection filtered by shared', async () => {
const { status, body } = await request(app)
.get('/album?shared=true')
.get('/albums?shared=true')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(4);
@@ -241,7 +241,7 @@ describe('/album', () => {
it('should return the album collection filtered by NOT shared', async () => {
const { status, body } = await request(app)
.get('/album?shared=false')
.get('/albums?shared=false')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(1);
@@ -258,7 +258,7 @@ describe('/album', () => {
it('should return the album collection filtered by assetId', async () => {
const { status, body } = await request(app)
.get(`/album?assetId=${user1Asset2.id}`)
.get(`/albums?assetId=${user1Asset2.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(1);
@@ -266,7 +266,7 @@ describe('/album', () => {
it('should return the album collection filtered by assetId and ignores shared=true', async () => {
const { status, body } = await request(app)
.get(`/album?shared=true&assetId=${user1Asset1.id}`)
.get(`/albums?shared=true&assetId=${user1Asset1.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(5);
@@ -274,23 +274,23 @@ describe('/album', () => {
it('should return the album collection filtered by assetId and ignores shared=false', async () => {
const { status, body } = await request(app)
.get(`/album?shared=false&assetId=${user1Asset1.id}`)
.get(`/albums?shared=false&assetId=${user1Asset1.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(5);
});
});
describe('GET /album/:id', () => {
describe('GET /albums/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/album/${user1Albums[0].id}`);
const { status, body } = await request(app).get(`/albums/${user1Albums[0].id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return album info for own album', async () => {
const { status, body } = await request(app)
.get(`/album/${user1Albums[0].id}?withoutAssets=false`)
.get(`/albums/${user1Albums[0].id}?withoutAssets=false`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -302,7 +302,7 @@ describe('/album', () => {
it('should return album info for shared album (editor)', async () => {
const { status, body } = await request(app)
.get(`/album/${user2Albums[0].id}?withoutAssets=false`)
.get(`/albums/${user2Albums[0].id}?withoutAssets=false`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -311,7 +311,7 @@ describe('/album', () => {
it('should return album info for shared album (viewer)', async () => {
const { status, body } = await request(app)
.get(`/album/${user1Albums[3].id}?withoutAssets=false`)
.get(`/albums/${user1Albums[3].id}?withoutAssets=false`)
.set('Authorization', `Bearer ${user2.accessToken}`);
expect(status).toBe(200);
@@ -320,7 +320,7 @@ describe('/album', () => {
it('should return album info with assets when withoutAssets is undefined', async () => {
const { status, body } = await request(app)
.get(`/album/${user1Albums[0].id}`)
.get(`/albums/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -332,7 +332,7 @@ describe('/album', () => {
it('should return album info without assets when withoutAssets is true', async () => {
const { status, body } = await request(app)
.get(`/album/${user1Albums[0].id}?withoutAssets=true`)
.get(`/albums/${user1Albums[0].id}?withoutAssets=true`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -344,16 +344,16 @@ describe('/album', () => {
});
});
describe('GET /album/count', () => {
describe('GET /albums/count', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/album/count');
const { status, body } = await request(app).get('/albums/count');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return total count of albums the user has access to', async () => {
const { status, body } = await request(app)
.get('/album/count')
.get('/albums/count')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -361,16 +361,16 @@ describe('/album', () => {
});
});
describe('POST /album', () => {
describe('POST /albums', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/album').send({ albumName: 'New album' });
const { status, body } = await request(app).post('/albums').send({ albumName: 'New album' });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should create an album', async () => {
const { status, body } = await request(app)
.post('/album')
.post('/albums')
.send({ albumName: 'New album' })
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(201);
@@ -383,7 +383,6 @@ describe('/album', () => {
description: '',
albumThumbnailAssetId: null,
shared: false,
sharedUsers: [],
albumUsers: [],
hasSharedLink: false,
assets: [],
@@ -395,9 +394,9 @@ describe('/album', () => {
});
});
describe('PUT /album/:id/assets', () => {
describe('PUT /albums/:id/assets', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/album/${user1Albums[0].id}/assets`);
const { status, body } = await request(app).put(`/albums/${user1Albums[0].id}/assets`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
@@ -405,7 +404,7 @@ describe('/album', () => {
it('should be able to add own asset to own album', async () => {
const asset = await utils.createAsset(user1.accessToken);
const { status, body } = await request(app)
.put(`/album/${user1Albums[0].id}/assets`)
.put(`/albums/${user1Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset.id] });
@@ -416,7 +415,7 @@ describe('/album', () => {
it('should be able to add own asset to shared album', async () => {
const asset = await utils.createAsset(user1.accessToken);
const { status, body } = await request(app)
.put(`/album/${user2Albums[0].id}/assets`)
.put(`/albums/${user2Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset.id] });
@@ -427,7 +426,7 @@ describe('/album', () => {
it('should not be able to add assets to album as a viewer', async () => {
const asset = await utils.createAsset(user2.accessToken);
const { status, body } = await request(app)
.put(`/album/${user1Albums[3].id}/assets`)
.put(`/albums/${user1Albums[3].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [asset.id] });
@@ -438,7 +437,7 @@ describe('/album', () => {
it('should add duplicate assets only once', async () => {
const asset = await utils.createAsset(user1.accessToken);
const { status, body } = await request(app)
.put(`/album/${user1Albums[0].id}/assets`)
.put(`/albums/${user1Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset.id, asset.id] });
@@ -450,10 +449,10 @@ describe('/album', () => {
});
});
describe('PATCH /album/:id', () => {
describe('PATCH /albums/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.patch(`/album/${uuidDto.notFound}`)
.patch(`/albums/${uuidDto.notFound}`)
.send({ albumName: 'New album name' });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -464,7 +463,7 @@ describe('/album', () => {
albumName: 'New album',
});
const { status, body } = await request(app)
.patch(`/album/${album.id}`)
.patch(`/albums/${album.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({
albumName: 'New album name',
@@ -481,7 +480,7 @@ describe('/album', () => {
it('should not be able to update as a viewer', async () => {
const { status, body } = await request(app)
.patch(`/album/${user1Albums[3].id}`)
.patch(`/albums/${user1Albums[3].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ albumName: 'New album name' });
@@ -491,7 +490,7 @@ describe('/album', () => {
it('should not be able to update as an editor', async () => {
const { status, body } = await request(app)
.patch(`/album/${user1Albums[0].id}`)
.patch(`/albums/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ albumName: 'New album name' });
@@ -500,10 +499,10 @@ describe('/album', () => {
});
});
describe('DELETE /album/:id/assets', () => {
describe('DELETE /albums/:id/assets', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[0].id}/assets`)
.delete(`/albums/${user1Albums[0].id}/assets`)
.send({ ids: [user1Asset1.id] });
expect(status).toBe(401);
@@ -512,7 +511,7 @@ describe('/album', () => {
it('should not be able to remove foreign asset from own album', async () => {
const { status, body } = await request(app)
.delete(`/album/${user2Albums[0].id}/assets`)
.delete(`/albums/${user2Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
@@ -528,7 +527,7 @@ describe('/album', () => {
it('should not be able to remove foreign asset from foreign album', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[0].id}/assets`)
.delete(`/albums/${user1Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
@@ -544,7 +543,7 @@ describe('/album', () => {
it('should be able to remove own asset from own album', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[0].id}/assets`)
.delete(`/albums/${user1Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [user1Asset1.id] });
@@ -554,7 +553,7 @@ describe('/album', () => {
it('should be able to remove own asset from shared album', async () => {
const { status, body } = await request(app)
.delete(`/album/${user2Albums[0].id}/assets`)
.delete(`/albums/${user2Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [user1Asset1.id] });
@@ -564,7 +563,7 @@ describe('/album', () => {
it('should not be able to remove assets from album as a viewer', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[3].id}/assets`)
.delete(`/albums/${user1Albums[3].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
@@ -574,7 +573,7 @@ describe('/album', () => {
it('should remove duplicate assets only once', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[1].id}/assets`)
.delete(`/albums/${user1Albums[1].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [user1Asset1.id, user1Asset1.id] });
@@ -596,7 +595,7 @@ describe('/album', () => {
});
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/album/${user1Albums[0].id}/users`).send({ sharedUserIds: [] });
const { status, body } = await request(app).put(`/albums/${user1Albums[0].id}/users`).send({ sharedUserIds: [] });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -604,21 +603,25 @@ describe('/album', () => {
it('should be able to add user to own album', async () => {
const { status, body } = await request(app)
.put(`/album/${album.id}/users`)
.put(`/albums/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
sharedUsers: [expect.objectContaining({ id: user2.userId })],
albumUsers: [
expect.objectContaining({
user: expect.objectContaining({ id: user2.userId }),
}),
],
}),
);
});
it('should not be able to share album with owner', async () => {
const { status, body } = await request(app)
.put(`/album/${album.id}/users`)
.put(`/albums/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
@@ -628,12 +631,12 @@ describe('/album', () => {
it('should not be able to add existing user to shared album', async () => {
await request(app)
.put(`/album/${album.id}/users`)
.put(`/albums/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
const { status, body } = await request(app)
.put(`/album/${album.id}/users`)
.put(`/albums/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
@@ -652,14 +655,16 @@ describe('/album', () => {
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status } = await request(app)
.put(`/album/${album.id}/user/${user2.userId}`)
.put(`/albums/${album.id}/user/${user2.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ role: AlbumUserRole.Editor });
expect(status).toBe(200);
// Get album to verify the role change
const { body } = await request(app).get(`/album/${album.id}`).set('Authorization', `Bearer ${user1.accessToken}`);
const { body } = await request(app)
.get(`/albums/${album.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(body).toEqual(
expect.objectContaining({
albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })],
@@ -676,7 +681,7 @@ describe('/album', () => {
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status, body } = await request(app)
.put(`/album/${album.id}/user/${user2.userId}`)
.put(`/albums/${album.id}/user/${user2.userId}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ role: AlbumUserRole.Editor });

View File

@@ -5,6 +5,7 @@ import {
LoginResponseDto,
SharedLinkType,
getAssetInfo,
getMyUserInfo,
updateAssets,
} from '@immich/sdk';
import { exiftool } from 'exiftool-vendored';
@@ -71,7 +72,7 @@ describe('/asset', () => {
let stackAssets: AssetFileUploadResponseDto[];
let locationAsset: AssetFileUploadResponseDto;
beforeAll(async () => {
const setupTests = async () => {
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
@@ -154,7 +155,8 @@ describe('/asset', () => {
assetId: user1Assets[0].id,
personId: person1.id,
});
}, 30_000);
};
beforeAll(setupTests, 30_000);
afterAll(() => {
utils.disconnectWebsocket(websocket);
@@ -538,14 +540,321 @@ describe('/asset', () => {
});
});
describe('GET /asset/thumbnail/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/asset/thumbnail/${locationAsset.id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should not include gps data for webp thumbnails', async () => {
await utils.waitForWebsocketEvent({
event: 'assetUpload',
id: locationAsset.id,
});
const { status, body, type } = await request(app)
.get(`/asset/thumbnail/${locationAsset.id}?format=WEBP`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toBeDefined();
expect(type).toBe('image/webp');
const exifData = await readTags(body, 'thumbnail.webp');
expect(exifData).not.toHaveProperty('GPSLongitude');
expect(exifData).not.toHaveProperty('GPSLatitude');
});
it('should not include gps data for jpeg thumbnails', async () => {
const { status, body, type } = await request(app)
.get(`/asset/thumbnail/${locationAsset.id}?format=JPEG`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toBeDefined();
expect(type).toBe('image/jpeg');
const exifData = await readTags(body, 'thumbnail.jpg');
expect(exifData).not.toHaveProperty('GPSLongitude');
expect(exifData).not.toHaveProperty('GPSLatitude');
});
});
describe('GET /asset/file/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/asset/thumbnail/${locationAsset.id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should download the original', async () => {
const { status, body, type } = await request(app)
.get(`/asset/file/${locationAsset.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toBeDefined();
expect(type).toBe('image/jpeg');
const asset = await utils.getAssetInfo(admin.accessToken, locationAsset.id);
const original = await readFile(locationAssetFilepath);
const originalChecksum = utils.sha1(original);
const downloadChecksum = utils.sha1(body);
expect(originalChecksum).toBe(downloadChecksum);
expect(downloadChecksum).toBe(asset.checksum);
});
});
describe('GET /asset/map-marker', () => {
beforeAll(async () => {
const files = [
'formats/avif/8bit-sRGB.avif',
'formats/jpg/el_torcal_rocks.jpg',
'formats/jxl/8bit-sRGB.jxl',
'formats/heic/IMG_2682.heic',
'formats/png/density_plot.png',
'formats/raw/Nikon/D80/glarus.nef',
'formats/raw/Nikon/D700/philadelphia.nef',
'formats/raw/Panasonic/DMC-GH4/4_3.rw2',
'formats/raw/Sony/ILCE-6300/12bit-compressed-(3_2).arw',
'formats/raw/Sony/ILCE-7M2/14bit-uncompressed-(3_2).arw',
];
utils.resetEvents();
const uploadFile = async (input: string) => {
const filepath = join(testAssetDir, input);
const { id } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
};
const uploads = files.map((f) => uploadFile(f));
await Promise.all(uploads);
}, 30_000);
it('should require authentication', async () => {
const { status, body } = await request(app).get('/asset/map-marker');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
// TODO archive one of these assets
it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(app)
.get('/asset/map-marker')
.query({ isArchived: false })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(2);
expect(body).toEqual([
{
city: 'Palisade',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
state: 'Colorado',
},
{
city: 'Ralston',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
state: 'Nebraska',
},
]);
});
// TODO archive one of these assets
it('should get all map markers', async () => {
const { status, body } = await request(app)
.get('/asset/map-marker')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([
{
city: 'Palisade',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
state: 'Colorado',
},
{
city: 'Ralston',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
state: 'Nebraska',
},
]);
});
});
describe('PUT /asset', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put('/asset');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid parent id', async () => {
const { status, body } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ stackParentId: uuidDto.invalid, ids: [stackAssets[0].id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['stackParentId must be a UUID']));
});
it('should require access to the parent', async () => {
const { status, body } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ stackParentId: stackAssets[3].id, ids: [user1Assets[0].id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission);
});
it('should add stack children', async () => {
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ stackParentId: stackAssets[0].id, ids: [stackAssets[3].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: stackAssets[3].id })]));
});
it('should remove stack children', async () => {
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ removeParent: true, ids: [stackAssets[1].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[2].id }),
expect.objectContaining({ id: stackAssets[3].id }),
]),
);
});
it('should remove all stack children', async () => {
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ removeParent: true, ids: [stackAssets[2].id, stackAssets[3].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).toBeUndefined();
});
it('should merge stack children', async () => {
// create stack after previous test removed stack children
await updateAssets(
{ assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } },
{ headers: asBearerAuth(stackUser.accessToken) },
);
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ stackParentId: stackAssets[3].id, ids: [stackAssets[0].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[3].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[0].id }),
expect.objectContaining({ id: stackAssets[1].id }),
expect.objectContaining({ id: stackAssets[2].id }),
]),
);
});
});
describe('PUT /asset/stack/parent', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put('/asset/stack/parent');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.put('/asset/stack/parent')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ oldParentId: uuidDto.invalid, newParentId: uuidDto.invalid });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest());
});
it('should require access', async () => {
const { status, body } = await request(app)
.put('/asset/stack/parent')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ oldParentId: stackAssets[3].id, newParentId: stackAssets[0].id });
expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission);
});
it('should make old parent child of new parent', async () => {
const { status } = await request(app)
.put('/asset/stack/parent')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ oldParentId: stackAssets[3].id, newParentId: stackAssets[0].id });
expect(status).toBe(200);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
// new parent
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[1].id }),
expect.objectContaining({ id: stackAssets[2].id }),
expect.objectContaining({ id: stackAssets[3].id }),
]),
);
});
});
describe('POST /asset/upload', () => {
beforeAll(setupTests, 30_000);
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/asset/upload`);
expect(body).toEqual(errorDto.unauthorized);
expect(status).toBe(401);
});
const invalid = [
it.each([
{ should: 'require `deviceAssetId`', dto: { ...makeUploadDto({ omit: 'deviceAssetId' }) } },
{ should: 'require `deviceId`', dto: { ...makeUploadDto({ omit: 'deviceId' }) } },
{ should: 'require `fileCreatedAt`', dto: { ...makeUploadDto({ omit: 'fileCreatedAt' }) } },
@@ -554,21 +863,17 @@ describe('/asset', () => {
{ should: 'throw if `isFavorite` is not a boolean', dto: { ...makeUploadDto(), isFavorite: 'not-a-boolean' } },
{ should: 'throw if `isVisible` is not a boolean', dto: { ...makeUploadDto(), isVisible: 'not-a-boolean' } },
{ should: 'throw if `isArchived` is not a boolean', dto: { ...makeUploadDto(), isArchived: 'not-a-boolean' } },
];
])('should $should', async ({ dto }) => {
const { status, body } = await request(app)
.post('/asset/upload')
.set('Authorization', `Bearer ${user1.accessToken}`)
.attach('assetData', makeRandomImage(), 'example.png')
.field(dto);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest());
});
for (const { should, dto } of invalid) {
it(`should ${should}`, async () => {
const { status, body } = await request(app)
.post('/asset/upload')
.set('Authorization', `Bearer ${user1.accessToken}`)
.attach('assetData', makeRandomImage(), 'example.png')
.field(dto);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest());
});
}
const tests = [
it.each([
{
input: 'formats/avif/8bit-sRGB.avif',
expected: {
@@ -784,26 +1089,22 @@ describe('/asset', () => {
},
},
},
];
for (const { input, expected } of tests) {
it(`should upload and generate a thumbnail for ${input}`, async () => {
const filepath = join(testAssetDir, input);
const { id, duplicate } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
});
expect(duplicate).toBe(false);
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: id });
const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(asset.exifInfo).toBeDefined();
expect(asset.exifInfo).toMatchObject(expected.exifInfo);
expect(asset).toMatchObject(expected);
])(`should upload and generate a thumbnail for $input`, async ({ input, expected }) => {
const filepath = join(testAssetDir, input);
const { id, duplicate } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
});
}
expect(duplicate).toBe(false);
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: id });
const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(asset.exifInfo).toBeDefined();
expect(asset.exifInfo).toMatchObject(expected.exifInfo);
expect(asset).toMatchObject(expected);
});
it('should handle a duplicate', async () => {
const filepath = 'formats/jpeg/el_torcal_rocks.jpeg';
@@ -830,7 +1131,7 @@ describe('/asset', () => {
expect(body).toEqual({ id: expect.any(String), duplicate: false });
expect(status).toBe(201);
const { body: user } = await request(app).get('/user/me').set('Authorization', `Bearer ${quotaUser.accessToken}`);
const user = await getMyUserInfo({ headers: asBearerAuth(quotaUser.accessToken) });
expect(user).toEqual(expect.objectContaining({ quotaUsageInBytes: 70 }));
});
@@ -854,7 +1155,7 @@ describe('/asset', () => {
// This ensures that immich+exiftool are extracting the videos the same way Samsung does.
// DO NOT assume immich+exiftool are doing things correctly and just copy whatever hash it gives
// into the test here.
const motionTests = [
it.each([
{
filepath: 'formats/motionphoto/Samsung One UI 5.jpg',
checksum: 'fr14niqCq6N20HB8rJYEvpsUVtI=',
@@ -867,329 +1168,23 @@ describe('/asset', () => {
filepath: 'formats/motionphoto/Samsung One UI 6.heic',
checksum: '/ejgzywvgvzvVhUYVfvkLzFBAF0=',
},
];
for (const { filepath, checksum } of motionTests) {
it(`should extract motionphoto video from ${filepath}`, async () => {
const response = await utils.createAsset(admin.accessToken, {
assetData: {
bytes: await readFile(join(testAssetDir, filepath)),
filename: basename(filepath),
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: response.id });
expect(response.duplicate).toBe(false);
const asset = await utils.getAssetInfo(admin.accessToken, response.id);
expect(asset.livePhotoVideoId).toBeDefined();
const video = await utils.getAssetInfo(admin.accessToken, asset.livePhotoVideoId as string);
expect(video.checksum).toStrictEqual(checksum);
});
}
});
describe('GET /asset/thumbnail/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/asset/thumbnail/${locationAsset.id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should not include gps data for webp thumbnails', async () => {
await utils.waitForWebsocketEvent({
event: 'assetUpload',
id: locationAsset.id,
])(`should extract motionphoto video from $filepath`, async ({ filepath, checksum }) => {
const response = await utils.createAsset(admin.accessToken, {
assetData: {
bytes: await readFile(join(testAssetDir, filepath)),
filename: basename(filepath),
},
});
const { status, body, type } = await request(app)
.get(`/asset/thumbnail/${locationAsset.id}?format=WEBP`)
.set('Authorization', `Bearer ${admin.accessToken}`);
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: response.id });
expect(status).toBe(200);
expect(body).toBeDefined();
expect(type).toBe('image/webp');
expect(response.duplicate).toBe(false);
const exifData = await readTags(body, 'thumbnail.webp');
expect(exifData).not.toHaveProperty('GPSLongitude');
expect(exifData).not.toHaveProperty('GPSLatitude');
});
const asset = await utils.getAssetInfo(admin.accessToken, response.id);
expect(asset.livePhotoVideoId).toBeDefined();
it('should not include gps data for jpeg thumbnails', async () => {
const { status, body, type } = await request(app)
.get(`/asset/thumbnail/${locationAsset.id}?format=JPEG`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toBeDefined();
expect(type).toBe('image/jpeg');
const exifData = await readTags(body, 'thumbnail.jpg');
expect(exifData).not.toHaveProperty('GPSLongitude');
expect(exifData).not.toHaveProperty('GPSLatitude');
});
});
describe('GET /asset/file/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/asset/thumbnail/${locationAsset.id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should download the original', async () => {
const { status, body, type } = await request(app)
.get(`/asset/file/${locationAsset.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toBeDefined();
expect(type).toBe('image/jpeg');
const asset = await utils.getAssetInfo(admin.accessToken, locationAsset.id);
const original = await readFile(locationAssetFilepath);
const originalChecksum = utils.sha1(original);
const downloadChecksum = utils.sha1(body);
expect(originalChecksum).toBe(downloadChecksum);
expect(downloadChecksum).toBe(asset.checksum);
});
});
describe('GET /asset/map-marker', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/asset/map-marker');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
// TODO archive one of these assets
it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(app)
.get('/asset/map-marker')
.query({ isArchived: false })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(2);
expect(body).toEqual([
{
city: 'Palisade',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
state: 'Colorado',
},
{
city: 'Ralston',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
state: 'Nebraska',
},
]);
});
// TODO archive one of these assets
it('should get all map markers', async () => {
const { status, body } = await request(app)
.get('/asset/map-marker')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual([
{
city: 'Palisade',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
state: 'Colorado',
},
{
city: 'Ralston',
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
state: 'Nebraska',
},
]);
});
});
describe('GET /asset', () => {
it('should return stack data', async () => {
const { status, body } = await request(app).get('/asset').set('Authorization', `Bearer ${stackUser.accessToken}`);
const stack = body.find((asset: AssetResponseDto) => asset.id === stackAssets[0].id);
expect(status).toBe(200);
expect(stack).toEqual(
expect.objectContaining({
stackCount: 3,
stack:
// Response includes children at the root level
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[1].id }),
expect.objectContaining({ id: stackAssets[2].id }),
]),
}),
);
});
});
describe('PUT /asset', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put('/asset');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid parent id', async () => {
const { status, body } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ stackParentId: uuidDto.invalid, ids: [stackAssets[0].id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['stackParentId must be a UUID']));
});
it('should require access to the parent', async () => {
const { status, body } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ stackParentId: stackAssets[3].id, ids: [user1Assets[0].id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission);
});
it('should add stack children', async () => {
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ stackParentId: stackAssets[0].id, ids: [stackAssets[3].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: stackAssets[3].id })]));
});
it('should remove stack children', async () => {
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ removeParent: true, ids: [stackAssets[1].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[2].id }),
expect.objectContaining({ id: stackAssets[3].id }),
]),
);
});
it('should remove all stack children', async () => {
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ removeParent: true, ids: [stackAssets[2].id, stackAssets[3].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).toBeUndefined();
});
it('should merge stack children', async () => {
// create stack after previous test removed stack children
await updateAssets(
{ assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } },
{ headers: asBearerAuth(stackUser.accessToken) },
);
const { status } = await request(app)
.put('/asset')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ stackParentId: stackAssets[3].id, ids: [stackAssets[0].id] });
expect(status).toBe(204);
const asset = await getAssetInfo({ id: stackAssets[3].id }, { headers: asBearerAuth(stackUser.accessToken) });
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[0].id }),
expect.objectContaining({ id: stackAssets[1].id }),
expect.objectContaining({ id: stackAssets[2].id }),
]),
);
});
});
describe('PUT /asset/stack/parent', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put('/asset/stack/parent');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.put('/asset/stack/parent')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ oldParentId: uuidDto.invalid, newParentId: uuidDto.invalid });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest());
});
it('should require access', async () => {
const { status, body } = await request(app)
.put('/asset/stack/parent')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ oldParentId: stackAssets[3].id, newParentId: stackAssets[0].id });
expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission);
});
it('should make old parent child of new parent', async () => {
const { status } = await request(app)
.put('/asset/stack/parent')
.set('Authorization', `Bearer ${stackUser.accessToken}`)
.send({ oldParentId: stackAssets[3].id, newParentId: stackAssets[0].id });
expect(status).toBe(200);
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
// new parent
expect(asset.stack).not.toBeUndefined();
expect(asset.stack).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: stackAssets[1].id }),
expect.objectContaining({ id: stackAssets[2].id }),
expect.objectContaining({ id: stackAssets[3].id }),
]),
);
const video = await utils.getAssetInfo(admin.accessToken, asset.livePhotoVideoId as string);
expect(video.checksum).toStrictEqual(checksum);
});
});
});

View File

@@ -2,7 +2,7 @@ import { deleteAssets, getAuditFiles, updateAsset, type LoginResponseDto } from
import { asBearerAuth, utils } from 'src/utils';
import { beforeAll, describe, expect, it } from 'vitest';
describe('/audit', () => {
describe('/audits', () => {
let admin: LoginResponseDto;
beforeAll(async () => {

View File

@@ -11,7 +11,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
const scan = async (accessToken: string, id: string, dto: ScanLibraryDto = {}) =>
scanLibrary({ id, scanLibraryDto: dto }, { headers: asBearerAuth(accessToken) });
describe('/library', () => {
describe('/libraries', () => {
let admin: LoginResponseDto;
let user: LoginResponseDto;
let library: LibraryResponseDto;
@@ -37,24 +37,24 @@ describe('/library', () => {
utils.resetEvents();
});
describe('GET /library', () => {
describe('GET /libraries', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/library');
const { status, body } = await request(app).get('/libraries');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
});
describe('POST /library', () => {
describe('POST /libraries', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/library').send({});
const { status, body } = await request(app).post('/libraries').send({});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require admin authentication', async () => {
const { status, body } = await request(app)
.post('/library')
.post('/libraries')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({ ownerId: admin.userId });
@@ -64,7 +64,7 @@ describe('/library', () => {
it('should create an external library with defaults', async () => {
const { status, body } = await request(app)
.post('/library')
.post('/libraries')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId });
@@ -83,7 +83,7 @@ describe('/library', () => {
it('should create an external library with options', async () => {
const { status, body } = await request(app)
.post('/library')
.post('/libraries')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
@@ -103,7 +103,7 @@ describe('/library', () => {
it('should not create an external library with duplicate import paths', async () => {
const { status, body } = await request(app)
.post('/library')
.post('/libraries')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
@@ -118,7 +118,7 @@ describe('/library', () => {
it('should not create an external library with duplicate exclusion patterns', async () => {
const { status, body } = await request(app)
.post('/library')
.post('/libraries')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
@@ -132,16 +132,16 @@ describe('/library', () => {
});
});
describe('PUT /library/:id', () => {
describe('PUT /libraries/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/library/${uuidDto.notFound}`).send({});
const { status, body } = await request(app).put(`/libraries/${uuidDto.notFound}`).send({});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should change the library name', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ name: 'New Library Name' });
@@ -155,7 +155,7 @@ describe('/library', () => {
it('should not set an empty name', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ name: '' });
@@ -165,7 +165,7 @@ describe('/library', () => {
it('should change the import paths', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ importPaths: [testAssetDirInternal] });
@@ -179,7 +179,7 @@ describe('/library', () => {
it('should reject an empty import path', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ importPaths: [''] });
@@ -189,7 +189,7 @@ describe('/library', () => {
it('should reject duplicate import paths', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ importPaths: ['/path', '/path'] });
@@ -199,7 +199,7 @@ describe('/library', () => {
it('should change the exclusion pattern', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ exclusionPatterns: ['**/Raw/**'] });
@@ -213,7 +213,7 @@ describe('/library', () => {
it('should reject duplicate exclusion patterns', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
@@ -223,7 +223,7 @@ describe('/library', () => {
it('should reject an empty exclusion pattern', async () => {
const { status, body } = await request(app)
.put(`/library/${library.id}`)
.put(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ exclusionPatterns: [''] });
@@ -232,9 +232,9 @@ describe('/library', () => {
});
});
describe('GET /library/:id', () => {
describe('GET /libraries/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/library/${uuidDto.notFound}`);
const { status, body } = await request(app).get(`/libraries/${uuidDto.notFound}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -242,7 +242,7 @@ describe('/library', () => {
it('should require admin access', async () => {
const { status, body } = await request(app)
.get(`/library/${uuidDto.notFound}`)
.get(`/libraries/${uuidDto.notFound}`)
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
@@ -252,7 +252,7 @@ describe('/library', () => {
const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
const { status, body } = await request(app)
.get(`/library/${library.id}`)
.get(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
@@ -269,18 +269,18 @@ describe('/library', () => {
});
});
describe('GET /library/:id/statistics', () => {
describe('GET /libraries/:id/statistics', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/library/${uuidDto.notFound}/statistics`);
const { status, body } = await request(app).get(`/libraries/${uuidDto.notFound}/statistics`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
});
describe('POST /library/:id/scan', () => {
describe('POST /libraries/:id/scan', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/library/${uuidDto.notFound}/scan`).send({});
const { status, body } = await request(app).post(`/libraries/${uuidDto.notFound}/scan`).send({});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -496,9 +496,9 @@ describe('/library', () => {
});
});
describe('POST /library/:id/removeOffline', () => {
describe('POST /libraries/:id/removeOffline', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/library/${uuidDto.notFound}/removeOffline`).send({});
const { status, body } = await request(app).post(`/libraries/${uuidDto.notFound}/removeOffline`).send({});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -532,7 +532,7 @@ describe('/library', () => {
expect(offlineAssets.count).toBe(1);
const { status } = await request(app)
.post(`/library/${library.id}/removeOffline`)
.post(`/libraries/${library.id}/removeOffline`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
@@ -557,7 +557,7 @@ describe('/library', () => {
expect(assetsBefore.count).toBeGreaterThan(1);
const { status } = await request(app)
.post(`/library/${library.id}/removeOffline`)
.post(`/libraries/${library.id}/removeOffline`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
@@ -569,9 +569,9 @@ describe('/library', () => {
});
});
describe('POST /library/:id/validate', () => {
describe('POST /libraries/:id/validate', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/library/${uuidDto.notFound}/validate`).send({});
const { status, body } = await request(app).post(`/libraries/${uuidDto.notFound}/validate`).send({});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -617,9 +617,9 @@ describe('/library', () => {
});
});
describe('DELETE /library/:id', () => {
describe('DELETE /libraries/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete(`/library/${uuidDto.notFound}`);
const { status, body } = await request(app).delete(`/libraries/${uuidDto.notFound}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -629,7 +629,7 @@ describe('/library', () => {
const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
const { status, body } = await request(app)
.delete(`/library/${library.id}`)
.delete(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
@@ -655,7 +655,7 @@ describe('/library', () => {
await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 2 });
const { status, body } = await request(app)
.delete(`/library/${library.id}`)
.delete(`/libraries/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);

View File

@@ -5,7 +5,7 @@ import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
describe('/partner', () => {
describe('/partners', () => {
let admin: LoginResponseDto;
let user1: LoginResponseDto;
let user2: LoginResponseDto;
@@ -28,9 +28,9 @@ describe('/partner', () => {
]);
});
describe('GET /partner', () => {
describe('GET /partners', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/partner');
const { status, body } = await request(app).get('/partners');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -38,7 +38,7 @@ describe('/partner', () => {
it('should get all partners shared by user', async () => {
const { status, body } = await request(app)
.get('/partner')
.get('/partners')
.set('Authorization', `Bearer ${user1.accessToken}`)
.query({ direction: 'shared-by' });
@@ -48,7 +48,7 @@ describe('/partner', () => {
it('should get all partners that share with user', async () => {
const { status, body } = await request(app)
.get('/partner')
.get('/partners')
.set('Authorization', `Bearer ${user1.accessToken}`)
.query({ direction: 'shared-with' });
@@ -57,9 +57,9 @@ describe('/partner', () => {
});
});
describe('POST /partner/:id', () => {
describe('POST /partners/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/partner/${user3.userId}`);
const { status, body } = await request(app).post(`/partners/${user3.userId}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -67,7 +67,7 @@ describe('/partner', () => {
it('should share with new partner', async () => {
const { status, body } = await request(app)
.post(`/partner/${user3.userId}`)
.post(`/partners/${user3.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(201);
@@ -76,7 +76,7 @@ describe('/partner', () => {
it('should not share with new partner if already sharing with this partner', async () => {
const { status, body } = await request(app)
.post(`/partner/${user2.userId}`)
.post(`/partners/${user2.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);
@@ -84,9 +84,9 @@ describe('/partner', () => {
});
});
describe('PUT /partner/:id', () => {
describe('PUT /partners/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/partner/${user2.userId}`);
const { status, body } = await request(app).put(`/partners/${user2.userId}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -94,7 +94,7 @@ describe('/partner', () => {
it('should update partner', async () => {
const { status, body } = await request(app)
.put(`/partner/${user2.userId}`)
.put(`/partners/${user2.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ inTimeline: false });
@@ -103,9 +103,9 @@ describe('/partner', () => {
});
});
describe('DELETE /partner/:id', () => {
describe('DELETE /partners/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete(`/partner/${user3.userId}`);
const { status, body } = await request(app).delete(`/partners/${user3.userId}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -113,7 +113,7 @@ describe('/partner', () => {
it('should delete partner', async () => {
const { status } = await request(app)
.delete(`/partner/${user3.userId}`)
.delete(`/partners/${user3.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -121,7 +121,7 @@ describe('/partner', () => {
it('should throw a bad request if partner not found', async () => {
const { status, body } = await request(app)
.delete(`/partner/${user3.userId}`)
.delete(`/partners/${user3.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);

View File

@@ -12,7 +12,7 @@ const invalidBirthday = [
{ birthDate: new Date(9999, 0, 0).toISOString(), response: ['Birth date cannot be in the future'] },
];
describe('/person', () => {
describe('/people', () => {
let admin: LoginResponseDto;
let visiblePerson: PersonResponseDto;
let hiddenPerson: PersonResponseDto;
@@ -47,11 +47,11 @@ describe('/person', () => {
]);
});
describe('GET /person', () => {
describe('GET /people', () => {
beforeEach(async () => {});
it('should require authentication', async () => {
const { status, body } = await request(app).get('/person');
const { status, body } = await request(app).get('/people');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -59,7 +59,7 @@ describe('/person', () => {
it('should return all people (including hidden)', async () => {
const { status, body } = await request(app)
.get('/person')
.get('/people')
.set('Authorization', `Bearer ${admin.accessToken}`)
.query({ withHidden: true });
@@ -76,7 +76,7 @@ describe('/person', () => {
});
it('should return only visible people', async () => {
const { status, body } = await request(app).get('/person').set('Authorization', `Bearer ${admin.accessToken}`);
const { status, body } = await request(app).get('/people').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
@@ -90,9 +90,9 @@ describe('/person', () => {
});
});
describe('GET /person/:id', () => {
describe('GET /people/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/person/${uuidDto.notFound}`);
const { status, body } = await request(app).get(`/people/${uuidDto.notFound}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -100,7 +100,7 @@ describe('/person', () => {
it('should throw error if person with id does not exist', async () => {
const { status, body } = await request(app)
.get(`/person/${uuidDto.notFound}`)
.get(`/people/${uuidDto.notFound}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
@@ -109,7 +109,7 @@ describe('/person', () => {
it('should return person information', async () => {
const { status, body } = await request(app)
.get(`/person/${visiblePerson.id}`)
.get(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
@@ -117,9 +117,9 @@ describe('/person', () => {
});
});
describe('GET /person/:id/statistics', () => {
describe('GET /people/:id/statistics', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/person/${multipleAssetsPerson.id}/statistics`);
const { status, body } = await request(app).get(`/people/${multipleAssetsPerson.id}/statistics`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -127,7 +127,7 @@ describe('/person', () => {
it('should throw error if person with id does not exist', async () => {
const { status, body } = await request(app)
.get(`/person/${uuidDto.notFound}/statistics`)
.get(`/people/${uuidDto.notFound}/statistics`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
@@ -136,7 +136,7 @@ describe('/person', () => {
it('should return the correct number of assets', async () => {
const { status, body } = await request(app)
.get(`/person/${multipleAssetsPerson.id}/statistics`)
.get(`/people/${multipleAssetsPerson.id}/statistics`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
@@ -144,9 +144,9 @@ describe('/person', () => {
});
});
describe('POST /person', () => {
describe('POST /people', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/person`);
const { status, body } = await request(app).post(`/people`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
@@ -154,7 +154,7 @@ describe('/person', () => {
for (const { birthDate, response } of invalidBirthday) {
it(`should not accept an invalid birth date [${birthDate}]`, async () => {
const { status, body } = await request(app)
.post(`/person`)
.post(`/people`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate });
expect(status).toBe(400);
@@ -164,7 +164,7 @@ describe('/person', () => {
it('should create a person', async () => {
const { status, body } = await request(app)
.post(`/person`)
.post(`/people`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
name: 'New Person',
@@ -179,9 +179,9 @@ describe('/person', () => {
});
});
describe('PUT /person/:id', () => {
describe('PUT /people/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/person/${uuidDto.notFound}`);
const { status, body } = await request(app).put(`/people/${uuidDto.notFound}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
@@ -193,7 +193,7 @@ describe('/person', () => {
]) {
it(`should not allow null ${key}`, async () => {
const { status, body } = await request(app)
.put(`/person/${visiblePerson.id}`)
.put(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ [key]: null });
expect(status).toBe(400);
@@ -204,7 +204,7 @@ describe('/person', () => {
for (const { birthDate, response } of invalidBirthday) {
it(`should not accept an invalid birth date [${birthDate}]`, async () => {
const { status, body } = await request(app)
.put(`/person/${visiblePerson.id}`)
.put(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate });
expect(status).toBe(400);
@@ -214,7 +214,7 @@ describe('/person', () => {
it('should update a date of birth', async () => {
const { status, body } = await request(app)
.put(`/person/${visiblePerson.id}`)
.put(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate: '1990-01-01T05:00:00.000Z' });
expect(status).toBe(200);
@@ -223,7 +223,7 @@ describe('/person', () => {
it('should clear a date of birth', async () => {
const { status, body } = await request(app)
.put(`/person/${visiblePerson.id}`)
.put(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate: null });
expect(status).toBe(200);

View File

@@ -15,16 +15,16 @@ describe('/server-info', () => {
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
});
describe('GET /server-info', () => {
describe('GET /server-info/storage', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server-info');
const { status, body } = await request(app).get('/server-info/storage');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return the disk information', async () => {
const { status, body } = await request(app)
.get('/server-info')
.get('/server-info/storage')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({

View File

@@ -13,7 +13,7 @@ import { app, asBearerAuth, shareUrl, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
describe('/shared-link', () => {
describe('/shared-links', () => {
let admin: LoginResponseDto;
let asset1: AssetFileUploadResponseDto;
let asset2: AssetFileUploadResponseDto;
@@ -114,9 +114,9 @@ describe('/shared-link', () => {
});
});
describe('GET /shared-link', () => {
describe('GET /shared-links', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/shared-link');
const { status, body } = await request(app).get('/shared-links');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -124,7 +124,7 @@ describe('/shared-link', () => {
it('should get all shared links created by user', async () => {
const { status, body } = await request(app)
.get('/shared-link')
.get('/shared-links')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -142,7 +142,7 @@ describe('/shared-link', () => {
it('should not get shared links created by other users', async () => {
const { status, body } = await request(app)
.get('/shared-link')
.get('/shared-links')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
@@ -150,15 +150,15 @@ describe('/shared-link', () => {
});
});
describe('GET /shared-link/me', () => {
describe('GET /shared-links/me', () => {
it('should not require admin authentication', async () => {
const { status } = await request(app).get('/shared-link/me').set('Authorization', `Bearer ${admin.accessToken}`);
const { status } = await request(app).get('/shared-links/me').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(403);
});
it('should get data for correct shared link', async () => {
const { status, body } = await request(app).get('/shared-link/me').query({ key: linkWithAlbum.key });
const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithAlbum.key });
expect(status).toBe(200);
expect(body).toEqual(
@@ -172,7 +172,7 @@ describe('/shared-link', () => {
it('should return unauthorized for incorrect shared link', async () => {
const { status, body } = await request(app)
.get('/shared-link/me')
.get('/shared-links/me')
.query({ key: linkWithAlbum.key + 'foo' });
expect(status).toBe(401);
@@ -180,14 +180,14 @@ describe('/shared-link', () => {
});
it('should return unauthorized if target has been soft deleted', async () => {
const { status, body } = await request(app).get('/shared-link/me').query({ key: linkWithDeletedAlbum.key });
const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithDeletedAlbum.key });
expect(status).toBe(401);
expect(body).toEqual(errorDto.invalidShareKey);
});
it('should return unauthorized for password protected link', async () => {
const { status, body } = await request(app).get('/shared-link/me').query({ key: linkWithPassword.key });
const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithPassword.key });
expect(status).toBe(401);
expect(body).toEqual(errorDto.invalidSharePassword);
@@ -195,7 +195,7 @@ describe('/shared-link', () => {
it('should get data for correct password protected link', async () => {
const { status, body } = await request(app)
.get('/shared-link/me')
.get('/shared-links/me')
.query({ key: linkWithPassword.key, password: 'foo' });
expect(status).toBe(200);
@@ -209,7 +209,7 @@ describe('/shared-link', () => {
});
it('should return metadata for album shared link', async () => {
const { status, body } = await request(app).get('/shared-link/me').query({ key: linkWithMetadata.key });
const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithMetadata.key });
expect(status).toBe(200);
expect(body.assets).toHaveLength(1);
@@ -225,7 +225,7 @@ describe('/shared-link', () => {
});
it('should not return metadata for album shared link without metadata', async () => {
const { status, body } = await request(app).get('/shared-link/me').query({ key: linkWithoutMetadata.key });
const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithoutMetadata.key });
expect(status).toBe(200);
expect(body.assets).toHaveLength(1);
@@ -239,9 +239,9 @@ describe('/shared-link', () => {
});
});
describe('GET /shared-link/:id', () => {
describe('GET /shared-links/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/shared-link/${linkWithAlbum.id}`);
const { status, body } = await request(app).get(`/shared-links/${linkWithAlbum.id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -249,7 +249,7 @@ describe('/shared-link', () => {
it('should get shared link by id', async () => {
const { status, body } = await request(app)
.get(`/shared-link/${linkWithAlbum.id}`)
.get(`/shared-links/${linkWithAlbum.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
@@ -264,7 +264,7 @@ describe('/shared-link', () => {
it('should not get shared link by id if user has not created the link or it does not exist', async () => {
const { status, body } = await request(app)
.get(`/shared-link/${linkWithAlbum.id}`)
.get(`/shared-links/${linkWithAlbum.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
@@ -272,10 +272,10 @@ describe('/shared-link', () => {
});
});
describe('POST /shared-link', () => {
describe('POST /shared-links', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.post('/shared-link')
.post('/shared-links')
.send({ type: SharedLinkType.Album, albumId: uuidDto.notFound });
expect(status).toBe(401);
@@ -284,7 +284,7 @@ describe('/shared-link', () => {
it('should require a type and the correspondent asset/album id', async () => {
const { status, body } = await request(app)
.post('/shared-link')
.post('/shared-links')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);
@@ -293,7 +293,7 @@ describe('/shared-link', () => {
it('should require an asset/album id', async () => {
const { status, body } = await request(app)
.post('/shared-link')
.post('/shared-links')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ type: SharedLinkType.Album });
@@ -303,7 +303,7 @@ describe('/shared-link', () => {
it('should require a valid asset id', async () => {
const { status, body } = await request(app)
.post('/shared-link')
.post('/shared-links')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ type: SharedLinkType.Individual, assetId: uuidDto.notFound });
@@ -313,7 +313,7 @@ describe('/shared-link', () => {
it('should create a shared link', async () => {
const { status, body } = await request(app)
.post('/shared-link')
.post('/shared-links')
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ type: SharedLinkType.Album, albumId: album.id });
@@ -327,10 +327,10 @@ describe('/shared-link', () => {
});
});
describe('PATCH /shared-link/:id', () => {
describe('PATCH /shared-links/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.patch(`/shared-link/${linkWithAlbum.id}`)
.patch(`/shared-links/${linkWithAlbum.id}`)
.send({ description: 'foo' });
expect(status).toBe(401);
@@ -339,7 +339,7 @@ describe('/shared-link', () => {
it('should fail if invalid link', async () => {
const { status, body } = await request(app)
.patch(`/shared-link/${uuidDto.notFound}`)
.patch(`/shared-links/${uuidDto.notFound}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ description: 'foo' });
@@ -349,7 +349,7 @@ describe('/shared-link', () => {
it('should update shared link', async () => {
const { status, body } = await request(app)
.patch(`/shared-link/${linkWithAlbum.id}`)
.patch(`/shared-links/${linkWithAlbum.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ description: 'foo' });
@@ -364,10 +364,10 @@ describe('/shared-link', () => {
});
});
describe('PUT /shared-link/:id/assets', () => {
describe('PUT /shared-links/:id/assets', () => {
it('should not add assets to shared link (album)', async () => {
const { status, body } = await request(app)
.put(`/shared-link/${linkWithAlbum.id}/assets`)
.put(`/shared-links/${linkWithAlbum.id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ assetIds: [asset2.id] });
@@ -377,7 +377,7 @@ describe('/shared-link', () => {
it('should add an assets to a shared link (individual)', async () => {
const { status, body } = await request(app)
.put(`/shared-link/${linkWithAssets.id}/assets`)
.put(`/shared-links/${linkWithAssets.id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ assetIds: [asset2.id] });
@@ -386,10 +386,10 @@ describe('/shared-link', () => {
});
});
describe('DELETE /shared-link/:id/assets', () => {
describe('DELETE /shared-links/:id/assets', () => {
it('should not remove assets from a shared link (album)', async () => {
const { status, body } = await request(app)
.delete(`/shared-link/${linkWithAlbum.id}/assets`)
.delete(`/shared-links/${linkWithAlbum.id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ assetIds: [asset2.id] });
@@ -399,7 +399,7 @@ describe('/shared-link', () => {
it('should remove assets from a shared link (individual)', async () => {
const { status, body } = await request(app)
.delete(`/shared-link/${linkWithAssets.id}/assets`)
.delete(`/shared-links/${linkWithAssets.id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ assetIds: [asset2.id] });
@@ -408,9 +408,9 @@ describe('/shared-link', () => {
});
});
describe('DELETE /shared-link/:id', () => {
describe('DELETE /shared-links/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete(`/shared-link/${linkWithAlbum.id}`);
const { status, body } = await request(app).delete(`/shared-links/${linkWithAlbum.id}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
@@ -418,7 +418,7 @@ describe('/shared-link', () => {
it('should fail if invalid link', async () => {
const { status, body } = await request(app)
.delete(`/shared-link/${uuidDto.notFound}`)
.delete(`/shared-links/${uuidDto.notFound}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);
@@ -427,7 +427,7 @@ describe('/shared-link', () => {
it('should delete a shared link', async () => {
const { status } = await request(app)
.delete(`/shared-link/${linkWithAlbum.id}`)
.delete(`/shared-links/${linkWithAlbum.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);

View File

@@ -1,4 +1,4 @@
import { LoginResponseDto, getAllAssets } from '@immich/sdk';
import { LoginResponseDto, getAssetInfo, getAssetStatistics } from '@immich/sdk';
import { Socket } from 'socket.io-client';
import { errorDto } from 'src/responses';
import { app, asBearerAuth, utils } from 'src/utils';
@@ -31,16 +31,16 @@ describe('/trash', () => {
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true }));
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: assetId });
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(after.length).toBe(0);
const after = await getAssetStatistics({ isTrashed: true }, { headers: asBearerAuth(admin.accessToken) });
expect(after.total).toBe(0);
});
});
@@ -56,14 +56,14 @@ describe('/trash', () => {
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true }));
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(after).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: false })]);
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: false }));
});
});

View File

@@ -6,7 +6,7 @@ import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
describe('/user', () => {
describe('/users', () => {
let websocket: Socket;
let admin: LoginResponseDto;
@@ -34,15 +34,15 @@ describe('/user', () => {
utils.disconnectWebsocket(websocket);
});
describe('GET /user', () => {
describe('GET /users', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/user');
const { status, body } = await request(app).get('/users');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should get users', async () => {
const { status, body } = await request(app).get('/user').set('Authorization', `Bearer ${admin.accessToken}`);
const { status, body } = await request(app).get('/users').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toEqual(200);
expect(body).toHaveLength(5);
expect(body).toEqual(
@@ -58,7 +58,7 @@ describe('/user', () => {
it('should hide deleted users', async () => {
const { status, body } = await request(app)
.get(`/user`)
.get(`/users`)
.query({ isAll: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
@@ -75,7 +75,7 @@ describe('/user', () => {
it('should include deleted users', async () => {
const { status, body } = await request(app)
.get(`/user`)
.get(`/users`)
.query({ isAll: false })
.set('Authorization', `Bearer ${admin.accessToken}`);
@@ -93,15 +93,15 @@ describe('/user', () => {
});
});
describe('GET /user/info/:id', () => {
describe('GET /users/:id', () => {
it('should require authentication', async () => {
const { status } = await request(app).get(`/user/info/${admin.userId}`);
const { status } = await request(app).get(`/users/${admin.userId}`);
expect(status).toEqual(401);
});
it('should get the user info', async () => {
const { status, body } = await request(app)
.get(`/user/info/${admin.userId}`)
.get(`/users/${admin.userId}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({
@@ -111,15 +111,15 @@ describe('/user', () => {
});
});
describe('GET /user/me', () => {
describe('GET /users/me', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get(`/user/me`);
const { status, body } = await request(app).get(`/users/me`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should get my info', async () => {
const { status, body } = await request(app).get(`/user/me`).set('Authorization', `Bearer ${admin.accessToken}`);
const { status, body } = await request(app).get(`/users/me`).set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({
id: admin.userId,
@@ -128,9 +128,9 @@ describe('/user', () => {
});
});
describe('POST /user', () => {
describe('POST /users', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/user`).send(createUserDto.user1);
const { status, body } = await request(app).post(`/users`).send(createUserDto.user1);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
@@ -138,7 +138,7 @@ describe('/user', () => {
for (const key of Object.keys(createUserDto.user1)) {
it(`should not allow null ${key}`, async () => {
const { status, body } = await request(app)
.post(`/user`)
.post(`/users`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...createUserDto.user1, [key]: null });
expect(status).toBe(400);
@@ -148,7 +148,7 @@ describe('/user', () => {
it('should ignore `isAdmin`', async () => {
const { status, body } = await request(app)
.post(`/user`)
.post(`/users`)
.send({
isAdmin: true,
email: 'user5@immich.cloud',
@@ -166,7 +166,7 @@ describe('/user', () => {
it('should create a user without memories enabled', async () => {
const { status, body } = await request(app)
.post(`/user`)
.post(`/users`)
.send({
email: 'no-memories@immich.cloud',
password: 'Password123',
@@ -182,16 +182,16 @@ describe('/user', () => {
});
});
describe('DELETE /user/:id', () => {
describe('DELETE /users/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete(`/user/${userToDelete.userId}`);
const { status, body } = await request(app).delete(`/users/${userToDelete.userId}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should delete user', async () => {
const { status, body } = await request(app)
.delete(`/user/${userToDelete.userId}`)
.delete(`/users/${userToDelete.userId}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
@@ -204,7 +204,7 @@ describe('/user', () => {
it('should hard delete user', async () => {
const { status, body } = await request(app)
.delete(`/user/${userToHardDelete.userId}`)
.delete(`/users/${userToHardDelete.userId}`)
.send({ force: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
@@ -219,9 +219,9 @@ describe('/user', () => {
});
});
describe('PUT /user', () => {
describe('PUT /users', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/user`);
const { status, body } = await request(app).put(`/users`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
@@ -229,7 +229,7 @@ describe('/user', () => {
for (const key of Object.keys(userDto.admin)) {
it(`should not allow null ${key}`, async () => {
const { status, body } = await request(app)
.put(`/user`)
.put(`/users`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...userDto.admin, [key]: null });
expect(status).toBe(400);
@@ -239,7 +239,7 @@ describe('/user', () => {
it('should not allow a non-admin to become an admin', async () => {
const { status, body } = await request(app)
.put(`/user`)
.put(`/users`)
.send({ isAdmin: true, id: nonAdmin.userId })
.set('Authorization', `Bearer ${admin.accessToken}`);
@@ -249,7 +249,7 @@ describe('/user', () => {
it('ignores updates to profileImagePath', async () => {
const { status, body } = await request(app)
.put(`/user`)
.put(`/users`)
.send({ id: admin.userId, profileImagePath: 'invalid.jpg' })
.set('Authorization', `Bearer ${admin.accessToken}`);
@@ -257,28 +257,11 @@ describe('/user', () => {
expect(body).toMatchObject({ id: admin.userId, profileImagePath: '' });
});
it('should ignore updates to createdAt, updatedAt and deletedAt', async () => {
const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
const { status, body } = await request(app)
.put(`/user`)
.send({
id: admin.userId,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z',
deletedAt: '2023-01-01T00:00:00.000Z',
})
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toStrictEqual(before);
});
it('should update first and last name', async () => {
const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
const { status, body } = await request(app)
.put(`/user`)
.put(`/users`)
.send({
id: admin.userId,
name: 'Name',
@@ -297,7 +280,7 @@ describe('/user', () => {
it('should update memories enabled', async () => {
const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
const { status, body } = await request(app)
.put(`/user`)
.put(`/users`)
.send({
id: admin.userId,
memoriesEnabled: false,

View File

@@ -1,4 +1,4 @@
import { LoginResponseDto, getAllAlbums, getAllAssets } from '@immich/sdk';
import { LoginResponseDto, getAllAlbums, getAssetStatistics } from '@immich/sdk';
import { readFileSync } from 'node:fs';
import { mkdir, readdir, rm, symlink } from 'node:fs/promises';
import { asKeyAuth, immichCli, testAssetDir, utils } from 'src/utils';
@@ -28,8 +28,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(1);
});
it('should skip a duplicate file', async () => {
@@ -40,8 +40,8 @@ describe(`immich upload`, () => {
);
expect(first.exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(1);
const second = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]);
expect(second.stderr).toBe('');
@@ -60,8 +60,8 @@ describe(`immich upload`, () => {
expect(stdout.split('\n')).toEqual(expect.arrayContaining([expect.stringContaining('No files found, exiting')]));
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
});
it('should have accurate dry run', async () => {
@@ -76,8 +76,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
});
it('dry run should handle duplicates', async () => {
@@ -88,8 +88,8 @@ describe(`immich upload`, () => {
);
expect(first.exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(1);
const second = await immichCli(['upload', `${testAssetDir}/albums/nature/`, '--dry-run']);
expect(second.stderr).toBe('');
@@ -112,8 +112,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(9);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(9);
});
});
@@ -135,8 +135,8 @@ describe(`immich upload`, () => {
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(9);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(9);
const albums = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums.length).toBe(1);
@@ -151,8 +151,8 @@ describe(`immich upload`, () => {
expect(response1.stderr).toBe('');
expect(response1.exitCode).toBe(0);
const assets1 = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets1.length).toBe(9);
const assets1 = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets1.total).toBe(9);
const albums1 = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums1.length).toBe(0);
@@ -167,8 +167,8 @@ describe(`immich upload`, () => {
expect(response2.stderr).toBe('');
expect(response2.exitCode).toBe(0);
const assets2 = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets2.length).toBe(9);
const assets2 = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets2.total).toBe(9);
const albums2 = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums2.length).toBe(1);
@@ -193,8 +193,8 @@ describe(`immich upload`, () => {
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
const albums = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums.length).toBe(0);
@@ -219,8 +219,8 @@ describe(`immich upload`, () => {
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(9);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(9);
const albums = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums.length).toBe(1);
@@ -245,8 +245,8 @@ describe(`immich upload`, () => {
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
const albums = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums.length).toBe(0);
@@ -276,8 +276,8 @@ describe(`immich upload`, () => {
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(9);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(9);
});
it('should have accurate dry run', async () => {
@@ -302,8 +302,8 @@ describe(`immich upload`, () => {
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
});
});
@@ -328,8 +328,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(1);
});
it('should throw an error if attempting dry run', async () => {
@@ -344,8 +344,8 @@ describe(`immich upload`, () => {
expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '-h, --skip-hash'`);
expect(exitCode).not.toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
});
});
@@ -367,8 +367,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(9);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(9);
});
it('should reject string argument', async () => {
@@ -408,8 +408,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(8);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(8);
});
it('should ignore assets matching glob pattern', async () => {
@@ -429,8 +429,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(1);
});
it('should have accurate dry run', async () => {
@@ -451,8 +451,8 @@ describe(`immich upload`, () => {
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(0);
});
});
});

View File

@@ -17,7 +17,6 @@ import {
createSharedLink,
createUser,
deleteAssets,
getAllAssets,
getAllJobsStatus,
getAssetInfo,
getConfigDefaults,
@@ -340,8 +339,6 @@ export const utils = {
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
getAllAssets: (accessToken: string) => getAllAssets({}, { headers: asBearerAuth(accessToken) }),
metadataSearch: async (accessToken: string, dto: MetadataSearchDto) => {
return searchMetadata({ metadataSearchDto: dto }, { headers: asBearerAuth(accessToken) });
},

View File

@@ -0,0 +1,46 @@
import { AssetFileUploadResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { utils } from 'src/utils';
test.describe('Detail Panel', () => {
let admin: LoginResponseDto;
let asset: AssetFileUploadResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken);
});
test('can be opened for shared links', async ({ page }) => {
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset.id],
});
await page.goto(`/share/${sharedLink.key}/photos/${asset.id}`);
await page.waitForSelector('#immich-asset-viewer');
await expect(page.getByRole('button', { name: 'Info' })).toBeVisible();
await page.keyboard.press('i');
await expect(page.locator('#detail-panel')).toBeVisible();
await page.keyboard.press('i');
await expect(page.locator('#detail-panel')).toHaveCount(0);
});
test('cannot be opened for shared links with hidden metadata', async ({ page }) => {
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset.id],
showMetadata: false,
});
await page.goto(`/share/${sharedLink.key}/photos/${asset.id}`);
await page.waitForSelector('#immich-asset-viewer');
await expect(page.getByRole('button', { name: 'Info' })).toHaveCount(0);
await page.keyboard.press('i');
await expect(page.locator('#detail-panel')).toHaveCount(0);
await page.keyboard.press('i');
await expect(page.locator('#detail-panel')).toHaveCount(0);
});
});

View File

@@ -0,0 +1,52 @@
import { AssetFileUploadResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { utils } from 'src/utils';
test.describe('Asset Viewer Navbar', () => {
let admin: LoginResponseDto;
let asset: AssetFileUploadResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken);
});
test.describe('shared link without metadata', () => {
test('visible guest actions', async ({ page }) => {
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset.id],
showMetadata: false,
});
await page.goto(`/share/${sharedLink.key}/photos/${asset.id}`);
await page.waitForSelector('#immich-asset-viewer');
const expected = ['Zoom Image', 'Copy Image', 'Download'];
const buttons = await page.getByTestId('asset-viewer-navbar-actions').getByRole('button').all();
for (const [i, button] of buttons.entries()) {
await expect(button).toHaveAccessibleName(expected[i]);
}
});
test('visible owner actions', async ({ context, page }) => {
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Individual,
assetIds: [asset.id],
showMetadata: false,
});
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/share/${sharedLink.key}/photos/${asset.id}`);
await page.waitForSelector('#immich-asset-viewer');
const expected = ['Share', 'Zoom Image', 'Copy Image', 'Download'];
const buttons = await page.getByTestId('asset-viewer-navbar-actions').getByRole('button').all();
for (const [i, button] of buttons.entries()) {
await expect(button).toHaveAccessibleName(expected[i]);
}
});
});
});

View File

@@ -95,3 +95,5 @@ COPY start.sh log_conf.json ./
COPY app .
ENTRYPOINT ["tini", "--"]
CMD ["./start.sh"]
HEALTHCHECK CMD python3 healthcheck.py

View File

@@ -0,0 +1,14 @@
import os
import sys
import requests
port = os.getenv("IMMICH_PORT", 3003)
try:
response = requests.get(f"http://localhost:{port}/ping", timeout=2)
if response.status_code == 200:
sys.exit(0)
sys.exit(1)
except requests.RequestException:
sys.exit(1)

View File

@@ -62,6 +62,8 @@ fi
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
npm --prefix server version "$SERVER_PUMP"
npm --prefix server ci
npm --prefix server run build
make open-api
npm --prefix open-api/typescript-sdk version "$SERVER_PUMP"
npm --prefix web version "$SERVER_PUMP"

View File

@@ -10,7 +10,7 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.931.0)
aws-partitions (1.932.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)

View File

@@ -10,7 +10,7 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.931.0)
aws-partitions (1.932.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)

View File

@@ -145,9 +145,10 @@ class Album {
.remoteIdEqualTo(dto.albumThumbnailAssetId)
.findFirst();
}
if (dto.sharedUsers.isNotEmpty) {
final users = await db.users
.getAllById(dto.sharedUsers.map((e) => e.id).toList(growable: false));
if (dto.albumUsers.isNotEmpty) {
final users = await db.users.getAllById(
dto.albumUsers.map((e) => e.user.id).toList(growable: false),
);
a.sharedUsers.addAll(users.cast());
}
if (dto.assets.isNotEmpty) {

View File

@@ -32,7 +32,7 @@ class ServerDiskInfo {
return 'ServerDiskInfo(diskAvailable: $diskAvailable, diskSize: $diskSize, diskUse: $diskUse, diskUsagePercentage: $diskUsagePercentage)';
}
ServerDiskInfo.fromDto(ServerInfoResponseDto dto)
ServerDiskInfo.fromDto(ServerStorageResponseDto dto)
: diskAvailable = dto.diskAvailable,
diskSize = dto.diskSize,
diskUse = dto.diskUse,

View File

@@ -374,7 +374,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
if (state.backupProgress != BackUpProgressEnum.inBackground) {
await _getBackupAlbumsInfo();
await updateServerInfo();
await updateDiskInfo();
await _updateBackupAssetCount();
} else {
log.warning("cannot get backup info - background backup is in progress!");
@@ -542,7 +542,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
_updatePersistentAlbumsSelection();
}
updateServerInfo();
updateDiskInfo();
}
void _onUploadProgress(int sent, int total) {
@@ -579,13 +579,13 @@ class BackupNotifier extends StateNotifier<BackUpState> {
);
}
Future<void> updateServerInfo() async {
final serverInfo = await _serverInfoService.getServerInfo();
Future<void> updateDiskInfo() async {
final diskInfo = await _serverInfoService.getDiskInfo();
// Update server info
if (serverInfo != null) {
if (diskInfo != null) {
state = state.copyWith(
serverInfo: serverInfo,
serverInfo: diskInfo,
);
}
}

View File

@@ -121,7 +121,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
bool isDuplicated,
) {
state = state.copyWith(successfulUploads: state.successfulUploads + 1);
_backupProvider.updateServerInfo();
_backupProvider.updateDiskInfo();
}
void _onAssetUploadError(ErrorUploadAsset errorAssetInfo) {

View File

@@ -180,7 +180,14 @@ class AlbumService {
CreateAlbumDto(
albumName: albumName,
assetIds: assets.map((asset) => asset.remoteId!).toList(),
sharedWithUserIds: sharedUsers.map((e) => e.id).toList(),
albumUsers: sharedUsers
.map(
(e) => AlbumUserCreateDto(
userId: e.id,
role: AlbumUserRole.editor,
),
)
.toList(),
),
);
if (remote != null) {

View File

@@ -20,6 +20,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/backup_progress.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
import 'package:isar/isar.dart';
import 'package:path_provider_ios/path_provider_ios.dart';
import 'package:photo_manager/photo_manager.dart';
@@ -590,6 +591,7 @@ enum IosBackgroundTask { fetch, processing }
/// entry point called by Kotlin/Java code; needs to be a top-level function
@pragma('vm:entry-point')
void _nativeEntry() {
HttpOverrides.global = HttpSSLCertOverride();
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
BackgroundService backgroundService = BackgroundService();

View File

@@ -8,6 +8,8 @@ import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import '../utils/string_helper.dart';
final memoryServiceProvider = StateProvider<MemoryService>((ref) {
return MemoryService(
ref.watch(apiServiceProvider),
@@ -36,13 +38,13 @@ class MemoryService {
}
List<Memory> memories = [];
for (final MemoryLaneResponseDto(:title, :assets) in data) {
for (final MemoryLaneResponseDto(:yearsAgo, :assets) in data) {
final dbAssets =
await _db.assets.getAllByRemoteId(assets.map((e) => e.id));
if (dbAssets.isNotEmpty) {
memories.add(
Memory(
title: title,
title: '$yearsAgo year${s(yearsAgo)} ago',
assets: dbAssets,
),
);

View File

@@ -18,14 +18,14 @@ class ServerInfoService {
ServerInfoService(this._apiService);
Future<ServerDiskInfo?> getServerInfo() async {
Future<ServerDiskInfo?> getDiskInfo() async {
try {
final dto = await _apiService.serverInfoApi.getServerInfo();
final dto = await _apiService.serverInfoApi.getStorage();
if (dto != null) {
return ServerDiskInfo.fromDto(dto);
}
} catch (e) {
debugPrint("Error [getServerInfo] ${e.toString()}");
debugPrint("Error [getDiskInfo] ${e.toString()}");
}
return null;
}

View File

@@ -362,15 +362,15 @@ class SyncService {
// update shared users
final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
sharedUsers.sort((a, b) => a.id.compareTo(b.id));
dto.sharedUsers.sort((a, b) => a.id.compareTo(b.id));
dto.albumUsers.sort((a, b) => a.user.id.compareTo(b.user.id));
final List<String> userIdsToAdd = [];
final List<User> usersToUnlink = [];
diffSortedListsSync(
dto.sharedUsers,
dto.albumUsers,
sharedUsers,
compare: (UserResponseDto a, User b) => a.id.compareTo(b.id),
compare: (AlbumUserResponseDto a, User b) => a.user.id.compareTo(b.id),
both: (a, b) => false,
onlyFirst: (UserResponseDto a) => userIdsToAdd.add(a.id),
onlyFirst: (AlbumUserResponseDto a) => userIdsToAdd.add(a.user.id),
onlySecond: (User a) => usersToUnlink.add(a),
);
@@ -905,7 +905,7 @@ bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) {
dto.albumName != a.name ||
dto.albumThumbnailAssetId != a.thumbnail.value?.remoteId ||
dto.shared != a.shared ||
dto.sharedUsers.length != a.sharedUsers.length ||
dto.albumUsers.length != a.sharedUsers.length ||
!dto.updatedAt.isAtSameMomentAs(a.modifiedAt) ||
!isAtSameMomentAs(dto.startDate, a.startDate) ||
!isAtSameMomentAs(dto.endDate, a.endDate) ||

View File

@@ -77,5 +77,5 @@ String getThumbnailUrlForRemoteId(
}
String getFaceThumbnailUrl(final String personId) {
return '${Store.get(StoreKey.serverEndpoint)}/person/$personId/thumbnail';
return '${Store.get(StoreKey.serverEndpoint)}/people/$personId/thumbnail';
}

View File

@@ -3,3 +3,5 @@ extension StringExtension on String {
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}
}
String s(num count) => (count == 1 ? '' : 's');

View File

@@ -31,7 +31,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
useEffect(
() {
ref.read(backupProvider.notifier).updateServerInfo();
ref.read(backupProvider.notifier).updateDiskInfo();
ref.read(currentUserProvider.notifier).refresh();
return null;
},

View File

@@ -215,7 +215,7 @@ class _ManualPicker extends HookWidget {
decorationText: "location_picker_longitude",
hintText: "location_picker_longitude_hint",
errorText: "location_picker_longitude_error",
focusNode: latitiudeFocusNode,
focusNode: longitudeFocusNode,
validator: _validateLong,
onUpdated: onLongitudeEditingCompleted,
),

View File

@@ -6,7 +6,7 @@ import 'package:immich_mobile/entities/user.entity.dart';
Widget userAvatar(BuildContext context, User u, {double? radius}) {
final url =
"${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${u.id}";
"${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
return CircleAvatar(
radius: radius,

View File

@@ -24,7 +24,7 @@ class UserCircleAvatar extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
bool isDarkTheme = Theme.of(context).brightness == Brightness.dark;
final profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}';
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}';
final textIcon = Text(
user.name[0].toUpperCase(),

156
mobile/openapi/README.md generated
View File

@@ -73,30 +73,29 @@ All URIs are relative to */api*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*APIKeyApi* | [**createApiKey**](doc//APIKeyApi.md#createapikey) | **POST** /api-key |
*APIKeyApi* | [**deleteApiKey**](doc//APIKeyApi.md#deleteapikey) | **DELETE** /api-key/{id} |
*APIKeyApi* | [**getApiKey**](doc//APIKeyApi.md#getapikey) | **GET** /api-key/{id} |
*APIKeyApi* | [**getApiKeys**](doc//APIKeyApi.md#getapikeys) | **GET** /api-key |
*APIKeyApi* | [**updateApiKey**](doc//APIKeyApi.md#updateapikey) | **PUT** /api-key/{id} |
*ActivityApi* | [**createActivity**](doc//ActivityApi.md#createactivity) | **POST** /activity |
*ActivityApi* | [**deleteActivity**](doc//ActivityApi.md#deleteactivity) | **DELETE** /activity/{id} |
*ActivityApi* | [**getActivities**](doc//ActivityApi.md#getactivities) | **GET** /activity |
*ActivityApi* | [**getActivityStatistics**](doc//ActivityApi.md#getactivitystatistics) | **GET** /activity/statistics |
*AlbumApi* | [**addAssetsToAlbum**](doc//AlbumApi.md#addassetstoalbum) | **PUT** /album/{id}/assets |
*AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /album/{id}/users |
*AlbumApi* | [**createAlbum**](doc//AlbumApi.md#createalbum) | **POST** /album |
*AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /album/{id} |
*AlbumApi* | [**getAlbumCount**](doc//AlbumApi.md#getalbumcount) | **GET** /album/count |
*AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /album/{id} |
*AlbumApi* | [**getAllAlbums**](doc//AlbumApi.md#getallalbums) | **GET** /album |
*AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{id}/assets |
*AlbumApi* | [**removeUserFromAlbum**](doc//AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{id}/user/{userId} |
*AlbumApi* | [**updateAlbumInfo**](doc//AlbumApi.md#updatealbuminfo) | **PATCH** /album/{id} |
*AlbumApi* | [**updateAlbumUser**](doc//AlbumApi.md#updatealbumuser) | **PUT** /album/{id}/user/{userId} |
*APIKeyApi* | [**createApiKey**](doc//APIKeyApi.md#createapikey) | **POST** /api-keys |
*APIKeyApi* | [**deleteApiKey**](doc//APIKeyApi.md#deleteapikey) | **DELETE** /api-keys/{id} |
*APIKeyApi* | [**getApiKey**](doc//APIKeyApi.md#getapikey) | **GET** /api-keys/{id} |
*APIKeyApi* | [**getApiKeys**](doc//APIKeyApi.md#getapikeys) | **GET** /api-keys |
*APIKeyApi* | [**updateApiKey**](doc//APIKeyApi.md#updateapikey) | **PUT** /api-keys/{id} |
*ActivityApi* | [**createActivity**](doc//ActivityApi.md#createactivity) | **POST** /activities |
*ActivityApi* | [**deleteActivity**](doc//ActivityApi.md#deleteactivity) | **DELETE** /activities/{id} |
*ActivityApi* | [**getActivities**](doc//ActivityApi.md#getactivities) | **GET** /activities |
*ActivityApi* | [**getActivityStatistics**](doc//ActivityApi.md#getactivitystatistics) | **GET** /activities/statistics |
*AlbumApi* | [**addAssetsToAlbum**](doc//AlbumApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets |
*AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /albums/{id}/users |
*AlbumApi* | [**createAlbum**](doc//AlbumApi.md#createalbum) | **POST** /albums |
*AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /albums/{id} |
*AlbumApi* | [**getAlbumCount**](doc//AlbumApi.md#getalbumcount) | **GET** /albums/count |
*AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /albums/{id} |
*AlbumApi* | [**getAllAlbums**](doc//AlbumApi.md#getallalbums) | **GET** /albums |
*AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets |
*AlbumApi* | [**removeUserFromAlbum**](doc//AlbumApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} |
*AlbumApi* | [**updateAlbumInfo**](doc//AlbumApi.md#updatealbuminfo) | **PATCH** /albums/{id} |
*AlbumApi* | [**updateAlbumUser**](doc//AlbumApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} |
*AssetApi* | [**checkBulkUpload**](doc//AssetApi.md#checkbulkupload) | **POST** /asset/bulk-upload-check |
*AssetApi* | [**checkExistingAssets**](doc//AssetApi.md#checkexistingassets) | **POST** /asset/exist |
*AssetApi* | [**deleteAssets**](doc//AssetApi.md#deleteassets) | **DELETE** /asset |
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
*AssetApi* | [**getAllUserAssetsByDeviceId**](doc//AssetApi.md#getalluserassetsbydeviceid) | **GET** /asset/device/{deviceId} |
*AssetApi* | [**getAssetInfo**](doc//AssetApi.md#getassetinfo) | **GET** /asset/{id} |
*AssetApi* | [**getAssetStatistics**](doc//AssetApi.md#getassetstatistics) | **GET** /asset/statistics |
@@ -104,6 +103,7 @@ Class | Method | HTTP request | Description
*AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
*AssetApi* | [**getMemoryLane**](doc//AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
*AssetApi* | [**getRandom**](doc//AssetApi.md#getrandom) | **GET** /asset/random |
*AssetApi* | [**replaceAsset**](doc//AssetApi.md#replaceasset) | **PUT** /asset/{id}/file |
*AssetApi* | [**runAssetJobs**](doc//AssetApi.md#runassetjobs) | **POST** /asset/jobs |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} |
@@ -120,22 +120,22 @@ Class | Method | HTTP request | Description
*DownloadApi* | [**downloadFile**](doc//DownloadApi.md#downloadfile) | **POST** /download/asset/{id} |
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
*DuplicateApi* | [**getAssetDuplicates**](doc//DuplicateApi.md#getassetduplicates) | **GET** /duplicates |
*FaceApi* | [**getFaces**](doc//FaceApi.md#getfaces) | **GET** /face |
*FaceApi* | [**reassignFacesById**](doc//FaceApi.md#reassignfacesbyid) | **PUT** /face/{id} |
*FileReportApi* | [**fixAuditFiles**](doc//FileReportApi.md#fixauditfiles) | **POST** /report/fix |
*FileReportApi* | [**getAuditFiles**](doc//FileReportApi.md#getauditfiles) | **GET** /report |
*FileReportApi* | [**getFileChecksums**](doc//FileReportApi.md#getfilechecksums) | **POST** /report/checksum |
*FaceApi* | [**getFaces**](doc//FaceApi.md#getfaces) | **GET** /faces |
*FaceApi* | [**reassignFacesById**](doc//FaceApi.md#reassignfacesbyid) | **PUT** /faces/{id} |
*FileReportApi* | [**fixAuditFiles**](doc//FileReportApi.md#fixauditfiles) | **POST** /reports/fix |
*FileReportApi* | [**getAuditFiles**](doc//FileReportApi.md#getauditfiles) | **GET** /reports |
*FileReportApi* | [**getFileChecksums**](doc//FileReportApi.md#getfilechecksums) | **POST** /reports/checksum |
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
*LibraryApi* | [**createLibrary**](doc//LibraryApi.md#createlibrary) | **POST** /library |
*LibraryApi* | [**deleteLibrary**](doc//LibraryApi.md#deletelibrary) | **DELETE** /library/{id} |
*LibraryApi* | [**getAllLibraries**](doc//LibraryApi.md#getalllibraries) | **GET** /library |
*LibraryApi* | [**getLibrary**](doc//LibraryApi.md#getlibrary) | **GET** /library/{id} |
*LibraryApi* | [**getLibraryStatistics**](doc//LibraryApi.md#getlibrarystatistics) | **GET** /library/{id}/statistics |
*LibraryApi* | [**removeOfflineFiles**](doc//LibraryApi.md#removeofflinefiles) | **POST** /library/{id}/removeOffline |
*LibraryApi* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan |
*LibraryApi* | [**updateLibrary**](doc//LibraryApi.md#updatelibrary) | **PUT** /library/{id} |
*LibraryApi* | [**validate**](doc//LibraryApi.md#validate) | **POST** /library/{id}/validate |
*LibraryApi* | [**createLibrary**](doc//LibraryApi.md#createlibrary) | **POST** /libraries |
*LibraryApi* | [**deleteLibrary**](doc//LibraryApi.md#deletelibrary) | **DELETE** /libraries/{id} |
*LibraryApi* | [**getAllLibraries**](doc//LibraryApi.md#getalllibraries) | **GET** /libraries |
*LibraryApi* | [**getLibrary**](doc//LibraryApi.md#getlibrary) | **GET** /libraries/{id} |
*LibraryApi* | [**getLibraryStatistics**](doc//LibraryApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics |
*LibraryApi* | [**removeOfflineFiles**](doc//LibraryApi.md#removeofflinefiles) | **POST** /libraries/{id}/removeOffline |
*LibraryApi* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /libraries/{id}/scan |
*LibraryApi* | [**updateLibrary**](doc//LibraryApi.md#updatelibrary) | **PUT** /libraries/{id} |
*LibraryApi* | [**validate**](doc//LibraryApi.md#validate) | **POST** /libraries/{id}/validate |
*MemoryApi* | [**addMemoryAssets**](doc//MemoryApi.md#addmemoryassets) | **PUT** /memories/{id}/assets |
*MemoryApi* | [**createMemory**](doc//MemoryApi.md#creatememory) | **POST** /memories |
*MemoryApi* | [**deleteMemory**](doc//MemoryApi.md#deletememory) | **DELETE** /memories/{id} |
@@ -148,20 +148,20 @@ Class | Method | HTTP request | Description
*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |
*OAuthApi* | [**startOAuth**](doc//OAuthApi.md#startoauth) | **POST** /oauth/authorize |
*OAuthApi* | [**unlinkOAuthAccount**](doc//OAuthApi.md#unlinkoauthaccount) | **POST** /oauth/unlink |
*PartnerApi* | [**createPartner**](doc//PartnerApi.md#createpartner) | **POST** /partner/{id} |
*PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner |
*PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} |
*PartnerApi* | [**updatePartner**](doc//PartnerApi.md#updatepartner) | **PUT** /partner/{id} |
*PersonApi* | [**createPerson**](doc//PersonApi.md#createperson) | **POST** /person |
*PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person |
*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} |
*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
*PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
*PersonApi* | [**reassignFaces**](doc//PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person |
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
*PartnerApi* | [**createPartner**](doc//PartnerApi.md#createpartner) | **POST** /partners/{id} |
*PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partners |
*PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partners/{id} |
*PartnerApi* | [**updatePartner**](doc//PartnerApi.md#updatepartner) | **PUT** /partners/{id} |
*PersonApi* | [**createPerson**](doc//PersonApi.md#createperson) | **POST** /people |
*PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /people |
*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /people/{id} |
*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /people/{id}/assets |
*PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /people/{id}/statistics |
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /people/{id}/thumbnail |
*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /people/{id}/merge |
*PersonApi* | [**reassignFaces**](doc//PersonApi.md#reassignfaces) | **PUT** /people/{id}/reassign |
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /people |
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /people/{id} |
*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities |
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions |
@@ -171,23 +171,23 @@ Class | Method | HTTP request | Description
*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart |
*ServerInfoApi* | [**getServerConfig**](doc//ServerInfoApi.md#getserverconfig) | **GET** /server-info/config |
*ServerInfoApi* | [**getServerFeatures**](doc//ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features |
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |
*ServerInfoApi* | [**getServerStatistics**](doc//ServerInfoApi.md#getserverstatistics) | **GET** /server-info/statistics |
*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |
*ServerInfoApi* | [**getStorage**](doc//ServerInfoApi.md#getstorage) | **GET** /server-info/storage |
*ServerInfoApi* | [**getSupportedMediaTypes**](doc//ServerInfoApi.md#getsupportedmediatypes) | **GET** /server-info/media-types |
*ServerInfoApi* | [**getTheme**](doc//ServerInfoApi.md#gettheme) | **GET** /server-info/theme |
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
*SessionsApi* | [**deleteAllSessions**](doc//SessionsApi.md#deleteallsessions) | **DELETE** /sessions |
*SessionsApi* | [**deleteSession**](doc//SessionsApi.md#deletesession) | **DELETE** /sessions/{id} |
*SessionsApi* | [**getSessions**](doc//SessionsApi.md#getsessions) | **GET** /sessions |
*SharedLinkApi* | [**addSharedLinkAssets**](doc//SharedLinkApi.md#addsharedlinkassets) | **PUT** /shared-link/{id}/assets |
*SharedLinkApi* | [**createSharedLink**](doc//SharedLinkApi.md#createsharedlink) | **POST** /shared-link |
*SharedLinkApi* | [**getAllSharedLinks**](doc//SharedLinkApi.md#getallsharedlinks) | **GET** /shared-link |
*SharedLinkApi* | [**getMySharedLink**](doc//SharedLinkApi.md#getmysharedlink) | **GET** /shared-link/me |
*SharedLinkApi* | [**getSharedLinkById**](doc//SharedLinkApi.md#getsharedlinkbyid) | **GET** /shared-link/{id} |
*SharedLinkApi* | [**removeSharedLink**](doc//SharedLinkApi.md#removesharedlink) | **DELETE** /shared-link/{id} |
*SharedLinkApi* | [**removeSharedLinkAssets**](doc//SharedLinkApi.md#removesharedlinkassets) | **DELETE** /shared-link/{id}/assets |
*SharedLinkApi* | [**updateSharedLink**](doc//SharedLinkApi.md#updatesharedlink) | **PATCH** /shared-link/{id} |
*SharedLinkApi* | [**addSharedLinkAssets**](doc//SharedLinkApi.md#addsharedlinkassets) | **PUT** /shared-links/{id}/assets |
*SharedLinkApi* | [**createSharedLink**](doc//SharedLinkApi.md#createsharedlink) | **POST** /shared-links |
*SharedLinkApi* | [**getAllSharedLinks**](doc//SharedLinkApi.md#getallsharedlinks) | **GET** /shared-links |
*SharedLinkApi* | [**getMySharedLink**](doc//SharedLinkApi.md#getmysharedlink) | **GET** /shared-links/me |
*SharedLinkApi* | [**getSharedLinkById**](doc//SharedLinkApi.md#getsharedlinkbyid) | **GET** /shared-links/{id} |
*SharedLinkApi* | [**removeSharedLink**](doc//SharedLinkApi.md#removesharedlink) | **DELETE** /shared-links/{id} |
*SharedLinkApi* | [**removeSharedLinkAssets**](doc//SharedLinkApi.md#removesharedlinkassets) | **DELETE** /shared-links/{id}/assets |
*SharedLinkApi* | [**updateSharedLink**](doc//SharedLinkApi.md#updatesharedlink) | **PATCH** /shared-links/{id} |
*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync |
*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync |
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |
@@ -198,29 +198,29 @@ Class | Method | HTTP request | Description
*SystemMetadataApi* | [**getAdminOnboarding**](doc//SystemMetadataApi.md#getadminonboarding) | **GET** /system-metadata/admin-onboarding |
*SystemMetadataApi* | [**getReverseGeocodingState**](doc//SystemMetadataApi.md#getreversegeocodingstate) | **GET** /system-metadata/reverse-geocoding-state |
*SystemMetadataApi* | [**updateAdminOnboarding**](doc//SystemMetadataApi.md#updateadminonboarding) | **POST** /system-metadata/admin-onboarding |
*TagApi* | [**createTag**](doc//TagApi.md#createtag) | **POST** /tag |
*TagApi* | [**deleteTag**](doc//TagApi.md#deletetag) | **DELETE** /tag/{id} |
*TagApi* | [**getAllTags**](doc//TagApi.md#getalltags) | **GET** /tag |
*TagApi* | [**getTagAssets**](doc//TagApi.md#gettagassets) | **GET** /tag/{id}/assets |
*TagApi* | [**getTagById**](doc//TagApi.md#gettagbyid) | **GET** /tag/{id} |
*TagApi* | [**tagAssets**](doc//TagApi.md#tagassets) | **PUT** /tag/{id}/assets |
*TagApi* | [**untagAssets**](doc//TagApi.md#untagassets) | **DELETE** /tag/{id}/assets |
*TagApi* | [**updateTag**](doc//TagApi.md#updatetag) | **PATCH** /tag/{id} |
*TagApi* | [**createTag**](doc//TagApi.md#createtag) | **POST** /tags |
*TagApi* | [**deleteTag**](doc//TagApi.md#deletetag) | **DELETE** /tags/{id} |
*TagApi* | [**getAllTags**](doc//TagApi.md#getalltags) | **GET** /tags |
*TagApi* | [**getTagAssets**](doc//TagApi.md#gettagassets) | **GET** /tags/{id}/assets |
*TagApi* | [**getTagById**](doc//TagApi.md#gettagbyid) | **GET** /tags/{id} |
*TagApi* | [**tagAssets**](doc//TagApi.md#tagassets) | **PUT** /tags/{id}/assets |
*TagApi* | [**untagAssets**](doc//TagApi.md#untagassets) | **DELETE** /tags/{id}/assets |
*TagApi* | [**updateTag**](doc//TagApi.md#updatetag) | **PATCH** /tags/{id} |
*TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket |
*TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets |
*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty |
*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets |
*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore |
*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |
*UserApi* | [**deleteProfileImage**](doc//UserApi.md#deleteprofileimage) | **DELETE** /user/profile-image |
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{id} |
*UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /user |
*UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /user/me |
*UserApi* | [**getProfileImage**](doc//UserApi.md#getprofileimage) | **GET** /user/profile-image/{id} |
*UserApi* | [**getUserById**](doc//UserApi.md#getuserbyid) | **GET** /user/info/{id} |
*UserApi* | [**restoreUser**](doc//UserApi.md#restoreuser) | **POST** /user/{id}/restore |
*UserApi* | [**updateUser**](doc//UserApi.md#updateuser) | **PUT** /user |
*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /users/profile-image |
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /users |
*UserApi* | [**deleteProfileImage**](doc//UserApi.md#deleteprofileimage) | **DELETE** /users/profile-image |
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /users/{id} |
*UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /users |
*UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /users/me |
*UserApi* | [**getProfileImage**](doc//UserApi.md#getprofileimage) | **GET** /users/{id}/profile-image |
*UserApi* | [**getUserById**](doc//UserApi.md#getuserbyid) | **GET** /users/{id} |
*UserApi* | [**restoreUser**](doc//UserApi.md#restoreuser) | **POST** /users/{id}/restore |
*UserApi* | [**updateUser**](doc//UserApi.md#updateuser) | **PUT** /users |
## Documentation For Models
@@ -259,6 +259,8 @@ Class | Method | HTTP request | Description
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
- [AssetJobName](doc//AssetJobName.md)
- [AssetJobsDto](doc//AssetJobsDto.md)
- [AssetMediaResponseDto](doc//AssetMediaResponseDto.md)
- [AssetMediaStatus](doc//AssetMediaStatus.md)
- [AssetOrder](doc//AssetOrder.md)
- [AssetResponseDto](doc//AssetResponseDto.md)
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
@@ -348,10 +350,10 @@ Class | Method | HTTP request | Description
- [SearchSuggestionType](doc//SearchSuggestionType.md)
- [ServerConfigDto](doc//ServerConfigDto.md)
- [ServerFeaturesDto](doc//ServerFeaturesDto.md)
- [ServerInfoResponseDto](doc//ServerInfoResponseDto.md)
- [ServerMediaTypesResponseDto](doc//ServerMediaTypesResponseDto.md)
- [ServerPingResponse](doc//ServerPingResponse.md)
- [ServerStatsResponseDto](doc//ServerStatsResponseDto.md)
- [ServerStorageResponseDto](doc//ServerStorageResponseDto.md)
- [ServerThemeDto](doc//ServerThemeDto.md)
- [ServerVersionResponseDto](doc//ServerVersionResponseDto.md)
- [SessionResponseDto](doc//SessionResponseDto.md)

View File

@@ -91,6 +91,8 @@ part 'model/asset_ids_dto.dart';
part 'model/asset_ids_response_dto.dart';
part 'model/asset_job_name.dart';
part 'model/asset_jobs_dto.dart';
part 'model/asset_media_response_dto.dart';
part 'model/asset_media_status.dart';
part 'model/asset_order.dart';
part 'model/asset_response_dto.dart';
part 'model/asset_stats_response_dto.dart';
@@ -180,10 +182,10 @@ part 'model/search_response_dto.dart';
part 'model/search_suggestion_type.dart';
part 'model/server_config_dto.dart';
part 'model/server_features_dto.dart';
part 'model/server_info_response_dto.dart';
part 'model/server_media_types_response_dto.dart';
part 'model/server_ping_response.dart';
part 'model/server_stats_response_dto.dart';
part 'model/server_storage_response_dto.dart';
part 'model/server_theme_dto.dart';
part 'model/server_version_response_dto.dart';
part 'model/session_response_dto.dart';

View File

@@ -16,13 +16,13 @@ class ActivityApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /activity' operation and returns the [Response].
/// Performs an HTTP 'POST /activities' operation and returns the [Response].
/// Parameters:
///
/// * [ActivityCreateDto] activityCreateDto (required):
Future<Response> createActivityWithHttpInfo(ActivityCreateDto activityCreateDto,) async {
// ignore: prefer_const_declarations
final path = r'/activity';
final path = r'/activities';
// ignore: prefer_final_locals
Object? postBody = activityCreateDto;
@@ -63,13 +63,13 @@ class ActivityApi {
return null;
}
/// Performs an HTTP 'DELETE /activity/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /activities/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteActivityWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/activity/{id}'
final path = r'/activities/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -103,7 +103,7 @@ class ActivityApi {
}
}
/// Performs an HTTP 'GET /activity' operation and returns the [Response].
/// Performs an HTTP 'GET /activities' operation and returns the [Response].
/// Parameters:
///
/// * [String] albumId (required):
@@ -117,7 +117,7 @@ class ActivityApi {
/// * [String] userId:
Future<Response> getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async {
// ignore: prefer_const_declarations
final path = r'/activity';
final path = r'/activities';
// ignore: prefer_final_locals
Object? postBody;
@@ -183,7 +183,7 @@ class ActivityApi {
return null;
}
/// Performs an HTTP 'GET /activity/statistics' operation and returns the [Response].
/// Performs an HTTP 'GET /activities/statistics' operation and returns the [Response].
/// Parameters:
///
/// * [String] albumId (required):
@@ -191,7 +191,7 @@ class ActivityApi {
/// * [String] assetId:
Future<Response> getActivityStatisticsWithHttpInfo(String albumId, { String? assetId, }) async {
// ignore: prefer_const_declarations
final path = r'/activity/statistics';
final path = r'/activities/statistics';
// ignore: prefer_final_locals
Object? postBody;

View File

@@ -16,7 +16,7 @@ class AlbumApi {
final ApiClient apiClient;
/// Performs an HTTP 'PUT /album/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'PUT /albums/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -26,7 +26,7 @@ class AlbumApi {
/// * [String] key:
Future<Response> addAssetsToAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}/assets'
final path = r'/albums/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -79,7 +79,7 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'PUT /album/{id}/users' operation and returns the [Response].
/// Performs an HTTP 'PUT /albums/{id}/users' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -87,7 +87,7 @@ class AlbumApi {
/// * [AddUsersDto] addUsersDto (required):
Future<Response> addUsersToAlbumWithHttpInfo(String id, AddUsersDto addUsersDto,) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}/users'
final path = r'/albums/{id}/users'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -131,13 +131,13 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'POST /album' operation and returns the [Response].
/// Performs an HTTP 'POST /albums' operation and returns the [Response].
/// Parameters:
///
/// * [CreateAlbumDto] createAlbumDto (required):
Future<Response> createAlbumWithHttpInfo(CreateAlbumDto createAlbumDto,) async {
// ignore: prefer_const_declarations
final path = r'/album';
final path = r'/albums';
// ignore: prefer_final_locals
Object? postBody = createAlbumDto;
@@ -178,13 +178,13 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'DELETE /album/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /albums/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteAlbumWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}'
final path = r'/albums/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -218,10 +218,10 @@ class AlbumApi {
}
}
/// Performs an HTTP 'GET /album/count' operation and returns the [Response].
/// Performs an HTTP 'GET /albums/count' operation and returns the [Response].
Future<Response> getAlbumCountWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/album/count';
final path = r'/albums/count';
// ignore: prefer_final_locals
Object? postBody;
@@ -259,7 +259,7 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'GET /album/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /albums/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -269,7 +269,7 @@ class AlbumApi {
/// * [bool] withoutAssets:
Future<Response> getAlbumInfoWithHttpInfo(String id, { String? key, bool? withoutAssets, }) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}'
final path = r'/albums/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -322,7 +322,7 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'GET /album' operation and returns the [Response].
/// Performs an HTTP 'GET /albums' operation and returns the [Response].
/// Parameters:
///
/// * [String] assetId:
@@ -331,7 +331,7 @@ class AlbumApi {
/// * [bool] shared:
Future<Response> getAllAlbumsWithHttpInfo({ String? assetId, bool? shared, }) async {
// ignore: prefer_const_declarations
final path = r'/album';
final path = r'/albums';
// ignore: prefer_final_locals
Object? postBody;
@@ -385,7 +385,7 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'DELETE /album/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'DELETE /albums/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -393,7 +393,7 @@ class AlbumApi {
/// * [BulkIdsDto] bulkIdsDto (required):
Future<Response> removeAssetFromAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}/assets'
final path = r'/albums/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -440,7 +440,7 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'DELETE /album/{id}/user/{userId}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /albums/{id}/user/{userId}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -448,7 +448,7 @@ class AlbumApi {
/// * [String] userId (required):
Future<Response> removeUserFromAlbumWithHttpInfo(String id, String userId,) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}/user/{userId}'
final path = r'/albums/{id}/user/{userId}'
.replaceAll('{id}', id)
.replaceAll('{userId}', userId);
@@ -485,7 +485,7 @@ class AlbumApi {
}
}
/// Performs an HTTP 'PATCH /album/{id}' operation and returns the [Response].
/// Performs an HTTP 'PATCH /albums/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -493,7 +493,7 @@ class AlbumApi {
/// * [UpdateAlbumDto] updateAlbumDto (required):
Future<Response> updateAlbumInfoWithHttpInfo(String id, UpdateAlbumDto updateAlbumDto,) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}'
final path = r'/albums/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -537,7 +537,7 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'PUT /album/{id}/user/{userId}' operation and returns the [Response].
/// Performs an HTTP 'PUT /albums/{id}/user/{userId}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -547,7 +547,7 @@ class AlbumApi {
/// * [UpdateAlbumUserDto] updateAlbumUserDto (required):
Future<Response> updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto,) async {
// ignore: prefer_const_declarations
final path = r'/album/{id}/user/{userId}'
final path = r'/albums/{id}/user/{userId}'
.replaceAll('{id}', id)
.replaceAll('{userId}', userId);

View File

@@ -16,13 +16,13 @@ class APIKeyApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /api-key' operation and returns the [Response].
/// Performs an HTTP 'POST /api-keys' operation and returns the [Response].
/// Parameters:
///
/// * [APIKeyCreateDto] aPIKeyCreateDto (required):
Future<Response> createApiKeyWithHttpInfo(APIKeyCreateDto aPIKeyCreateDto,) async {
// ignore: prefer_const_declarations
final path = r'/api-key';
final path = r'/api-keys';
// ignore: prefer_final_locals
Object? postBody = aPIKeyCreateDto;
@@ -63,13 +63,13 @@ class APIKeyApi {
return null;
}
/// Performs an HTTP 'DELETE /api-key/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /api-keys/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteApiKeyWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/api-key/{id}'
final path = r'/api-keys/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -103,13 +103,13 @@ class APIKeyApi {
}
}
/// Performs an HTTP 'GET /api-key/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /api-keys/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getApiKeyWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/api-key/{id}'
final path = r'/api-keys/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -151,10 +151,10 @@ class APIKeyApi {
return null;
}
/// Performs an HTTP 'GET /api-key' operation and returns the [Response].
/// Performs an HTTP 'GET /api-keys' operation and returns the [Response].
Future<Response> getApiKeysWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/api-key';
final path = r'/api-keys';
// ignore: prefer_final_locals
Object? postBody;
@@ -195,7 +195,7 @@ class APIKeyApi {
return null;
}
/// Performs an HTTP 'PUT /api-key/{id}' operation and returns the [Response].
/// Performs an HTTP 'PUT /api-keys/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -203,7 +203,7 @@ class APIKeyApi {
/// * [APIKeyUpdateDto] aPIKeyUpdateDto (required):
Future<Response> updateApiKeyWithHttpInfo(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/api-key/{id}'
final path = r'/api-keys/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -159,117 +159,6 @@ class AssetApi {
}
}
/// Get all AssetEntity belong to the user
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] ifNoneMatch:
/// ETag of data already cached on the client
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [int] skip:
///
/// * [int] take:
///
/// * [DateTime] updatedAfter:
///
/// * [DateTime] updatedBefore:
///
/// * [String] userId:
Future<Response> getAllAssetsWithHttpInfo({ String? ifNoneMatch, bool? isArchived, bool? isFavorite, int? skip, int? take, DateTime? updatedAfter, DateTime? updatedBefore, String? userId, }) async {
// ignore: prefer_const_declarations
final path = r'/asset';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip));
}
if (take != null) {
queryParams.addAll(_queryParams('', 'take', take));
}
if (updatedAfter != null) {
queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter));
}
if (updatedBefore != null) {
queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore));
}
if (userId != null) {
queryParams.addAll(_queryParams('', 'userId', userId));
}
if (ifNoneMatch != null) {
headerParams[r'if-none-match'] = parameterToString(ifNoneMatch);
}
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get all AssetEntity belong to the user
///
/// Parameters:
///
/// * [String] ifNoneMatch:
/// ETag of data already cached on the client
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [int] skip:
///
/// * [int] take:
///
/// * [DateTime] updatedAfter:
///
/// * [DateTime] updatedBefore:
///
/// * [String] userId:
Future<List<AssetResponseDto>?> getAllAssets({ String? ifNoneMatch, bool? isArchived, bool? isFavorite, int? skip, int? take, DateTime? updatedAfter, DateTime? updatedBefore, String? userId, }) async {
final response = await getAllAssetsWithHttpInfo( ifNoneMatch: ifNoneMatch, isArchived: isArchived, isFavorite: isFavorite, skip: skip, take: take, updatedAfter: updatedAfter, updatedBefore: updatedBefore, userId: userId, );
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<AssetResponseDto>') as List)
.cast<AssetResponseDto>()
.toList(growable: false);
}
return null;
}
/// Get all asset of a device that are in the database, ID only.
///
/// Note: This method returns the HTTP [Response].
@@ -710,6 +599,121 @@ class AssetApi {
return null;
}
/// Replace the asset with new file, without changing its id
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
///
/// * [MultipartFile] assetData (required):
///
/// * [String] deviceAssetId (required):
///
/// * [String] deviceId (required):
///
/// * [DateTime] fileCreatedAt (required):
///
/// * [DateTime] fileModifiedAt (required):
///
/// * [String] key:
///
/// * [String] duration:
Future<Response> replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? duration, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/{id}/file'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['multipart/form-data'];
bool hasFields = false;
final mp = MultipartRequest('PUT', Uri.parse(path));
if (assetData != null) {
hasFields = true;
mp.fields[r'assetData'] = assetData.field;
mp.files.add(assetData);
}
if (deviceAssetId != null) {
hasFields = true;
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
}
if (deviceId != null) {
hasFields = true;
mp.fields[r'deviceId'] = parameterToString(deviceId);
}
if (duration != null) {
hasFields = true;
mp.fields[r'duration'] = parameterToString(duration);
}
if (fileCreatedAt != null) {
hasFields = true;
mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt);
}
if (fileModifiedAt != null) {
hasFields = true;
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
}
if (hasFields) {
postBody = mp;
}
return apiClient.invokeAPI(
path,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Replace the asset with new file, without changing its id
///
/// Parameters:
///
/// * [String] id (required):
///
/// * [MultipartFile] assetData (required):
///
/// * [String] deviceAssetId (required):
///
/// * [String] deviceId (required):
///
/// * [DateTime] fileCreatedAt (required):
///
/// * [DateTime] fileModifiedAt (required):
///
/// * [String] key:
///
/// * [String] duration:
Future<AssetMediaResponseDto?> replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? duration, }) async {
final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, duration: duration, );
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), 'AssetMediaResponseDto',) as AssetMediaResponseDto;
}
return null;
}
/// Performs an HTTP 'POST /asset/jobs' operation and returns the [Response].
/// Parameters:
///

View File

@@ -16,13 +16,13 @@ class FaceApi {
final ApiClient apiClient;
/// Performs an HTTP 'GET /face' operation and returns the [Response].
/// Performs an HTTP 'GET /faces' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getFacesWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/face';
final path = r'/faces';
// ignore: prefer_final_locals
Object? postBody;
@@ -68,7 +68,7 @@ class FaceApi {
return null;
}
/// Performs an HTTP 'PUT /face/{id}' operation and returns the [Response].
/// Performs an HTTP 'PUT /faces/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -76,7 +76,7 @@ class FaceApi {
/// * [FaceDto] faceDto (required):
Future<Response> reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto,) async {
// ignore: prefer_const_declarations
final path = r'/face/{id}'
final path = r'/faces/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -16,13 +16,13 @@ class FileReportApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /report/fix' operation and returns the [Response].
/// Performs an HTTP 'POST /reports/fix' operation and returns the [Response].
/// Parameters:
///
/// * [FileReportFixDto] fileReportFixDto (required):
Future<Response> fixAuditFilesWithHttpInfo(FileReportFixDto fileReportFixDto,) async {
// ignore: prefer_const_declarations
final path = r'/report/fix';
final path = r'/reports/fix';
// ignore: prefer_final_locals
Object? postBody = fileReportFixDto;
@@ -55,10 +55,10 @@ class FileReportApi {
}
}
/// Performs an HTTP 'GET /report' operation and returns the [Response].
/// Performs an HTTP 'GET /reports' operation and returns the [Response].
Future<Response> getAuditFilesWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/report';
final path = r'/reports';
// ignore: prefer_final_locals
Object? postBody;
@@ -96,13 +96,13 @@ class FileReportApi {
return null;
}
/// Performs an HTTP 'POST /report/checksum' operation and returns the [Response].
/// Performs an HTTP 'POST /reports/checksum' operation and returns the [Response].
/// Parameters:
///
/// * [FileChecksumDto] fileChecksumDto (required):
Future<Response> getFileChecksumsWithHttpInfo(FileChecksumDto fileChecksumDto,) async {
// ignore: prefer_const_declarations
final path = r'/report/checksum';
final path = r'/reports/checksum';
// ignore: prefer_final_locals
Object? postBody = fileChecksumDto;

View File

@@ -16,13 +16,13 @@ class LibraryApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /library' operation and returns the [Response].
/// Performs an HTTP 'POST /libraries' operation and returns the [Response].
/// Parameters:
///
/// * [CreateLibraryDto] createLibraryDto (required):
Future<Response> createLibraryWithHttpInfo(CreateLibraryDto createLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library';
final path = r'/libraries';
// ignore: prefer_final_locals
Object? postBody = createLibraryDto;
@@ -63,13 +63,13 @@ class LibraryApi {
return null;
}
/// Performs an HTTP 'DELETE /library/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /libraries/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteLibraryWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}'
final path = r'/libraries/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -103,10 +103,10 @@ class LibraryApi {
}
}
/// Performs an HTTP 'GET /library' operation and returns the [Response].
/// Performs an HTTP 'GET /libraries' operation and returns the [Response].
Future<Response> getAllLibrariesWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/library';
final path = r'/libraries';
// ignore: prefer_final_locals
Object? postBody;
@@ -147,13 +147,13 @@ class LibraryApi {
return null;
}
/// Performs an HTTP 'GET /library/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /libraries/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getLibraryWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}'
final path = r'/libraries/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -195,13 +195,13 @@ class LibraryApi {
return null;
}
/// Performs an HTTP 'GET /library/{id}/statistics' operation and returns the [Response].
/// Performs an HTTP 'GET /libraries/{id}/statistics' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getLibraryStatisticsWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/statistics'
final path = r'/libraries/{id}/statistics'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -243,13 +243,13 @@ class LibraryApi {
return null;
}
/// Performs an HTTP 'POST /library/{id}/removeOffline' operation and returns the [Response].
/// Performs an HTTP 'POST /libraries/{id}/removeOffline' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> removeOfflineFilesWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/removeOffline'
final path = r'/libraries/{id}/removeOffline'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -283,7 +283,7 @@ class LibraryApi {
}
}
/// Performs an HTTP 'POST /library/{id}/scan' operation and returns the [Response].
/// Performs an HTTP 'POST /libraries/{id}/scan' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -291,7 +291,7 @@ class LibraryApi {
/// * [ScanLibraryDto] scanLibraryDto (required):
Future<Response> scanLibraryWithHttpInfo(String id, ScanLibraryDto scanLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/scan'
final path = r'/libraries/{id}/scan'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -327,7 +327,7 @@ class LibraryApi {
}
}
/// Performs an HTTP 'PUT /library/{id}' operation and returns the [Response].
/// Performs an HTTP 'PUT /libraries/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -335,7 +335,7 @@ class LibraryApi {
/// * [UpdateLibraryDto] updateLibraryDto (required):
Future<Response> updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}'
final path = r'/libraries/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -379,7 +379,7 @@ class LibraryApi {
return null;
}
/// Performs an HTTP 'POST /library/{id}/validate' operation and returns the [Response].
/// Performs an HTTP 'POST /libraries/{id}/validate' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -387,7 +387,7 @@ class LibraryApi {
/// * [ValidateLibraryDto] validateLibraryDto (required):
Future<Response> validateWithHttpInfo(String id, ValidateLibraryDto validateLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/validate'
final path = r'/libraries/{id}/validate'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -16,13 +16,13 @@ class PartnerApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /partner/{id}' operation and returns the [Response].
/// Performs an HTTP 'POST /partners/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> createPartnerWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/partner/{id}'
final path = r'/partners/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -64,13 +64,13 @@ class PartnerApi {
return null;
}
/// Performs an HTTP 'GET /partner' operation and returns the [Response].
/// Performs an HTTP 'GET /partners' operation and returns the [Response].
/// Parameters:
///
/// * [String] direction (required):
Future<Response> getPartnersWithHttpInfo(String direction,) async {
// ignore: prefer_const_declarations
final path = r'/partner';
final path = r'/partners';
// ignore: prefer_final_locals
Object? postBody;
@@ -116,13 +116,13 @@ class PartnerApi {
return null;
}
/// Performs an HTTP 'DELETE /partner/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /partners/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> removePartnerWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/partner/{id}'
final path = r'/partners/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -156,7 +156,7 @@ class PartnerApi {
}
}
/// Performs an HTTP 'PUT /partner/{id}' operation and returns the [Response].
/// Performs an HTTP 'PUT /partners/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -164,7 +164,7 @@ class PartnerApi {
/// * [UpdatePartnerDto] updatePartnerDto (required):
Future<Response> updatePartnerWithHttpInfo(String id, UpdatePartnerDto updatePartnerDto,) async {
// ignore: prefer_const_declarations
final path = r'/partner/{id}'
final path = r'/partners/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -16,13 +16,13 @@ class PersonApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /person' operation and returns the [Response].
/// Performs an HTTP 'POST /people' operation and returns the [Response].
/// Parameters:
///
/// * [PersonCreateDto] personCreateDto (required):
Future<Response> createPersonWithHttpInfo(PersonCreateDto personCreateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person';
final path = r'/people';
// ignore: prefer_final_locals
Object? postBody = personCreateDto;
@@ -63,13 +63,13 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'GET /person' operation and returns the [Response].
/// Performs an HTTP 'GET /people' operation and returns the [Response].
/// Parameters:
///
/// * [bool] withHidden:
Future<Response> getAllPeopleWithHttpInfo({ bool? withHidden, }) async {
// ignore: prefer_const_declarations
final path = r'/person';
final path = r'/people';
// ignore: prefer_final_locals
Object? postBody;
@@ -114,13 +114,13 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'GET /person/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /people/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getPersonWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}'
final path = r'/people/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -162,13 +162,13 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'GET /person/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'GET /people/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getPersonAssetsWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/assets'
final path = r'/people/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -213,13 +213,13 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'GET /person/{id}/statistics' operation and returns the [Response].
/// Performs an HTTP 'GET /people/{id}/statistics' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getPersonStatisticsWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/statistics'
final path = r'/people/{id}/statistics'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -261,13 +261,13 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'GET /person/{id}/thumbnail' operation and returns the [Response].
/// Performs an HTTP 'GET /people/{id}/thumbnail' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getPersonThumbnailWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/thumbnail'
final path = r'/people/{id}/thumbnail'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -309,7 +309,7 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'POST /person/{id}/merge' operation and returns the [Response].
/// Performs an HTTP 'POST /people/{id}/merge' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -317,7 +317,7 @@ class PersonApi {
/// * [MergePersonDto] mergePersonDto (required):
Future<Response> mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/merge'
final path = r'/people/{id}/merge'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -364,7 +364,7 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'PUT /person/{id}/reassign' operation and returns the [Response].
/// Performs an HTTP 'PUT /people/{id}/reassign' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -372,7 +372,7 @@ class PersonApi {
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<Response> reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/reassign'
final path = r'/people/{id}/reassign'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -419,13 +419,13 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'PUT /person' operation and returns the [Response].
/// Performs an HTTP 'PUT /people' operation and returns the [Response].
/// Parameters:
///
/// * [PeopleUpdateDto] peopleUpdateDto (required):
Future<Response> updatePeopleWithHttpInfo(PeopleUpdateDto peopleUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person';
final path = r'/people';
// ignore: prefer_final_locals
Object? postBody = peopleUpdateDto;
@@ -469,7 +469,7 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'PUT /person/{id}' operation and returns the [Response].
/// Performs an HTTP 'PUT /people/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -477,7 +477,7 @@ class PersonApi {
/// * [PersonUpdateDto] personUpdateDto (required):
Future<Response> updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}'
final path = r'/people/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -98,47 +98,6 @@ class ServerInfoApi {
return null;
}
/// Performs an HTTP 'GET /server-info' operation and returns the [Response].
Future<Response> getServerInfoWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/server-info';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<ServerInfoResponseDto?> getServerInfo() async {
final response = await getServerInfoWithHttpInfo();
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), 'ServerInfoResponseDto',) as ServerInfoResponseDto;
}
return null;
}
/// Performs an HTTP 'GET /server-info/statistics' operation and returns the [Response].
Future<Response> getServerStatisticsWithHttpInfo() async {
// ignore: prefer_const_declarations
@@ -221,6 +180,47 @@ class ServerInfoApi {
return null;
}
/// Performs an HTTP 'GET /server-info/storage' operation and returns the [Response].
Future<Response> getStorageWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/server-info/storage';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<ServerStorageResponseDto?> getStorage() async {
final response = await getStorageWithHttpInfo();
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), 'ServerStorageResponseDto',) as ServerStorageResponseDto;
}
return null;
}
/// Performs an HTTP 'GET /server-info/media-types' operation and returns the [Response].
Future<Response> getSupportedMediaTypesWithHttpInfo() async {
// ignore: prefer_const_declarations

View File

@@ -16,7 +16,7 @@ class SharedLinkApi {
final ApiClient apiClient;
/// Performs an HTTP 'PUT /shared-link/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'PUT /shared-links/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -26,7 +26,7 @@ class SharedLinkApi {
/// * [String] key:
Future<Response> addSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/shared-link/{id}/assets'
final path = r'/shared-links/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -79,13 +79,13 @@ class SharedLinkApi {
return null;
}
/// Performs an HTTP 'POST /shared-link' operation and returns the [Response].
/// Performs an HTTP 'POST /shared-links' operation and returns the [Response].
/// Parameters:
///
/// * [SharedLinkCreateDto] sharedLinkCreateDto (required):
Future<Response> createSharedLinkWithHttpInfo(SharedLinkCreateDto sharedLinkCreateDto,) async {
// ignore: prefer_const_declarations
final path = r'/shared-link';
final path = r'/shared-links';
// ignore: prefer_final_locals
Object? postBody = sharedLinkCreateDto;
@@ -126,10 +126,10 @@ class SharedLinkApi {
return null;
}
/// Performs an HTTP 'GET /shared-link' operation and returns the [Response].
/// Performs an HTTP 'GET /shared-links' operation and returns the [Response].
Future<Response> getAllSharedLinksWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/shared-link';
final path = r'/shared-links';
// ignore: prefer_final_locals
Object? postBody;
@@ -170,7 +170,7 @@ class SharedLinkApi {
return null;
}
/// Performs an HTTP 'GET /shared-link/me' operation and returns the [Response].
/// Performs an HTTP 'GET /shared-links/me' operation and returns the [Response].
/// Parameters:
///
/// * [String] key:
@@ -180,7 +180,7 @@ class SharedLinkApi {
/// * [String] token:
Future<Response> getMySharedLinkWithHttpInfo({ String? key, String? password, String? token, }) async {
// ignore: prefer_const_declarations
final path = r'/shared-link/me';
final path = r'/shared-links/me';
// ignore: prefer_final_locals
Object? postBody;
@@ -235,13 +235,13 @@ class SharedLinkApi {
return null;
}
/// Performs an HTTP 'GET /shared-link/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /shared-links/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getSharedLinkByIdWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/shared-link/{id}'
final path = r'/shared-links/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -283,13 +283,13 @@ class SharedLinkApi {
return null;
}
/// Performs an HTTP 'DELETE /shared-link/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /shared-links/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> removeSharedLinkWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/shared-link/{id}'
final path = r'/shared-links/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -323,7 +323,7 @@ class SharedLinkApi {
}
}
/// Performs an HTTP 'DELETE /shared-link/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'DELETE /shared-links/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -333,7 +333,7 @@ class SharedLinkApi {
/// * [String] key:
Future<Response> removeSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/shared-link/{id}/assets'
final path = r'/shared-links/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -386,7 +386,7 @@ class SharedLinkApi {
return null;
}
/// Performs an HTTP 'PATCH /shared-link/{id}' operation and returns the [Response].
/// Performs an HTTP 'PATCH /shared-links/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -394,7 +394,7 @@ class SharedLinkApi {
/// * [SharedLinkEditDto] sharedLinkEditDto (required):
Future<Response> updateSharedLinkWithHttpInfo(String id, SharedLinkEditDto sharedLinkEditDto,) async {
// ignore: prefer_const_declarations
final path = r'/shared-link/{id}'
final path = r'/shared-links/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -16,13 +16,13 @@ class TagApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /tag' operation and returns the [Response].
/// Performs an HTTP 'POST /tags' operation and returns the [Response].
/// Parameters:
///
/// * [CreateTagDto] createTagDto (required):
Future<Response> createTagWithHttpInfo(CreateTagDto createTagDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag';
final path = r'/tags';
// ignore: prefer_final_locals
Object? postBody = createTagDto;
@@ -63,13 +63,13 @@ class TagApi {
return null;
}
/// Performs an HTTP 'DELETE /tag/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /tags/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteTagWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}'
final path = r'/tags/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -103,10 +103,10 @@ class TagApi {
}
}
/// Performs an HTTP 'GET /tag' operation and returns the [Response].
/// Performs an HTTP 'GET /tags' operation and returns the [Response].
Future<Response> getAllTagsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/tag';
final path = r'/tags';
// ignore: prefer_final_locals
Object? postBody;
@@ -147,13 +147,13 @@ class TagApi {
return null;
}
/// Performs an HTTP 'GET /tag/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'GET /tags/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getTagAssetsWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}/assets'
final path = r'/tags/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -198,13 +198,13 @@ class TagApi {
return null;
}
/// Performs an HTTP 'GET /tag/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /tags/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getTagByIdWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}'
final path = r'/tags/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -246,7 +246,7 @@ class TagApi {
return null;
}
/// Performs an HTTP 'PUT /tag/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'PUT /tags/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -254,7 +254,7 @@ class TagApi {
/// * [AssetIdsDto] assetIdsDto (required):
Future<Response> tagAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}/assets'
final path = r'/tags/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -301,7 +301,7 @@ class TagApi {
return null;
}
/// Performs an HTTP 'DELETE /tag/{id}/assets' operation and returns the [Response].
/// Performs an HTTP 'DELETE /tags/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -309,7 +309,7 @@ class TagApi {
/// * [AssetIdsDto] assetIdsDto (required):
Future<Response> untagAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}/assets'
final path = r'/tags/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -356,7 +356,7 @@ class TagApi {
return null;
}
/// Performs an HTTP 'PATCH /tag/{id}' operation and returns the [Response].
/// Performs an HTTP 'PATCH /tags/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -364,7 +364,7 @@ class TagApi {
/// * [UpdateTagDto] updateTagDto (required):
Future<Response> updateTagWithHttpInfo(String id, UpdateTagDto updateTagDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}'
final path = r'/tags/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals

View File

@@ -16,13 +16,13 @@ class UserApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /user/profile-image' operation and returns the [Response].
/// Performs an HTTP 'POST /users/profile-image' operation and returns the [Response].
/// Parameters:
///
/// * [MultipartFile] file (required):
Future<Response> createProfileImageWithHttpInfo(MultipartFile file,) async {
// ignore: prefer_const_declarations
final path = r'/user/profile-image';
final path = r'/users/profile-image';
// ignore: prefer_final_locals
Object? postBody;
@@ -73,13 +73,13 @@ class UserApi {
return null;
}
/// Performs an HTTP 'POST /user' operation and returns the [Response].
/// Performs an HTTP 'POST /users' operation and returns the [Response].
/// Parameters:
///
/// * [CreateUserDto] createUserDto (required):
Future<Response> createUserWithHttpInfo(CreateUserDto createUserDto,) async {
// ignore: prefer_const_declarations
final path = r'/user';
final path = r'/users';
// ignore: prefer_final_locals
Object? postBody = createUserDto;
@@ -120,10 +120,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'DELETE /user/profile-image' operation and returns the [Response].
/// Performs an HTTP 'DELETE /users/profile-image' operation and returns the [Response].
Future<Response> deleteProfileImageWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/user/profile-image';
final path = r'/users/profile-image';
// ignore: prefer_final_locals
Object? postBody;
@@ -153,7 +153,7 @@ class UserApi {
}
}
/// Performs an HTTP 'DELETE /user/{id}' operation and returns the [Response].
/// Performs an HTTP 'DELETE /users/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
@@ -161,7 +161,7 @@ class UserApi {
/// * [DeleteUserDto] deleteUserDto (required):
Future<Response> deleteUserWithHttpInfo(String id, DeleteUserDto deleteUserDto,) async {
// ignore: prefer_const_declarations
final path = r'/user/{id}'
final path = r'/users/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -205,13 +205,13 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user' operation and returns the [Response].
/// Performs an HTTP 'GET /users' operation and returns the [Response].
/// Parameters:
///
/// * [bool] isAll (required):
Future<Response> getAllUsersWithHttpInfo(bool isAll,) async {
// ignore: prefer_const_declarations
final path = r'/user';
final path = r'/users';
// ignore: prefer_final_locals
Object? postBody;
@@ -257,10 +257,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/me' operation and returns the [Response].
/// Performs an HTTP 'GET /users/me' operation and returns the [Response].
Future<Response> getMyUserInfoWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/user/me';
final path = r'/users/me';
// ignore: prefer_final_locals
Object? postBody;
@@ -298,13 +298,13 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/profile-image/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /users/{id}/profile-image' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getProfileImageWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/user/profile-image/{id}'
final path = r'/users/{id}/profile-image'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -346,13 +346,13 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/info/{id}' operation and returns the [Response].
/// Performs an HTTP 'GET /users/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getUserByIdWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/user/info/{id}'
final path = r'/users/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -394,13 +394,13 @@ class UserApi {
return null;
}
/// Performs an HTTP 'POST /user/{id}/restore' operation and returns the [Response].
/// Performs an HTTP 'POST /users/{id}/restore' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> restoreUserWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/user/{id}/restore'
final path = r'/users/{id}/restore'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@@ -442,13 +442,13 @@ class UserApi {
return null;
}
/// Performs an HTTP 'PUT /user' operation and returns the [Response].
/// Performs an HTTP 'PUT /users' operation and returns the [Response].
/// Parameters:
///
/// * [UpdateUserDto] updateUserDto (required):
Future<Response> updateUserWithHttpInfo(UpdateUserDto updateUserDto,) async {
// ignore: prefer_const_declarations
final path = r'/user';
final path = r'/users';
// ignore: prefer_final_locals
Object? postBody = updateUserDto;

View File

@@ -250,6 +250,10 @@ class ApiClient {
return AssetJobNameTypeTransformer().decode(value);
case 'AssetJobsDto':
return AssetJobsDto.fromJson(value);
case 'AssetMediaResponseDto':
return AssetMediaResponseDto.fromJson(value);
case 'AssetMediaStatus':
return AssetMediaStatusTypeTransformer().decode(value);
case 'AssetOrder':
return AssetOrderTypeTransformer().decode(value);
case 'AssetResponseDto':
@@ -428,14 +432,14 @@ class ApiClient {
return ServerConfigDto.fromJson(value);
case 'ServerFeaturesDto':
return ServerFeaturesDto.fromJson(value);
case 'ServerInfoResponseDto':
return ServerInfoResponseDto.fromJson(value);
case 'ServerMediaTypesResponseDto':
return ServerMediaTypesResponseDto.fromJson(value);
case 'ServerPingResponse':
return ServerPingResponse.fromJson(value);
case 'ServerStatsResponseDto':
return ServerStatsResponseDto.fromJson(value);
case 'ServerStorageResponseDto':
return ServerStorageResponseDto.fromJson(value);
case 'ServerThemeDto':
return ServerThemeDto.fromJson(value);
case 'ServerVersionResponseDto':

View File

@@ -61,6 +61,9 @@ String parameterToString(dynamic value) {
if (value is AssetJobName) {
return AssetJobNameTypeTransformer().encode(value).toString();
}
if (value is AssetMediaStatus) {
return AssetMediaStatusTypeTransformer().encode(value).toString();
}
if (value is AssetOrder) {
return AssetOrderTypeTransformer().encode(value).toString();
}

View File

@@ -14,32 +14,25 @@ class AddUsersDto {
/// Returns a new [AddUsersDto] instance.
AddUsersDto({
this.albumUsers = const [],
this.sharedUserIds = const [],
});
List<AlbumUserAddDto> albumUsers;
/// This property was deprecated in v1.102.0
List<String> sharedUserIds;
@override
bool operator ==(Object other) => identical(this, other) || other is AddUsersDto &&
_deepEquality.equals(other.albumUsers, albumUsers) &&
_deepEquality.equals(other.sharedUserIds, sharedUserIds);
_deepEquality.equals(other.albumUsers, albumUsers);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(albumUsers.hashCode) +
(sharedUserIds.hashCode);
(albumUsers.hashCode);
@override
String toString() => 'AddUsersDto[albumUsers=$albumUsers, sharedUserIds=$sharedUserIds]';
String toString() => 'AddUsersDto[albumUsers=$albumUsers]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'albumUsers'] = this.albumUsers;
json[r'sharedUserIds'] = this.sharedUserIds;
return json;
}
@@ -52,9 +45,6 @@ class AddUsersDto {
return AddUsersDto(
albumUsers: AlbumUserAddDto.listFromJson(json[r'albumUsers']),
sharedUserIds: json[r'sharedUserIds'] is Iterable
? (json[r'sharedUserIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;

View File

@@ -29,7 +29,6 @@ class AlbumResponseDto {
required this.owner,
required this.ownerId,
required this.shared,
this.sharedUsers = const [],
this.startDate,
required this.updatedAt,
});
@@ -84,9 +83,6 @@ class AlbumResponseDto {
bool shared;
/// This property was deprecated in v1.102.0
List<UserResponseDto> sharedUsers;
///
/// 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
@@ -115,7 +111,6 @@ class AlbumResponseDto {
other.owner == owner &&
other.ownerId == ownerId &&
other.shared == shared &&
_deepEquality.equals(other.sharedUsers, sharedUsers) &&
other.startDate == startDate &&
other.updatedAt == updatedAt;
@@ -138,12 +133,11 @@ class AlbumResponseDto {
(owner.hashCode) +
(ownerId.hashCode) +
(shared.hashCode) +
(sharedUsers.hashCode) +
(startDate == null ? 0 : startDate!.hashCode) +
(updatedAt.hashCode);
@override
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]';
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -179,7 +173,6 @@ class AlbumResponseDto {
json[r'owner'] = this.owner;
json[r'ownerId'] = this.ownerId;
json[r'shared'] = this.shared;
json[r'sharedUsers'] = this.sharedUsers;
if (this.startDate != null) {
json[r'startDate'] = this.startDate!.toUtc().toIso8601String();
} else {
@@ -213,7 +206,6 @@ class AlbumResponseDto {
owner: UserResponseDto.fromJson(json[r'owner'])!,
ownerId: mapValueOfType<String>(json, r'ownerId')!,
shared: mapValueOfType<bool>(json, r'shared')!,
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers']),
startDate: mapDateTime(json, r'startDate', r''),
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
);
@@ -276,7 +268,6 @@ class AlbumResponseDto {
'owner',
'ownerId',
'shared',
'sharedUsers',
'updatedAt',
};
}

View File

@@ -14,6 +14,7 @@ class AssetBulkUpdateDto {
/// Returns a new [AssetBulkUpdateDto] instance.
AssetBulkUpdateDto({
this.dateTimeOriginal,
this.duplicateId,
this.ids = const [],
this.isArchived,
this.isFavorite,
@@ -31,6 +32,8 @@ class AssetBulkUpdateDto {
///
String? dateTimeOriginal;
String? duplicateId;
List<String> ids;
///
@@ -84,6 +87,7 @@ class AssetBulkUpdateDto {
@override
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
other.dateTimeOriginal == dateTimeOriginal &&
other.duplicateId == duplicateId &&
_deepEquality.equals(other.ids, ids) &&
other.isArchived == isArchived &&
other.isFavorite == isFavorite &&
@@ -96,6 +100,7 @@ class AssetBulkUpdateDto {
int get hashCode =>
// ignore: unnecessary_parenthesis
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
(duplicateId == null ? 0 : duplicateId!.hashCode) +
(ids.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
@@ -105,7 +110,7 @@ class AssetBulkUpdateDto {
(stackParentId == null ? 0 : stackParentId!.hashCode);
@override
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, removeParent=$removeParent, stackParentId=$stackParentId]';
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, removeParent=$removeParent, stackParentId=$stackParentId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -113,6 +118,11 @@ class AssetBulkUpdateDto {
json[r'dateTimeOriginal'] = this.dateTimeOriginal;
} else {
// json[r'dateTimeOriginal'] = null;
}
if (this.duplicateId != null) {
json[r'duplicateId'] = this.duplicateId;
} else {
// json[r'duplicateId'] = null;
}
json[r'ids'] = this.ids;
if (this.isArchived != null) {
@@ -157,6 +167,7 @@ class AssetBulkUpdateDto {
return AssetBulkUpdateDto(
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
ids: json[r'ids'] is Iterable
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [],

View File

@@ -0,0 +1,106 @@
//
// 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 AssetMediaResponseDto {
/// Returns a new [AssetMediaResponseDto] instance.
AssetMediaResponseDto({
required this.id,
required this.status,
});
String id;
AssetMediaStatus status;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetMediaResponseDto &&
other.id == id &&
other.status == status;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(status.hashCode);
@override
String toString() => 'AssetMediaResponseDto[id=$id, status=$status]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'id'] = this.id;
json[r'status'] = this.status;
return json;
}
/// Returns a new [AssetMediaResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetMediaResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetMediaResponseDto(
id: mapValueOfType<String>(json, r'id')!,
status: AssetMediaStatus.fromJson(json[r'status'])!,
);
}
return null;
}
static List<AssetMediaResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetMediaResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetMediaResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetMediaResponseDto> mapFromJson(dynamic json) {
final map = <String, AssetMediaResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetMediaResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetMediaResponseDto-objects as value to a dart map
static Map<String, List<AssetMediaResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetMediaResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetMediaResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'id',
'status',
};
}

View File

@@ -0,0 +1,85 @@
//
// 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 AssetMediaStatus {
/// Instantiate a new enum with the provided [value].
const AssetMediaStatus._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const replaced = AssetMediaStatus._(r'replaced');
static const duplicate = AssetMediaStatus._(r'duplicate');
/// List of all possible values in this [enum][AssetMediaStatus].
static const values = <AssetMediaStatus>[
replaced,
duplicate,
];
static AssetMediaStatus? fromJson(dynamic value) => AssetMediaStatusTypeTransformer().decode(value);
static List<AssetMediaStatus> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetMediaStatus>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetMediaStatus.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [AssetMediaStatus] to String,
/// and [decode] dynamic data back to [AssetMediaStatus].
class AssetMediaStatusTypeTransformer {
factory AssetMediaStatusTypeTransformer() => _instance ??= const AssetMediaStatusTypeTransformer._();
const AssetMediaStatusTypeTransformer._();
String encode(AssetMediaStatus data) => data.value;
/// Decodes a [dynamic value][data] to a AssetMediaStatus.
///
/// 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.
AssetMediaStatus? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'replaced': return AssetMediaStatus.replaced;
case r'duplicate': return AssetMediaStatus.duplicate;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [AssetMediaStatusTypeTransformer] instance.
static AssetMediaStatusTypeTransformer? _instance;
}

View File

@@ -24,10 +24,8 @@ class AssetResponseDto {
required this.hasMetadata,
required this.id,
required this.isArchived,
this.isExternal,
required this.isFavorite,
required this.isOffline,
this.isReadOnly,
required this.isTrashed,
this.libraryId,
this.livePhotoVideoId,
@@ -77,28 +75,10 @@ class AssetResponseDto {
bool isArchived;
/// This property was deprecated in v1.104.0
///
/// 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? isExternal;
bool isFavorite;
bool isOffline;
/// This property was deprecated in v1.104.0
///
/// 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? isReadOnly;
bool isTrashed;
/// This property was deprecated in v1.106.0
@@ -161,10 +141,8 @@ class AssetResponseDto {
other.hasMetadata == hasMetadata &&
other.id == id &&
other.isArchived == isArchived &&
other.isExternal == isExternal &&
other.isFavorite == isFavorite &&
other.isOffline == isOffline &&
other.isReadOnly == isReadOnly &&
other.isTrashed == isTrashed &&
other.libraryId == libraryId &&
other.livePhotoVideoId == livePhotoVideoId &&
@@ -198,10 +176,8 @@ class AssetResponseDto {
(hasMetadata.hashCode) +
(id.hashCode) +
(isArchived.hashCode) +
(isExternal == null ? 0 : isExternal!.hashCode) +
(isFavorite.hashCode) +
(isOffline.hashCode) +
(isReadOnly == null ? 0 : isReadOnly!.hashCode) +
(isTrashed.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
@@ -222,7 +198,7 @@ class AssetResponseDto {
(updatedAt.hashCode);
@override
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, stackCount=$stackCount, stackParentId=$stackParentId, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, stackCount=$stackCount, stackParentId=$stackParentId, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -245,18 +221,8 @@ class AssetResponseDto {
json[r'hasMetadata'] = this.hasMetadata;
json[r'id'] = this.id;
json[r'isArchived'] = this.isArchived;
if (this.isExternal != null) {
json[r'isExternal'] = this.isExternal;
} else {
// json[r'isExternal'] = null;
}
json[r'isFavorite'] = this.isFavorite;
json[r'isOffline'] = this.isOffline;
if (this.isReadOnly != null) {
json[r'isReadOnly'] = this.isReadOnly;
} else {
// json[r'isReadOnly'] = null;
}
json[r'isTrashed'] = this.isTrashed;
if (this.libraryId != null) {
json[r'libraryId'] = this.libraryId;
@@ -325,10 +291,8 @@ class AssetResponseDto {
hasMetadata: mapValueOfType<bool>(json, r'hasMetadata')!,
id: mapValueOfType<String>(json, r'id')!,
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
isExternal: mapValueOfType<bool>(json, r'isExternal'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly'),
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
libraryId: mapValueOfType<String>(json, r'libraryId'),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),

View File

@@ -17,12 +17,10 @@ class CreateAlbumDto {
this.albumUsers = const [],
this.assetIds = const [],
this.description,
this.sharedWithUserIds = const [],
});
String albumName;
/// This property was added in v1.104.0
List<AlbumUserCreateDto> albumUsers;
List<String> assetIds;
@@ -35,16 +33,12 @@ class CreateAlbumDto {
///
String? description;
/// This property was deprecated in v1.104.0
List<String> sharedWithUserIds;
@override
bool operator ==(Object other) => identical(this, other) || other is CreateAlbumDto &&
other.albumName == albumName &&
_deepEquality.equals(other.albumUsers, albumUsers) &&
_deepEquality.equals(other.assetIds, assetIds) &&
other.description == description &&
_deepEquality.equals(other.sharedWithUserIds, sharedWithUserIds);
other.description == description;
@override
int get hashCode =>
@@ -52,11 +46,10 @@ class CreateAlbumDto {
(albumName.hashCode) +
(albumUsers.hashCode) +
(assetIds.hashCode) +
(description == null ? 0 : description!.hashCode) +
(sharedWithUserIds.hashCode);
(description == null ? 0 : description!.hashCode);
@override
String toString() => 'CreateAlbumDto[albumName=$albumName, albumUsers=$albumUsers, assetIds=$assetIds, description=$description, sharedWithUserIds=$sharedWithUserIds]';
String toString() => 'CreateAlbumDto[albumName=$albumName, albumUsers=$albumUsers, assetIds=$assetIds, description=$description]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -68,7 +61,6 @@ class CreateAlbumDto {
} else {
// json[r'description'] = null;
}
json[r'sharedWithUserIds'] = this.sharedWithUserIds;
return json;
}
@@ -86,9 +78,6 @@ class CreateAlbumDto {
? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
description: mapValueOfType<String>(json, r'description'),
sharedWithUserIds: json[r'sharedWithUserIds'] is Iterable
? (json[r'sharedWithUserIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;

View File

@@ -14,37 +14,30 @@ class MemoryLaneResponseDto {
/// Returns a new [MemoryLaneResponseDto] instance.
MemoryLaneResponseDto({
this.assets = const [],
required this.title,
required this.yearsAgo,
});
List<AssetResponseDto> assets;
/// This property was deprecated in v1.100.0
String title;
int yearsAgo;
@override
bool operator ==(Object other) => identical(this, other) || other is MemoryLaneResponseDto &&
_deepEquality.equals(other.assets, assets) &&
other.title == title &&
other.yearsAgo == yearsAgo;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assets.hashCode) +
(title.hashCode) +
(yearsAgo.hashCode);
@override
String toString() => 'MemoryLaneResponseDto[assets=$assets, title=$title, yearsAgo=$yearsAgo]';
String toString() => 'MemoryLaneResponseDto[assets=$assets, yearsAgo=$yearsAgo]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assets'] = this.assets;
json[r'title'] = this.title;
json[r'yearsAgo'] = this.yearsAgo;
return json;
}
@@ -58,7 +51,6 @@ class MemoryLaneResponseDto {
return MemoryLaneResponseDto(
assets: AssetResponseDto.listFromJson(json[r'assets']),
title: mapValueOfType<String>(json, r'title')!,
yearsAgo: mapValueOfType<int>(json, r'yearsAgo')!,
);
}
@@ -108,7 +100,6 @@ class MemoryLaneResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assets',
'title',
'yearsAgo',
};
}

View File

@@ -39,7 +39,6 @@ class MetadataSearchDto {
this.page,
this.personIds = const [],
this.previewPath,
this.resizePath,
this.size,
this.state,
this.takenAfter,
@@ -50,7 +49,6 @@ class MetadataSearchDto {
this.type,
this.updatedAfter,
this.updatedBefore,
this.webpPath,
this.withArchived = false,
this.withDeleted,
this.withExif,
@@ -261,15 +259,6 @@ class MetadataSearchDto {
///
String? previewPath;
/// This property was deprecated in v1.100.0
///
/// 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? resizePath;
/// Minimum value: 1
/// Maximum value: 1000
///
@@ -352,15 +341,6 @@ class MetadataSearchDto {
///
DateTime? updatedBefore;
/// This property was deprecated in v1.100.0
///
/// 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? webpPath;
bool withArchived;
///
@@ -423,7 +403,6 @@ class MetadataSearchDto {
other.page == page &&
_deepEquality.equals(other.personIds, personIds) &&
other.previewPath == previewPath &&
other.resizePath == resizePath &&
other.size == size &&
other.state == state &&
other.takenAfter == takenAfter &&
@@ -434,7 +413,6 @@ class MetadataSearchDto {
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.webpPath == webpPath &&
other.withArchived == withArchived &&
other.withDeleted == withDeleted &&
other.withExif == withExif &&
@@ -470,7 +448,6 @@ class MetadataSearchDto {
(page == null ? 0 : page!.hashCode) +
(personIds.hashCode) +
(previewPath == null ? 0 : previewPath!.hashCode) +
(resizePath == null ? 0 : resizePath!.hashCode) +
(size == null ? 0 : size!.hashCode) +
(state == null ? 0 : state!.hashCode) +
(takenAfter == null ? 0 : takenAfter!.hashCode) +
@@ -481,7 +458,6 @@ class MetadataSearchDto {
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(webpPath == null ? 0 : webpPath!.hashCode) +
(withArchived.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) +
@@ -489,7 +465,7 @@ class MetadataSearchDto {
(withStacked == null ? 0 : withStacked!.hashCode);
@override
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -619,11 +595,6 @@ class MetadataSearchDto {
} else {
// json[r'previewPath'] = null;
}
if (this.resizePath != null) {
json[r'resizePath'] = this.resizePath;
} else {
// json[r'resizePath'] = null;
}
if (this.size != null) {
json[r'size'] = this.size;
} else {
@@ -673,11 +644,6 @@ class MetadataSearchDto {
json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String();
} else {
// json[r'updatedBefore'] = null;
}
if (this.webpPath != null) {
json[r'webpPath'] = this.webpPath;
} else {
// json[r'webpPath'] = null;
}
json[r'withArchived'] = this.withArchived;
if (this.withDeleted != null) {
@@ -739,7 +705,6 @@ class MetadataSearchDto {
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
previewPath: mapValueOfType<String>(json, r'previewPath'),
resizePath: mapValueOfType<String>(json, r'resizePath'),
size: num.parse('${json[r'size']}'),
state: mapValueOfType<String>(json, r'state'),
takenAfter: mapDateTime(json, r'takenAfter', r''),
@@ -750,7 +715,6 @@ class MetadataSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
webpPath: mapValueOfType<String>(json, r'webpPath'),
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false,
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'),

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class ServerInfoResponseDto {
/// Returns a new [ServerInfoResponseDto] instance.
ServerInfoResponseDto({
class ServerStorageResponseDto {
/// Returns a new [ServerStorageResponseDto] instance.
ServerStorageResponseDto({
required this.diskAvailable,
required this.diskAvailableRaw,
required this.diskSize,
@@ -37,7 +37,7 @@ class ServerInfoResponseDto {
int diskUseRaw;
@override
bool operator ==(Object other) => identical(this, other) || other is ServerInfoResponseDto &&
bool operator ==(Object other) => identical(this, other) || other is ServerStorageResponseDto &&
other.diskAvailable == diskAvailable &&
other.diskAvailableRaw == diskAvailableRaw &&
other.diskSize == diskSize &&
@@ -58,7 +58,7 @@ class ServerInfoResponseDto {
(diskUseRaw.hashCode);
@override
String toString() => 'ServerInfoResponseDto[diskAvailable=$diskAvailable, diskAvailableRaw=$diskAvailableRaw, diskSize=$diskSize, diskSizeRaw=$diskSizeRaw, diskUsagePercentage=$diskUsagePercentage, diskUse=$diskUse, diskUseRaw=$diskUseRaw]';
String toString() => 'ServerStorageResponseDto[diskAvailable=$diskAvailable, diskAvailableRaw=$diskAvailableRaw, diskSize=$diskSize, diskSizeRaw=$diskSizeRaw, diskUsagePercentage=$diskUsagePercentage, diskUse=$diskUse, diskUseRaw=$diskUseRaw]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -72,14 +72,14 @@ class ServerInfoResponseDto {
return json;
}
/// Returns a new [ServerInfoResponseDto] instance and imports its values from
/// Returns a new [ServerStorageResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ServerInfoResponseDto? fromJson(dynamic value) {
static ServerStorageResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return ServerInfoResponseDto(
return ServerStorageResponseDto(
diskAvailable: mapValueOfType<String>(json, r'diskAvailable')!,
diskAvailableRaw: mapValueOfType<int>(json, r'diskAvailableRaw')!,
diskSize: mapValueOfType<String>(json, r'diskSize')!,
@@ -92,11 +92,11 @@ class ServerInfoResponseDto {
return null;
}
static List<ServerInfoResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ServerInfoResponseDto>[];
static List<ServerStorageResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ServerStorageResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ServerInfoResponseDto.fromJson(row);
final value = ServerStorageResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -105,12 +105,12 @@ class ServerInfoResponseDto {
return result.toList(growable: growable);
}
static Map<String, ServerInfoResponseDto> mapFromJson(dynamic json) {
final map = <String, ServerInfoResponseDto>{};
static Map<String, ServerStorageResponseDto> mapFromJson(dynamic json) {
final map = <String, ServerStorageResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ServerInfoResponseDto.fromJson(entry.value);
final value = ServerStorageResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -119,14 +119,14 @@ class ServerInfoResponseDto {
return map;
}
// maps a json object with a list of ServerInfoResponseDto-objects as value to a dart map
static Map<String, List<ServerInfoResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ServerInfoResponseDto>>{};
// maps a json object with a list of ServerStorageResponseDto-objects as value to a dart map
static Map<String, List<ServerStorageResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ServerStorageResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ServerInfoResponseDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = ServerStorageResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

@@ -24,7 +24,8 @@ function typescript {
npm --prefix typescript-sdk ci && npm --prefix typescript-sdk run build
}
node ./bin/sync-spec-version.js
# requires server to be built
npm run sync:open-api --prefix=../server
if [[ $1 == 'dart' ]]; then
dart

View File

@@ -1,9 +0,0 @@
const spec = require('../immich-openapi-specs.json');
const pkg = require('../../server/package.json');
const path = require('path');
const fs = require('fs');
spec.info.version = pkg.version;
fs.writeFileSync(
path.join(__dirname, '../immich-openapi-specs.json'),
JSON.stringify(spec, null, 2)
);

File diff suppressed because it is too large Load Diff

View File

@@ -13,15 +13,14 @@ npm i --save @immich/sdk
For a more detailed example, check out the [`@immich/cli`](https://github.com/immich-app/immich/tree/main/cli).
```typescript
import { getAllAlbums, getAllAssets, getMyUserInfo, init } from "@immich/sdk";
import { getAllAlbums, getMyUserInfo, init } from "@immich/sdk";
const API_KEY = "<API_KEY>"; // process.env.IMMICH_API_KEY
init({ baseUrl: "https://demo.immich.app/api", apiKey: API_KEY });
const user = await getMyUserInfo();
const assets = await getAllAssets({ take: 1000 });
const albums = await getAllAlbums({});
console.log({ user, assets, albums });
console.log({ user, albums });
```

View File

@@ -123,12 +123,8 @@ export type AssetResponseDto = {
hasMetadata: boolean;
id: string;
isArchived: boolean;
/** This property was deprecated in v1.104.0 */
isExternal?: boolean;
isFavorite: boolean;
isOffline: boolean;
/** This property was deprecated in v1.104.0 */
isReadOnly?: boolean;
isTrashed: boolean;
/** This property was deprecated in v1.106.0 */
libraryId?: string | null;
@@ -166,8 +162,6 @@ export type AlbumResponseDto = {
owner: UserResponseDto;
ownerId: string;
shared: boolean;
/** This property was deprecated in v1.102.0 */
sharedUsers: UserResponseDto[];
startDate?: string;
updatedAt: string;
};
@@ -177,12 +171,9 @@ export type AlbumUserCreateDto = {
};
export type CreateAlbumDto = {
albumName: string;
/** This property was added in v1.104.0 */
albumUsers?: AlbumUserCreateDto[];
assetIds?: string[];
description?: string;
/** This property was deprecated in v1.104.0 */
sharedWithUserIds?: string[];
};
export type AlbumCountResponseDto = {
notShared: number;
@@ -213,8 +204,6 @@ export type AlbumUserAddDto = {
};
export type AddUsersDto = {
albumUsers: AlbumUserAddDto[];
/** This property was deprecated in v1.102.0 */
sharedUserIds?: string[];
};
export type ApiKeyResponseDto = {
createdAt: string;
@@ -238,6 +227,7 @@ export type AssetBulkDeleteDto = {
};
export type AssetBulkUpdateDto = {
dateTimeOriginal?: string;
duplicateId?: string | null;
ids: string[];
isArchived?: boolean;
isFavorite?: boolean;
@@ -284,8 +274,6 @@ export type MapMarkerResponseDto = {
};
export type MemoryLaneResponseDto = {
assets: AssetResponseDto[];
/** This property was deprecated in v1.100.0 */
title: string;
yearsAgo: number;
};
export type UpdateStackParentDto = {
@@ -323,6 +311,18 @@ export type UpdateAssetDto = {
latitude?: number;
longitude?: number;
};
export type AssetMediaReplaceDto = {
assetData: Blob;
deviceAssetId: string;
deviceId: string;
duration?: string;
fileCreatedAt: string;
fileModifiedAt: string;
};
export type AssetMediaResponseDto = {
id: string;
status: AssetMediaStatus;
};
export type AuditDeletesResponseDto = {
ids: string[];
needsFullSync: boolean;
@@ -647,8 +647,6 @@ export type MetadataSearchDto = {
page?: number;
personIds?: string[];
previewPath?: string;
/** This property was deprecated in v1.100.0 */
resizePath?: string;
size?: number;
state?: string;
takenAfter?: string;
@@ -659,8 +657,6 @@ export type MetadataSearchDto = {
"type"?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
/** This property was deprecated in v1.100.0 */
webpPath?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
@@ -732,15 +728,6 @@ export type SmartSearchDto = {
withDeleted?: boolean;
withExif?: boolean;
};
export type ServerInfoResponseDto = {
diskAvailable: string;
diskAvailableRaw: number;
diskSize: string;
diskSizeRaw: number;
diskUsagePercentage: number;
diskUse: string;
diskUseRaw: number;
};
export type ServerConfigDto = {
externalDomain: string;
isInitialized: boolean;
@@ -788,6 +775,15 @@ export type ServerStatsResponseDto = {
usageByUser: UsageByUserDto[];
videos: number;
};
export type ServerStorageResponseDto = {
diskAvailable: string;
diskAvailableRaw: number;
diskSize: string;
diskSizeRaw: number;
diskUsagePercentage: number;
diskUse: string;
diskUseRaw: number;
};
export type ServerThemeDto = {
customCss: string;
};
@@ -1106,7 +1102,7 @@ export function getActivities({ albumId, assetId, level, $type, userId }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ActivityResponseDto[];
}>(`/activity${QS.query(QS.explode({
}>(`/activities${QS.query(QS.explode({
albumId,
assetId,
level,
@@ -1122,7 +1118,7 @@ export function createActivity({ activityCreateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: ActivityResponseDto;
}>("/activity", oazapfts.json({
}>("/activities", oazapfts.json({
...opts,
method: "POST",
body: activityCreateDto
@@ -1135,7 +1131,7 @@ export function getActivityStatistics({ albumId, assetId }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ActivityStatisticsResponseDto;
}>(`/activity/statistics${QS.query(QS.explode({
}>(`/activities/statistics${QS.query(QS.explode({
albumId,
assetId
}))}`, {
@@ -1145,7 +1141,7 @@ export function getActivityStatistics({ albumId, assetId }: {
export function deleteActivity({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/activity/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/activities/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -1157,7 +1153,7 @@ export function getAllAlbums({ assetId, shared }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AlbumResponseDto[];
}>(`/album${QS.query(QS.explode({
}>(`/albums${QS.query(QS.explode({
assetId,
shared
}))}`, {
@@ -1170,7 +1166,7 @@ export function createAlbum({ createAlbumDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: AlbumResponseDto;
}>("/album", oazapfts.json({
}>("/albums", oazapfts.json({
...opts,
method: "POST",
body: createAlbumDto
@@ -1180,14 +1176,14 @@ export function getAlbumCount(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AlbumCountResponseDto;
}>("/album/count", {
}>("/albums/count", {
...opts
}));
}
export function deleteAlbum({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/album/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -1200,7 +1196,7 @@ export function getAlbumInfo({ id, key, withoutAssets }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AlbumResponseDto;
}>(`/album/${encodeURIComponent(id)}${QS.query(QS.explode({
}>(`/albums/${encodeURIComponent(id)}${QS.query(QS.explode({
key,
withoutAssets
}))}`, {
@@ -1214,7 +1210,7 @@ export function updateAlbumInfo({ id, updateAlbumDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AlbumResponseDto;
}>(`/album/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/albums/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PATCH",
body: updateAlbumDto
@@ -1227,7 +1223,7 @@ export function removeAssetFromAlbum({ id, bulkIdsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: BulkIdResponseDto[];
}>(`/album/${encodeURIComponent(id)}/assets`, oazapfts.json({
}>(`/albums/${encodeURIComponent(id)}/assets`, oazapfts.json({
...opts,
method: "DELETE",
body: bulkIdsDto
@@ -1241,7 +1237,7 @@ export function addAssetsToAlbum({ id, key, bulkIdsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: BulkIdResponseDto[];
}>(`/album/${encodeURIComponent(id)}/assets${QS.query(QS.explode({
}>(`/albums/${encodeURIComponent(id)}/assets${QS.query(QS.explode({
key
}))}`, oazapfts.json({
...opts,
@@ -1253,7 +1249,7 @@ export function removeUserFromAlbum({ id, userId }: {
id: string;
userId: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/album/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, {
return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, {
...opts,
method: "DELETE"
}));
@@ -1263,7 +1259,7 @@ export function updateAlbumUser({ id, userId, updateAlbumUserDto }: {
userId: string;
updateAlbumUserDto: UpdateAlbumUserDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/album/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, oazapfts.json({
return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, oazapfts.json({
...opts,
method: "PUT",
body: updateAlbumUserDto
@@ -1276,7 +1272,7 @@ export function addUsersToAlbum({ id, addUsersDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AlbumResponseDto;
}>(`/album/${encodeURIComponent(id)}/users`, oazapfts.json({
}>(`/albums/${encodeURIComponent(id)}/users`, oazapfts.json({
...opts,
method: "PUT",
body: addUsersDto
@@ -1286,7 +1282,7 @@ export function getApiKeys(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ApiKeyResponseDto[];
}>("/api-key", {
}>("/api-keys", {
...opts
}));
}
@@ -1296,7 +1292,7 @@ export function createApiKey({ apiKeyCreateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: ApiKeyCreateResponseDto;
}>("/api-key", oazapfts.json({
}>("/api-keys", oazapfts.json({
...opts,
method: "POST",
body: apiKeyCreateDto
@@ -1305,7 +1301,7 @@ export function createApiKey({ apiKeyCreateDto }: {
export function deleteApiKey({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/api-key/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/api-keys/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -1316,7 +1312,7 @@ export function getApiKey({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ApiKeyResponseDto;
}>(`/api-key/${encodeURIComponent(id)}`, {
}>(`/api-keys/${encodeURIComponent(id)}`, {
...opts
}));
}
@@ -1327,7 +1323,7 @@ export function updateApiKey({ id, apiKeyUpdateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ApiKeyResponseDto;
}>(`/api-key/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/api-keys/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: apiKeyUpdateDto
@@ -1342,37 +1338,6 @@ export function deleteAssets({ assetBulkDeleteDto }: {
body: assetBulkDeleteDto
})));
}
/**
* Get all AssetEntity belong to the user
*/
export function getAllAssets({ ifNoneMatch, isArchived, isFavorite, skip, take, updatedAfter, updatedBefore, userId }: {
ifNoneMatch?: string;
isArchived?: boolean;
isFavorite?: boolean;
skip?: number;
take?: number;
updatedAfter?: string;
updatedBefore?: string;
userId?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto[];
}>(`/asset${QS.query(QS.explode({
isArchived,
isFavorite,
skip,
take,
updatedAfter,
updatedBefore,
userId
}))}`, {
...opts,
headers: oazapfts.mergeHeaders(opts?.headers, {
"if-none-match": ifNoneMatch
})
}));
}
export function updateAssets({ assetBulkUpdateDto }: {
assetBulkUpdateDto: AssetBulkUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
@@ -1584,6 +1549,25 @@ export function updateAsset({ id, updateAssetDto }: {
body: updateAssetDto
})));
}
/**
* Replace the asset with new file, without changing its id
*/
export function replaceAsset({ id, key, assetMediaReplaceDto }: {
id: string;
key?: string;
assetMediaReplaceDto: AssetMediaReplaceDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetMediaResponseDto;
}>(`/asset/${encodeURIComponent(id)}/file${QS.query(QS.explode({
key
}))}`, oazapfts.multipart({
...opts,
method: "PUT",
body: assetMediaReplaceDto
})));
}
export function getAuditDeletes({ after, entityType, userId }: {
after: string;
entityType: EntityType;
@@ -1712,7 +1696,7 @@ export function getFaces({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetFaceResponseDto[];
}>(`/face${QS.query(QS.explode({
}>(`/faces${QS.query(QS.explode({
id
}))}`, {
...opts
@@ -1725,7 +1709,7 @@ export function reassignFacesById({ id, faceDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PersonResponseDto;
}>(`/face/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/faces/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: faceDto
@@ -1756,7 +1740,7 @@ export function getAllLibraries(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: LibraryResponseDto[];
}>("/library", {
}>("/libraries", {
...opts
}));
}
@@ -1766,7 +1750,7 @@ export function createLibrary({ createLibraryDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: LibraryResponseDto;
}>("/library", oazapfts.json({
}>("/libraries", oazapfts.json({
...opts,
method: "POST",
body: createLibraryDto
@@ -1775,7 +1759,7 @@ export function createLibrary({ createLibraryDto }: {
export function deleteLibrary({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/library/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/libraries/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -1786,7 +1770,7 @@ export function getLibrary({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: LibraryResponseDto;
}>(`/library/${encodeURIComponent(id)}`, {
}>(`/libraries/${encodeURIComponent(id)}`, {
...opts
}));
}
@@ -1797,7 +1781,7 @@ export function updateLibrary({ id, updateLibraryDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: LibraryResponseDto;
}>(`/library/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/libraries/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: updateLibraryDto
@@ -1806,7 +1790,7 @@ export function updateLibrary({ id, updateLibraryDto }: {
export function removeOfflineFiles({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/library/${encodeURIComponent(id)}/removeOffline`, {
return oazapfts.ok(oazapfts.fetchText(`/libraries/${encodeURIComponent(id)}/removeOffline`, {
...opts,
method: "POST"
}));
@@ -1815,7 +1799,7 @@ export function scanLibrary({ id, scanLibraryDto }: {
id: string;
scanLibraryDto: ScanLibraryDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/library/${encodeURIComponent(id)}/scan`, oazapfts.json({
return oazapfts.ok(oazapfts.fetchText(`/libraries/${encodeURIComponent(id)}/scan`, oazapfts.json({
...opts,
method: "POST",
body: scanLibraryDto
@@ -1827,7 +1811,7 @@ export function getLibraryStatistics({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: LibraryStatsResponseDto;
}>(`/library/${encodeURIComponent(id)}/statistics`, {
}>(`/libraries/${encodeURIComponent(id)}/statistics`, {
...opts
}));
}
@@ -1838,7 +1822,7 @@ export function validate({ id, validateLibraryDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ValidateLibraryResponseDto;
}>(`/library/${encodeURIComponent(id)}/validate`, oazapfts.json({
}>(`/libraries/${encodeURIComponent(id)}/validate`, oazapfts.json({
...opts,
method: "POST",
body: validateLibraryDto
@@ -1977,7 +1961,7 @@ export function getPartners({ direction }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PartnerResponseDto[];
}>(`/partner${QS.query(QS.explode({
}>(`/partners${QS.query(QS.explode({
direction
}))}`, {
...opts
@@ -1986,7 +1970,7 @@ export function getPartners({ direction }: {
export function removePartner({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/partner/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/partners/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -1997,7 +1981,7 @@ export function createPartner({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: PartnerResponseDto;
}>(`/partner/${encodeURIComponent(id)}`, {
}>(`/partners/${encodeURIComponent(id)}`, {
...opts,
method: "POST"
}));
@@ -2009,7 +1993,7 @@ export function updatePartner({ id, updatePartnerDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PartnerResponseDto;
}>(`/partner/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/partners/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: updatePartnerDto
@@ -2021,7 +2005,7 @@ export function getAllPeople({ withHidden }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PeopleResponseDto;
}>(`/person${QS.query(QS.explode({
}>(`/people${QS.query(QS.explode({
withHidden
}))}`, {
...opts
@@ -2033,7 +2017,7 @@ export function createPerson({ personCreateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: PersonResponseDto;
}>("/person", oazapfts.json({
}>("/people", oazapfts.json({
...opts,
method: "POST",
body: personCreateDto
@@ -2045,7 +2029,7 @@ export function updatePeople({ peopleUpdateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: BulkIdResponseDto[];
}>("/person", oazapfts.json({
}>("/people", oazapfts.json({
...opts,
method: "PUT",
body: peopleUpdateDto
@@ -2057,7 +2041,7 @@ export function getPerson({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PersonResponseDto;
}>(`/person/${encodeURIComponent(id)}`, {
}>(`/people/${encodeURIComponent(id)}`, {
...opts
}));
}
@@ -2068,7 +2052,7 @@ export function updatePerson({ id, personUpdateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PersonResponseDto;
}>(`/person/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/people/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: personUpdateDto
@@ -2080,7 +2064,7 @@ export function getPersonAssets({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto[];
}>(`/person/${encodeURIComponent(id)}/assets`, {
}>(`/people/${encodeURIComponent(id)}/assets`, {
...opts
}));
}
@@ -2091,7 +2075,7 @@ export function mergePerson({ id, mergePersonDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: BulkIdResponseDto[];
}>(`/person/${encodeURIComponent(id)}/merge`, oazapfts.json({
}>(`/people/${encodeURIComponent(id)}/merge`, oazapfts.json({
...opts,
method: "POST",
body: mergePersonDto
@@ -2104,7 +2088,7 @@ export function reassignFaces({ id, assetFaceUpdateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PersonResponseDto[];
}>(`/person/${encodeURIComponent(id)}/reassign`, oazapfts.json({
}>(`/people/${encodeURIComponent(id)}/reassign`, oazapfts.json({
...opts,
method: "PUT",
body: assetFaceUpdateDto
@@ -2116,7 +2100,7 @@ export function getPersonStatistics({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PersonStatisticsResponseDto;
}>(`/person/${encodeURIComponent(id)}/statistics`, {
}>(`/people/${encodeURIComponent(id)}/statistics`, {
...opts
}));
}
@@ -2126,7 +2110,7 @@ export function getPersonThumbnail({ id }: {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/person/${encodeURIComponent(id)}/thumbnail`, {
}>(`/people/${encodeURIComponent(id)}/thumbnail`, {
...opts
}));
}
@@ -2134,7 +2118,7 @@ export function getAuditFiles(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: FileReportDto;
}>("/report", {
}>("/reports", {
...opts
}));
}
@@ -2144,7 +2128,7 @@ export function getFileChecksums({ fileChecksumDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: FileChecksumResponseDto[];
}>("/report/checksum", oazapfts.json({
}>("/reports/checksum", oazapfts.json({
...opts,
method: "POST",
body: fileChecksumDto
@@ -2153,7 +2137,7 @@ export function getFileChecksums({ fileChecksumDto }: {
export function fixAuditFiles({ fileReportFixDto }: {
fileReportFixDto: FileReportFixDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/report/fix", oazapfts.json({
return oazapfts.ok(oazapfts.fetchText("/reports/fix", oazapfts.json({
...opts,
method: "POST",
body: fileReportFixDto
@@ -2245,14 +2229,6 @@ export function getSearchSuggestions({ country, make, model, state, $type }: {
...opts
}));
}
export function getServerInfo(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ServerInfoResponseDto;
}>("/server-info", {
...opts
}));
}
export function getServerConfig(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
@@ -2293,6 +2269,14 @@ export function getServerStatistics(opts?: Oazapfts.RequestOpts) {
...opts
}));
}
export function getStorage(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: ServerStorageResponseDto;
}>("/server-info/storage", {
...opts
}));
}
export function getTheme(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
@@ -2335,7 +2319,7 @@ export function getAllSharedLinks(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: SharedLinkResponseDto[];
}>("/shared-link", {
}>("/shared-links", {
...opts
}));
}
@@ -2345,7 +2329,7 @@ export function createSharedLink({ sharedLinkCreateDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: SharedLinkResponseDto;
}>("/shared-link", oazapfts.json({
}>("/shared-links", oazapfts.json({
...opts,
method: "POST",
body: sharedLinkCreateDto
@@ -2359,7 +2343,7 @@ export function getMySharedLink({ key, password, token }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: SharedLinkResponseDto;
}>(`/shared-link/me${QS.query(QS.explode({
}>(`/shared-links/me${QS.query(QS.explode({
key,
password,
token
@@ -2370,7 +2354,7 @@ export function getMySharedLink({ key, password, token }: {
export function removeSharedLink({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/shared-link/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/shared-links/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -2381,7 +2365,7 @@ export function getSharedLinkById({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: SharedLinkResponseDto;
}>(`/shared-link/${encodeURIComponent(id)}`, {
}>(`/shared-links/${encodeURIComponent(id)}`, {
...opts
}));
}
@@ -2392,7 +2376,7 @@ export function updateSharedLink({ id, sharedLinkEditDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: SharedLinkResponseDto;
}>(`/shared-link/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/shared-links/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PATCH",
body: sharedLinkEditDto
@@ -2406,7 +2390,7 @@ export function removeSharedLinkAssets({ id, key, assetIdsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetIdsResponseDto[];
}>(`/shared-link/${encodeURIComponent(id)}/assets${QS.query(QS.explode({
}>(`/shared-links/${encodeURIComponent(id)}/assets${QS.query(QS.explode({
key
}))}`, oazapfts.json({
...opts,
@@ -2422,7 +2406,7 @@ export function addSharedLinkAssets({ id, key, assetIdsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetIdsResponseDto[];
}>(`/shared-link/${encodeURIComponent(id)}/assets${QS.query(QS.explode({
}>(`/shared-links/${encodeURIComponent(id)}/assets${QS.query(QS.explode({
key
}))}`, oazapfts.json({
...opts,
@@ -2533,7 +2517,7 @@ export function getAllTags(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TagResponseDto[];
}>("/tag", {
}>("/tags", {
...opts
}));
}
@@ -2543,7 +2527,7 @@ export function createTag({ createTagDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: TagResponseDto;
}>("/tag", oazapfts.json({
}>("/tags", oazapfts.json({
...opts,
method: "POST",
body: createTagDto
@@ -2552,7 +2536,7 @@ export function createTag({ createTagDto }: {
export function deleteTag({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/tag/${encodeURIComponent(id)}`, {
return oazapfts.ok(oazapfts.fetchText(`/tags/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
@@ -2563,7 +2547,7 @@ export function getTagById({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TagResponseDto;
}>(`/tag/${encodeURIComponent(id)}`, {
}>(`/tags/${encodeURIComponent(id)}`, {
...opts
}));
}
@@ -2574,7 +2558,7 @@ export function updateTag({ id, updateTagDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TagResponseDto;
}>(`/tag/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/tags/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PATCH",
body: updateTagDto
@@ -2587,7 +2571,7 @@ export function untagAssets({ id, assetIdsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetIdsResponseDto[];
}>(`/tag/${encodeURIComponent(id)}/assets`, oazapfts.json({
}>(`/tags/${encodeURIComponent(id)}/assets`, oazapfts.json({
...opts,
method: "DELETE",
body: assetIdsDto
@@ -2599,7 +2583,7 @@ export function getTagAssets({ id }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto[];
}>(`/tag/${encodeURIComponent(id)}/assets`, {
}>(`/tags/${encodeURIComponent(id)}/assets`, {
...opts
}));
}
@@ -2610,7 +2594,7 @@ export function tagAssets({ id, assetIdsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetIdsResponseDto[];
}>(`/tag/${encodeURIComponent(id)}/assets`, oazapfts.json({
}>(`/tags/${encodeURIComponent(id)}/assets`, oazapfts.json({
...opts,
method: "PUT",
body: assetIdsDto
@@ -2709,7 +2693,7 @@ export function getAllUsers({ isAll }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: UserResponseDto[];
}>(`/user${QS.query(QS.explode({
}>(`/users${QS.query(QS.explode({
isAll
}))}`, {
...opts
@@ -2721,7 +2705,7 @@ export function createUser({ createUserDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: UserResponseDto;
}>("/user", oazapfts.json({
}>("/users", oazapfts.json({
...opts,
method: "POST",
body: createUserDto
@@ -2733,32 +2717,22 @@ export function updateUser({ updateUserDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: UserResponseDto;
}>("/user", oazapfts.json({
}>("/users", oazapfts.json({
...opts,
method: "PUT",
body: updateUserDto
})));
}
export function getUserById({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: UserResponseDto;
}>(`/user/info/${encodeURIComponent(id)}`, {
...opts
}));
}
export function getMyUserInfo(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: UserResponseDto;
}>("/user/me", {
}>("/users/me", {
...opts
}));
}
export function deleteProfileImage(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/user/profile-image", {
return oazapfts.ok(oazapfts.fetchText("/users/profile-image", {
...opts,
method: "DELETE"
}));
@@ -2769,22 +2743,12 @@ export function createProfileImage({ createProfileImageDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: CreateProfileImageResponseDto;
}>("/user/profile-image", oazapfts.multipart({
}>("/users/profile-image", oazapfts.multipart({
...opts,
method: "POST",
body: createProfileImageDto
})));
}
export function getProfileImage({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/user/profile-image/${encodeURIComponent(id)}`, {
...opts
}));
}
export function deleteUser({ id, deleteUserDto }: {
id: string;
deleteUserDto: DeleteUserDto;
@@ -2792,19 +2756,39 @@ export function deleteUser({ id, deleteUserDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: UserResponseDto;
}>(`/user/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/users/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "DELETE",
body: deleteUserDto
})));
}
export function getUserById({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: UserResponseDto;
}>(`/users/${encodeURIComponent(id)}`, {
...opts
}));
}
export function getProfileImage({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/users/${encodeURIComponent(id)}/profile-image`, {
...opts
}));
}
export function restoreUser({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: UserResponseDto;
}>(`/user/${encodeURIComponent(id)}/restore`, {
}>(`/users/${encodeURIComponent(id)}/restore`, {
...opts,
method: "POST"
}));
@@ -2880,6 +2864,10 @@ export enum ThumbnailFormat {
Jpeg = "JPEG",
Webp = "WEBP"
}
export enum AssetMediaStatus {
Replaced = "replaced",
Duplicate = "duplicate"
}
export enum EntityType {
Asset = "ASSET",
Album = "ALBUM"

View File

@@ -9,12 +9,12 @@
</p>
<p align="center">
<img src="../design/immich-logo-stacked-light.svg" width="300" title="Login With Custom URL">
<img src="/design/immich-logo-stacked-light.svg" width="300" title="Login With Custom URL">
</p>
<h3 align="center">Immich - Высокопроизводительное решение для автономоного создания фото и видео архивов</h3>
<h3 align="center">Высокопроизводительное автономное решение для хранения и группировки фото и видео</h3>
<br/>
<a href="https://immich.app">
<img src="../design/immich-screenshots.png" title="Main Screenshot">
<img src="/design/immich-screenshots.png" title="Main Screenshot">
</a>
<br/>
<p align="center">
@@ -38,9 +38,9 @@
## Предупреждение
- ⚠️ Этот проект находится **в очень активной** разработке.
- ⚠️ Ожидайте ошибок и критических изменение.
- ⚠️ **Не используйте это приложение для бекапа ваших фото и видео.**
- ⚠️ Всегда следуйте [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) плану резервного копирования ваших драгоценных фото и видео!
- ⚠️ Ожидайте множество ошибок и глобальных изменений.
- ⚠️ **Не используйте это приложение как единственное хранилище своих фото и видео.**
- ⚠️ Всегда следуйте [плану резервного копирования «3-2-1»](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/ "Стратегии резервного копирования: Почему стратегия резервного копирования «3-2-1» — лучшая") для ваших драгоценных фотографий и видео!
## Содержание
@@ -49,18 +49,18 @@
- [Демо](#demo)
- [Возможности](#features)
- [Введение](https://immich.app/docs/overview/introduction)
- [Инсталяция](https://immich.app/docs/install/requirements)
- [Гайд по доработке проекта](https://immich.app/docs/overview/support-the-project)
- [Установка](https://immich.app/docs/install/requirements)
- [Гид по доработке проекта](https://immich.app/docs/overview/support-the-project)
## Документация
Вы можете найти основную документация, включая инструкции по установке по ссылке https://immich.app/.
Вы можете прочитать инструкции по установке и остальную документацию [здесь](https://immich.app/)
## Демо
Вы можете посмотреть веб демо по ссылке https://demo.immich.app
Вы можете опробовать [демонстрационную версию](https://demo.immich.app/).
Для мобильного приложения вы можете использовать адрес `https://demo.immich.app/api` в поле `Server Endpoint URL`
Для мобильного приложения вы можете использовать адрес `https://demo.immich.app/api` в поле `Server Endpoint URL`.
```bash title="Демо доступ"
Реквизиты доступа
@@ -72,38 +72,56 @@
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
## Активность
![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image")
## Возможности
| Возможности | Приложение | Веб |
| --------------------------------------------------- | ---------- | --- |
| Выгрузка на сервер и просмотр видео и фото | Да | Да |
| Авто бекап когда приложение открыто | Да | Н/Д |
| Выбор альбома(ов) для бекапа | Да | Н/Д |
| загрузка с сервера фото и видео на устройство | Да | Да |
| Загрузка на сервер и просмотр видео и фото | Да | Да |
| Автоматический бекап, когда приложение открыто | Да | Н/Д |
| Предотвращение дупликации данных | Да | Да |
| Выбор альбома (-ов) для бекапа | Да | Н/Д |
| Скачивание с сервера фото и видео на устройство | Да | Да |
| Поддержка нескольких пользователей | Да | Да |
| Альбомы и общие альбомы | Да | Да |
| Прокручиваемая/перетаскиваемая полоса прокрутки | Да | Да |
| Поддержка формата RAW | Да | Да |
| Поддержка raw-форматов | Да | Да |
| Просмотр метаданных (EXIF, map) | Да | Да |
| Поиск до метаданным, объектам, лицам и CLIP | Да | Да |
| Административные функци (управление пользователями) | Нет | Да |
| Фоновый бекпа | Да | Н/Д |
| Административные функции (управление пользователями)| Нет | Да |
| Фоновое резервное копирование | Да | Н/Д |
| Виртуальная прокрутка | Да | Да |
| Поддержка OAuth | Да | Да |
| Ключи API | Н/Д | Да |
| LivePhoto/MotionPhoto бекап и воспроизведение | Да | Да |
| LivePhoto/MotionPhoto воспроизведение и бекап | Да | Да |
| Поддержка отображения изображений 360° | Нет | Да |
| Настраиваемая структура хранилища | Да | Да |
| Публичные альбомы | Нет | Да |
| Архив и Избранное | Да | Да |
| Общий доступ к контенту | Нет | Да |
| Архив и избранное | Да | Да |
| Мировая карта | Да | Да |
| Совместное использование | Да | Да |
| Распознавание лиц и группировка по лицам | Да | Да |
| В этот день (x лет назад) | Да | Да |
| Распознавание и группировка по лицам | Да | Да |
| Воспоминания (в этот день x лет назад) | Да | Да |
| Работа без интернета | Да | Нет |
| Галлереи только для просмотра | Да | Да |
| Колллажи | Да | Да |
| Галереи только для просмотра | Да | Да |
| Коллажи | Да | Да |
## Авторы
<a href="https://github.com/alextran1502/immich/graphs/contributors">
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
</a>
## Star History
<a href="https://star-history.com/#immich-app/immich&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" width="100%" />
</picture>
</a>

View File

@@ -62,3 +62,5 @@ VOLUME /usr/src/app/upload
EXPOSE 3001
ENTRYPOINT ["tini", "--", "/bin/bash"]
CMD ["start.sh"]
HEALTHCHECK CMD npm run healthcheck

View File

@@ -18,6 +18,7 @@
"check": "tsc --noEmit",
"check:code": "npm run format && npm run lint && npm run check",
"check:all": "npm run check:code && npm run test:cov",
"healthcheck": "node ./dist/utils/healthcheck.js",
"test": "vitest",
"test:watch": "vitest --watch",
"test:cov": "vitest --coverage",
@@ -29,7 +30,8 @@
"typeorm:migrations:revert": "typeorm migration:revert -d ./dist/database.config.js",
"typeorm:schema:drop": "typeorm query -d ./dist/database.config.js 'DROP schema public cascade; CREATE schema public;'",
"typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run",
"sql:generate": "node ./dist/utils/sql.js",
"sync:open-api": "node ./dist/bin/sync-open-api.js",
"sync:sql": "node ./dist/bin/sync-sql.js",
"email:dev": "email dev -p 3050 --dir src/emails"
},
"dependencies": {

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env node
process.env.DB_URL = 'postgres://postgres:postgres@localhost:5432/immich';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { ApiModule } from 'src/app.module';
import { useSwagger } from 'src/utils/misc';
const sync = async () => {
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { preview: true });
useSwagger(app, true);
await app.close();
};
sync()
.then(() => {
console.log('Done');
process.exit(0);
})
.catch((error) => {
console.error(error);
console.log('Something went wrong');
process.exit(1);
});

View File

@@ -1,18 +1,18 @@
import { Command, CommandRunner } from 'nest-commander';
import { UserService } from 'src/services/user.service';
import { CliService } from 'src/services/cli.service';
@Command({
name: 'list-users',
description: 'List Immich users',
})
export class ListUsersCommand extends CommandRunner {
constructor(private userService: UserService) {
constructor(private service: CliService) {
super();
}
async run(): Promise<void> {
try {
const users = await this.userService.listUsers();
const users = await this.service.listUsers();
console.dir(users);
} catch (error) {
console.error(error);

View File

@@ -1,19 +1,17 @@
import { Command, CommandRunner } from 'nest-commander';
import { SystemConfigService } from 'src/services/system-config.service';
import { CliService } from 'src/services/cli.service';
@Command({
name: 'enable-oauth-login',
description: 'Enable OAuth login',
})
export class EnableOAuthLogin extends CommandRunner {
constructor(private configService: SystemConfigService) {
constructor(private service: CliService) {
super();
}
async run(): Promise<void> {
const config = await this.configService.getConfig();
config.oauth.enabled = true;
await this.configService.updateConfig(config);
await this.service.enableOAuthLogin();
console.log('OAuth login has been enabled.');
}
}
@@ -23,14 +21,12 @@ export class EnableOAuthLogin extends CommandRunner {
description: 'Disable OAuth login',
})
export class DisableOAuthLogin extends CommandRunner {
constructor(private configService: SystemConfigService) {
constructor(private service: CliService) {
super();
}
async run(): Promise<void> {
const config = await this.configService.getConfig();
config.oauth.enabled = false;
await this.configService.updateConfig(config);
await this.service.disableOAuthLogin();
console.log('OAuth login has been disabled.');
}
}

View File

@@ -1,19 +1,17 @@
import { Command, CommandRunner } from 'nest-commander';
import { SystemConfigService } from 'src/services/system-config.service';
import { CliService } from 'src/services/cli.service';
@Command({
name: 'enable-password-login',
description: 'Enable password login',
})
export class EnablePasswordLoginCommand extends CommandRunner {
constructor(private configService: SystemConfigService) {
constructor(private service: CliService) {
super();
}
async run(): Promise<void> {
const config = await this.configService.getConfig();
config.passwordLogin.enabled = true;
await this.configService.updateConfig(config);
await this.service.enablePasswordLogin();
console.log('Password login has been enabled.');
}
}
@@ -23,14 +21,12 @@ export class EnablePasswordLoginCommand extends CommandRunner {
description: 'Disable password login',
})
export class DisablePasswordLoginCommand extends CommandRunner {
constructor(private configService: SystemConfigService) {
constructor(private service: CliService) {
super();
}
async run(): Promise<void> {
const config = await this.configService.getConfig();
config.passwordLogin.enabled = false;
await this.configService.updateConfig(config);
await this.service.disablePasswordLogin();
console.log('Password login has been disabled.');
}
}

View File

@@ -1,20 +1,9 @@
import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander';
import { UserResponseDto } from 'src/dtos/user.dto';
import { UserService } from 'src/services/user.service';
import { CliService } from 'src/services/cli.service';
@Command({
name: 'reset-admin-password',
description: 'Reset the admin password',
})
export class ResetAdminPasswordCommand extends CommandRunner {
constructor(
private userService: UserService,
private inquirer: InquirerService,
) {
super();
}
ask = (admin: UserResponseDto) => {
const prompt = (inquirer: InquirerService) => {
return function ask(admin: UserResponseDto) {
const { id, oauthId, email, name } = admin;
console.log(`Found Admin:
- ID=${id}
@@ -22,12 +11,25 @@ export class ResetAdminPasswordCommand extends CommandRunner {
- Email=${email}
- Name=${name}`);
return this.inquirer.ask<{ password: string }>('prompt-password', {}).then(({ password }) => password);
return inquirer.ask<{ password: string }>('prompt-password', {}).then(({ password }) => password);
};
};
@Command({
name: 'reset-admin-password',
description: 'Reset the admin password',
})
export class ResetAdminPasswordCommand extends CommandRunner {
constructor(
private service: CliService,
private inquirer: InquirerService,
) {
super();
}
async run(): Promise<void> {
try {
const { password, provided } = await this.userService.resetAdminPassword(this.ask);
const { password, provided } = await this.service.resetAdminPassword(prompt(this.inquirer));
if (provided) {
console.log(`The admin password has been updated.`);

View File

@@ -14,7 +14,7 @@ import { ActivityService } from 'src/services/activity.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Activity')
@Controller('activity')
@Controller('activities')
export class ActivityController {
constructor(private service: ActivityService) {}

View File

@@ -17,7 +17,7 @@ import { AlbumService } from 'src/services/album.service';
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
@ApiTags('Album')
@Controller('album')
@Controller('albums')
export class AlbumController {
constructor(private service: AlbumService) {}

View File

@@ -7,7 +7,7 @@ import { APIKeyService } from 'src/services/api-key.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('API Key')
@Controller('api-key')
@Controller('api-keys')
export class APIKeyController {
constructor(private service: APIKeyService) {}

View File

@@ -0,0 +1,94 @@
import {
Body,
Controller,
HttpCode,
HttpStatus,
Inject,
Param,
ParseFilePipe,
Post,
Put,
Res,
UploadedFiles,
UseInterceptors,
} from '@nestjs/common';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { EndpointLifecycle } from 'src/decorators';
import {
AssetBulkUploadCheckResponseDto,
AssetMediaResponseDto,
AssetMediaStatusEnum,
CheckExistingAssetsResponseDto,
} from 'src/dtos/asset-media-response.dto';
import {
AssetBulkUploadCheckDto,
AssetMediaReplaceDto,
CheckExistingAssetsDto,
UploadFieldName,
} from 'src/dtos/asset-media.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, Route, UploadFiles, getFiles } from 'src/middleware/file-upload.interceptor';
import { AssetMediaService } from 'src/services/asset-media.service';
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
@ApiTags('Asset')
@Controller(Route.ASSET)
export class AssetMediaController {
constructor(
@Inject(ILoggerRepository) private logger: ILoggerRepository,
private service: AssetMediaService,
) {}
/**
* Replace the asset with new file, without changing its id
*/
@Put(':id/file')
@UseInterceptors(FileUploadInterceptor)
@ApiConsumes('multipart/form-data')
@Authenticated({ sharedLink: true })
@EndpointLifecycle({ addedAt: 'v1.106.0' })
async replaceAsset(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator([UploadFieldName.ASSET_DATA])] }))
files: UploadFiles,
@Body() dto: AssetMediaReplaceDto,
@Res({ passthrough: true }) res: Response,
): Promise<AssetMediaResponseDto> {
const { file } = getFiles(files);
const responseDto = await this.service.replaceAsset(auth, id, dto, file);
if (responseDto.status === AssetMediaStatusEnum.DUPLICATE) {
res.status(HttpStatus.OK);
}
return responseDto;
}
/**
* Checks if multiple assets exist on the server and returns all existing - used by background backup
*/
@Post('exist')
@HttpCode(HttpStatus.OK)
@Authenticated()
checkExistingAssets(
@Auth() auth: AuthDto,
@Body() dto: CheckExistingAssetsDto,
): Promise<CheckExistingAssetsResponseDto> {
return this.service.checkExistingAssets(auth, dto);
}
/**
* Checks if assets exist by checksums
*/
@Post('bulk-upload-check')
@HttpCode(HttpStatus.OK)
@Authenticated()
checkBulkUpload(
@Auth() auth: AuthDto,
@Body() dto: AssetBulkUploadCheckDto,
): Promise<AssetBulkUploadCheckResponseDto> {
return this.service.bulkUploadCheck(auth, dto);
}
}

View File

@@ -2,7 +2,6 @@ import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Inject,
Next,
@@ -16,35 +15,17 @@ import {
} from '@nestjs/common';
import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger';
import { NextFunction, Response } from 'express';
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import {
AssetBulkUploadCheckResponseDto,
AssetFileUploadResponseDto,
CheckExistingAssetsResponseDto,
} from 'src/dtos/asset-v1-response.dto';
import {
AssetBulkUploadCheckDto,
AssetSearchDto,
CheckExistingAssetsDto,
CreateAssetDto,
GetAssetThumbnailDto,
ServeFileDto,
} from 'src/dtos/asset-v1.dto';
import { AssetFileUploadResponseDto } from 'src/dtos/asset-v1-response.dto';
import { CreateAssetDto, GetAssetThumbnailDto, ServeFileDto } from 'src/dtos/asset-v1.dto';
import { AuthDto, ImmichHeader } from 'src/dtos/auth.dto';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from 'src/middleware/file-upload.interceptor';
import { FileUploadInterceptor, Route, UploadFiles, mapToUploadFile } from 'src/middleware/file-upload.interceptor';
import { AssetServiceV1 } from 'src/services/asset-v1.service';
import { sendFile } from 'src/utils/file';
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
interface UploadFiles {
assetData: ImmichFile[];
livePhotoData?: ImmichFile[];
sidecarData: ImmichFile[];
}
@ApiTags('Asset')
@Controller(Route.ASSET)
export class AssetControllerV1 {
@@ -115,45 +96,4 @@ export class AssetControllerV1 {
) {
await sendFile(res, next, () => this.service.serveThumbnail(auth, id, dto), this.logger);
}
/**
* Get all AssetEntity belong to the user
*/
@Get('/')
@ApiHeader({
name: 'if-none-match',
description: 'ETag of data already cached on the client',
required: false,
schema: { type: 'string' },
})
@Authenticated()
getAllAssets(@Auth() auth: AuthDto, @Query() dto: AssetSearchDto): Promise<AssetResponseDto[]> {
return this.service.getAllAssets(auth, dto);
}
/**
* Checks if multiple assets exist on the server and returns all existing - used by background backup
*/
@Post('/exist')
@HttpCode(HttpStatus.OK)
@Authenticated()
checkExistingAssets(
@Auth() auth: AuthDto,
@Body() dto: CheckExistingAssetsDto,
): Promise<CheckExistingAssetsResponseDto> {
return this.service.checkExistingAssets(auth, dto);
}
/**
* Checks if assets exist by checksums
*/
@Post('/bulk-upload-check')
@HttpCode(HttpStatus.OK)
@Authenticated()
checkBulkUpload(
@Auth() auth: AuthDto,
@Body() dto: AssetBulkUploadCheckDto,
): Promise<AssetBulkUploadCheckResponseDto> {
return this.service.bulkUploadCheck(auth, dto);
}
}

View File

@@ -7,7 +7,7 @@ import { PersonService } from 'src/services/person.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Face')
@Controller('face')
@Controller('faces')
export class FaceController {
constructor(private service: PersonService) {}

View File

@@ -5,7 +5,7 @@ import { Authenticated } from 'src/middleware/auth.guard';
import { AuditService } from 'src/services/audit.service';
@ApiTags('File Report')
@Controller('report')
@Controller('reports')
export class ReportController {
constructor(private service: AuditService) {}

View File

@@ -2,6 +2,7 @@ import { ActivityController } from 'src/controllers/activity.controller';
import { AlbumController } from 'src/controllers/album.controller';
import { APIKeyController } from 'src/controllers/api-key.controller';
import { AppController } from 'src/controllers/app.controller';
import { AssetMediaController } from 'src/controllers/asset-media.controller';
import { AssetControllerV1 } from 'src/controllers/asset-v1.controller';
import { AssetController } from 'src/controllers/asset.controller';
import { AuditController } from 'src/controllers/audit.controller';
@@ -35,6 +36,7 @@ export const controllers = [
AppController,
AssetController,
AssetControllerV1,
AssetMediaController,
AuditController,
AuthController,
DownloadController,

View File

@@ -14,7 +14,7 @@ import { LibraryService } from 'src/services/library.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Library')
@Controller('library')
@Controller('libraries')
export class LibraryController {
constructor(private service: LibraryService) {}

View File

@@ -8,7 +8,7 @@ import { PartnerService } from 'src/services/partner.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Partner')
@Controller('partner')
@Controller('partners')
export class PartnerController {
constructor(private service: PartnerService) {}

View File

@@ -22,7 +22,7 @@ import { sendFile } from 'src/utils/file';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Person')
@Controller('person')
@Controller('people')
export class PersonController {
constructor(
private service: PersonService,

View File

@@ -3,10 +3,10 @@ import { ApiTags } from '@nestjs/swagger';
import {
ServerConfigDto,
ServerFeaturesDto,
ServerInfoResponseDto,
ServerMediaTypesResponseDto,
ServerPingResponse,
ServerStatsResponseDto,
ServerStorageResponseDto,
ServerThemeDto,
ServerVersionResponseDto,
} from 'src/dtos/server-info.dto';
@@ -22,10 +22,10 @@ export class ServerInfoController {
private versionService: VersionService,
) {}
@Get()
@Get('storage')
@Authenticated()
getServerInfo(): Promise<ServerInfoResponseDto> {
return this.service.getInfo();
getStorage(): Promise<ServerStorageResponseDto> {
return this.service.getStorage();
}
@Get('ping')

View File

@@ -17,7 +17,7 @@ import { respondWithCookie } from 'src/utils/response';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Shared Link')
@Controller('shared-link')
@Controller('shared-links')
export class SharedLinkController {
constructor(private service: SharedLinkService) {}

View File

@@ -10,7 +10,7 @@ import { TagService } from 'src/services/tag.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Tag')
@Controller('tag')
@Controller('tags')
export class TagController {
constructor(private service: TagService) {}

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