mirror of
https://github.com/immich-app/immich.git
synced 2026-06-16 11:52:16 -07:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c4cc56dd0 | |||
| f382624e68 | |||
| 24dad15636 | |||
| 7ab533b57b | |||
| d10153bbc7 | |||
| b846afeb08 | |||
| e222b19576 | |||
| 1fee99cd2a | |||
| 70bb7e4b7e | |||
| f973927c68 | |||
| e29267359e | |||
| 164cda87a3 | |||
| 12d344efe0 | |||
| 474efd39f8 | |||
| 9e453440e6 | |||
| 8860817c76 | |||
| 3c108a8d22 |
@@ -45,7 +45,7 @@ jobs:
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
if: always()
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
issue_number: context.payload.pull_request.number,
|
||||
|
||||
@@ -90,6 +90,8 @@ jobs:
|
||||
mobile/**/*.g.dart
|
||||
mobile/**/*.gr.dart
|
||||
mobile/**/*.drift.dart
|
||||
mobile/**/*.g.swift
|
||||
mobile/**/*.g.kt
|
||||
|
||||
- name: Verify files have not changed
|
||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||
|
||||
@@ -97,7 +97,7 @@ services:
|
||||
command: ['./run.sh', '-disable-reporting']
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:12.4.3-ubuntu@sha256:ca3f764fdc48cebdf22dd206f33ecb0795a9a7210eacd1b5c02204aebd78b223
|
||||
image: grafana/grafana:12.4.4-ubuntu@sha256:df2e7ef5f32f771794cf76bad5f2bceac227036460a2cc269a9045e5662abc58
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat
|
||||
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
|
||||
|
||||
```bash
|
||||
make open-api
|
||||
mise open-api
|
||||
```
|
||||
|
||||
You can find the generated client SDK in the `packages/sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
||||
|
||||
@@ -218,7 +218,7 @@ When the Dev Container starts, it automatically:
|
||||
- Debug ports: 9230 (workers), 9231 (API)
|
||||
|
||||
:::info
|
||||
The Dev Container setup replaces the `make dev` command from the traditional setup. All services start automatically when you open the container.
|
||||
The Dev Container setup replaces the `mise dev` command from the traditional setup. All services start automatically when you open the container.
|
||||
:::
|
||||
|
||||
### Accessing Services
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment.
|
||||
:::warning
|
||||
The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`make dev`, ....). Feel free to contribute!
|
||||
The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`mise dev`, ....). Feel free to contribute!
|
||||
:::
|
||||
When contributing code through a pull request, please check the following:
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ All the services are packaged to run as with single Docker Compose command.
|
||||
5. From the root directory, run:
|
||||
|
||||
```bash title="Start development server"
|
||||
make dev # required Makefile installed on the system.
|
||||
mise dev
|
||||
```
|
||||
|
||||
5. Access the dev instance in your browser at http://localhost:3000, or connect via the mobile app.
|
||||
@@ -88,7 +88,7 @@ To see local changes to `@immich/ui` in Immich, do the following:
|
||||
3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yml` file (`../../ui:/usr/src/ui`)
|
||||
4. Uncomment the corresponding alias in the `web/vite.config.ts` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui/packages/ui')`)
|
||||
5. Uncomment the import statement in `web/src/app.css` file `@import '../../../ui/packages/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';`
|
||||
6. Start up the stack via `make dev`
|
||||
6. Start up the stack via `mise dev`
|
||||
7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`)
|
||||
|
||||
### Mobile app
|
||||
|
||||
@@ -12,7 +12,7 @@ You need to run `mise //server:install` before _once_.
|
||||
The e2e tests can be run by first starting up a test production environment via:
|
||||
|
||||
```bash
|
||||
make e2e
|
||||
mise e2e
|
||||
```
|
||||
|
||||
Before you can run the tests, you need to run the following commands _once_:
|
||||
|
||||
@@ -4,7 +4,8 @@ services:
|
||||
e2e-auth-server:
|
||||
container_name: immich-e2e-auth-server
|
||||
build:
|
||||
context: ../packages/e2e-auth-server
|
||||
context: ../
|
||||
dockerfile: packages/e2e-auth-server/Dockerfile
|
||||
ports:
|
||||
- 2286:2286
|
||||
|
||||
|
||||
@@ -504,13 +504,14 @@ describe('/albums', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to share album with owner', async () => {
|
||||
it('should deduplicate owner from albumUsers on create', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/albums')
|
||||
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Cannot share album with owner'));
|
||||
expect(status).toBe(201);
|
||||
expect(body.albumUsers).toHaveLength(1);
|
||||
expect(body.albumUsers[0]).toMatchObject({ role: AlbumUserRole.Owner, user: { id: user1.userId } });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -82,40 +82,8 @@ url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"]
|
||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
|
||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"]
|
||||
checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"]
|
||||
checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a536c2"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
|
||||
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094"
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz"
|
||||
|
||||
[[tools."github:webassembly/binaryen"]]
|
||||
version = "version_124"
|
||||
@@ -156,6 +124,30 @@ checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2
|
||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-windows.tar.gz"
|
||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288925833"
|
||||
|
||||
[[tools.java]]
|
||||
version = "21.0.2"
|
||||
backend = "core:java"
|
||||
|
||||
[tools.java."platforms.linux-arm64"]
|
||||
checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2"
|
||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz"
|
||||
|
||||
[tools.java."platforms.linux-x64"]
|
||||
checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f"
|
||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz"
|
||||
|
||||
[tools.java."platforms.macos-arm64"]
|
||||
checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4"
|
||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz"
|
||||
|
||||
[tools.java."platforms.macos-x64"]
|
||||
checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3"
|
||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz"
|
||||
|
||||
[tools.java."platforms.windows-x64"]
|
||||
checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664"
|
||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip"
|
||||
|
||||
[[tools.node]]
|
||||
version = "24.15.0"
|
||||
backend = "core:node"
|
||||
@@ -225,36 +217,38 @@ checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c70773
|
||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
||||
|
||||
[[tools.pnpm]]
|
||||
version = "10.33.4"
|
||||
version = "11.4.0"
|
||||
backend = "aqua:pnpm/pnpm"
|
||||
|
||||
[tools.pnpm."platforms.linux-arm64"]
|
||||
checksum = "sha256:d29649c7380b5cd522f574208fbd35335846686498f45004604d3f5b8658b5cb"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-arm64"
|
||||
checksum = "sha256:cc38ebd5b2610a5744f84576b963c49e6609a8df5aed714ae3de749998d4478c"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64.tar.gz"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:d29649c7380b5cd522f574208fbd35335846686498f45004604d3f5b8658b5cb"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-arm64"
|
||||
checksum = "sha256:a1e2ec9123c709fd04b704227cfcf3b50cd2bbbc1bd39d2df414530b5697eb75"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64-musl.tar.gz"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.linux-x64"]
|
||||
checksum = "sha256:ff1795595535a10d0dfe327303f3dd02377be141190b1f5756de68edde2cf813"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-x64"
|
||||
checksum = "sha256:f3f8d1217eef013bbc71a24d52efb1f1041e4aff55edd80e0b08e25f409305a4"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64.tar.gz"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:ff1795595535a10d0dfe327303f3dd02377be141190b1f5756de68edde2cf813"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-x64"
|
||||
checksum = "sha256:60010ad00a96b71e20d1618acaca7a71395e710cbd5e88946c030a1d07c56916"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64-musl.tar.gz"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.macos-arm64"]
|
||||
checksum = "sha256:7aae186a04e1ffaa0047d43cd07d68a98dec303304f28be52234ba955d26c671"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-macos-arm64"
|
||||
|
||||
[tools.pnpm."platforms.macos-x64"]
|
||||
checksum = "sha256:3b0c97b9f794cdda293949a8ee0e0151ca08f512f4a832408386221c7c73eec6"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-macos-x64"
|
||||
checksum = "sha256:ba59014c2c1ce8b76af9f559385206a2623de4ff2b694b5c91598a8f44abb4e2"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-darwin-arm64.tar.gz"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.windows-x64"]
|
||||
checksum = "sha256:3268b2f29defe0dce8a3a26c0ef01488f0d4aa4872923173186ef618ab7d68ef"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-win-x64.exe"
|
||||
checksum = "sha256:84ce90e38bc0b1164173eb853a0fbffc7edcb050cb0d5c8ce4ca609f5c808e0a"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-win32-x64.zip"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[[tools.terragrunt]]
|
||||
version = "1.0.3"
|
||||
|
||||
@@ -16,13 +16,14 @@ config_roots = [
|
||||
|
||||
[tools]
|
||||
node = "24.15.0"
|
||||
pnpm = "10.33.4"
|
||||
pnpm = "11.4.0"
|
||||
terragrunt = "1.0.3"
|
||||
opentofu = "1.11.6"
|
||||
"npm:oazapfts" = "7.5.0"
|
||||
"github:extism/cli" = "1.6.3"
|
||||
"github:webassembly/binaryen" = "version_124"
|
||||
"github:extism/js-pdk" = "1.6.0"
|
||||
java = "21.0.2"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"]
|
||||
version = "7.1.3-6"
|
||||
|
||||
-18
@@ -11,24 +11,6 @@ import Foundation
|
||||
#error("Unsupported platform.")
|
||||
#endif
|
||||
|
||||
/// Error class for passing custom error details to Dart side.
|
||||
final class PigeonError: Error {
|
||||
let code: String
|
||||
let message: String?
|
||||
let details: Sendable?
|
||||
|
||||
init(code: String, message: String?, details: Sendable?) {
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
}
|
||||
|
||||
var localizedDescription: String {
|
||||
return
|
||||
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
|
||||
}
|
||||
}
|
||||
|
||||
private func wrapResult(_ result: Any?) -> [Any?] {
|
||||
return [result]
|
||||
}
|
||||
|
||||
@@ -221,6 +221,10 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
return;
|
||||
}
|
||||
|
||||
if (_scrubberHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_thumbAnimationController.status != AnimationStatus.forward) {
|
||||
_thumbAnimationController.forward();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
|
||||
|
||||
@@ -20,6 +21,7 @@ class GroupSettings extends HookConsumerWidget {
|
||||
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
||||
await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy);
|
||||
ref.invalidate(appSettingsServiceProvider);
|
||||
ref.invalidate(timelineServiceProvider);
|
||||
}
|
||||
|
||||
void changeGroupValue(GroupAssetsBy? value) {
|
||||
@@ -46,10 +48,6 @@ class GroupSettings extends HookConsumerWidget {
|
||||
title: 'month'.t(context: context),
|
||||
value: GroupAssetsBy.month,
|
||||
),
|
||||
SettingsRadioGroup(
|
||||
title: 'asset_list_layout_settings_group_automatically'.t(context: context),
|
||||
value: GroupAssetsBy.auto,
|
||||
),
|
||||
],
|
||||
groupBy: groupBy.value,
|
||||
onRadioChanged: changeGroupValue,
|
||||
|
||||
Generated
+14
@@ -148,6 +148,20 @@ Class | Method | HTTP request | Description
|
||||
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner
|
||||
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
|
||||
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
|
||||
*DeprecatedApi* | [**updateApiKey**](doc//DeprecatedApi.md#updateapikey) | **PUT** /api-keys/{id} | Update an API key
|
||||
*DeprecatedApi* | [**updateAsset**](doc//DeprecatedApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
||||
*DeprecatedApi* | [**updateAssets**](doc//DeprecatedApi.md#updateassets) | **PUT** /assets | Update assets
|
||||
*DeprecatedApi* | [**updateLibrary**](doc//DeprecatedApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
|
||||
*DeprecatedApi* | [**updateMemory**](doc//DeprecatedApi.md#updatememory) | **PUT** /memories/{id} | Update a memory
|
||||
*DeprecatedApi* | [**updateMyPreferences**](doc//DeprecatedApi.md#updatemypreferences) | **PUT** /users/me/preferences | Update my preferences
|
||||
*DeprecatedApi* | [**updateMyUser**](doc//DeprecatedApi.md#updatemyuser) | **PUT** /users/me | Update current user
|
||||
*DeprecatedApi* | [**updatePerson**](doc//DeprecatedApi.md#updateperson) | **PUT** /people/{id} | Update person
|
||||
*DeprecatedApi* | [**updateSession**](doc//DeprecatedApi.md#updatesession) | **PUT** /sessions/{id} | Update a session
|
||||
*DeprecatedApi* | [**updateStack**](doc//DeprecatedApi.md#updatestack) | **PUT** /stacks/{id} | Update a stack
|
||||
*DeprecatedApi* | [**updateTag**](doc//DeprecatedApi.md#updatetag) | **PUT** /tags/{id} | Update a tag
|
||||
*DeprecatedApi* | [**updateUserAdmin**](doc//DeprecatedApi.md#updateuseradmin) | **PUT** /admin/users/{id} | Update a user
|
||||
*DeprecatedApi* | [**updateUserPreferencesAdmin**](doc//DeprecatedApi.md#updateuserpreferencesadmin) | **PUT** /admin/users/{id}/preferences | Update user preferences
|
||||
*DeprecatedApi* | [**updateWorkflow**](doc//DeprecatedApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow
|
||||
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
|
||||
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
|
||||
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Dismiss a duplicate group
|
||||
|
||||
+845
@@ -184,4 +184,849 @@ class DeprecatedApi {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update an API key
|
||||
///
|
||||
/// Updates the name and permissions of an API key by its ID. The current user must own this API key.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [ApiKeyUpdateDto] apiKeyUpdateDto (required):
|
||||
Future<Response> updateApiKeyWithHttpInfo(String id, ApiKeyUpdateDto apiKeyUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/api-keys/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = apiKeyUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update an API key
|
||||
///
|
||||
/// Updates the name and permissions of an API key by its ID. The current user must own this API key.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [ApiKeyUpdateDto] apiKeyUpdateDto (required):
|
||||
Future<ApiKeyResponseDto?> updateApiKey(String id, ApiKeyUpdateDto apiKeyUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateApiKeyWithHttpInfo(id, apiKeyUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'ApiKeyResponseDto',) as ApiKeyResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update an asset
|
||||
///
|
||||
/// Update information of a specific asset.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateAssetDto] updateAssetDto (required):
|
||||
Future<Response> updateAssetWithHttpInfo(String id, UpdateAssetDto updateAssetDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = updateAssetDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update an asset
|
||||
///
|
||||
/// Update information of a specific asset.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateAssetDto] updateAssetDto (required):
|
||||
Future<AssetResponseDto?> updateAsset(String id, UpdateAssetDto updateAssetDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateAssetWithHttpInfo(id, updateAssetDto, abortTrigger: abortTrigger,);
|
||||
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), 'AssetResponseDto',) as AssetResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update assets
|
||||
///
|
||||
/// Updates multiple assets at the same time.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetBulkUpdateDto] assetBulkUpdateDto (required):
|
||||
Future<Response> updateAssetsWithHttpInfo(AssetBulkUpdateDto assetBulkUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetBulkUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update assets
|
||||
///
|
||||
/// Updates multiple assets at the same time.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetBulkUpdateDto] assetBulkUpdateDto (required):
|
||||
Future<void> updateAssets(AssetBulkUpdateDto assetBulkUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateAssetsWithHttpInfo(assetBulkUpdateDto, abortTrigger: abortTrigger,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a library
|
||||
///
|
||||
/// Update an existing external library.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateLibraryDto] updateLibraryDto (required):
|
||||
Future<Response> updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/libraries/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = updateLibraryDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a library
|
||||
///
|
||||
/// Update an existing external library.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateLibraryDto] updateLibraryDto (required):
|
||||
Future<LibraryResponseDto?> updateLibrary(String id, UpdateLibraryDto updateLibraryDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateLibraryWithHttpInfo(id, updateLibraryDto, abortTrigger: abortTrigger,);
|
||||
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), 'LibraryResponseDto',) as LibraryResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update a memory
|
||||
///
|
||||
/// Update an existing memory by its ID.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [MemoryUpdateDto] memoryUpdateDto (required):
|
||||
Future<Response> updateMemoryWithHttpInfo(String id, MemoryUpdateDto memoryUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/memories/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = memoryUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a memory
|
||||
///
|
||||
/// Update an existing memory by its ID.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [MemoryUpdateDto] memoryUpdateDto (required):
|
||||
Future<MemoryResponseDto?> updateMemory(String id, MemoryUpdateDto memoryUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateMemoryWithHttpInfo(id, memoryUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'MemoryResponseDto',) as MemoryResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update my preferences
|
||||
///
|
||||
/// Update the preferences of the current user.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required):
|
||||
Future<Response> updateMyPreferencesWithHttpInfo(UserPreferencesUpdateDto userPreferencesUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/users/me/preferences';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = userPreferencesUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update my preferences
|
||||
///
|
||||
/// Update the preferences of the current user.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required):
|
||||
Future<UserPreferencesResponseDto?> updateMyPreferences(UserPreferencesUpdateDto userPreferencesUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateMyPreferencesWithHttpInfo(userPreferencesUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'UserPreferencesResponseDto',) as UserPreferencesResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update current user
|
||||
///
|
||||
/// Update the current user making the API request.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [UserUpdateMeDto] userUpdateMeDto (required):
|
||||
Future<Response> updateMyUserWithHttpInfo(UserUpdateMeDto userUpdateMeDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/users/me';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = userUpdateMeDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update current user
|
||||
///
|
||||
/// Update the current user making the API request.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [UserUpdateMeDto] userUpdateMeDto (required):
|
||||
Future<UserAdminResponseDto?> updateMyUser(UserUpdateMeDto userUpdateMeDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateMyUserWithHttpInfo(userUpdateMeDto, abortTrigger: abortTrigger,);
|
||||
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), 'UserAdminResponseDto',) as UserAdminResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update person
|
||||
///
|
||||
/// Update an individual person.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [PersonUpdateDto] personUpdateDto (required):
|
||||
Future<Response> updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/people/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = personUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update person
|
||||
///
|
||||
/// Update an individual person.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [PersonUpdateDto] personUpdateDto (required):
|
||||
Future<PersonResponseDto?> updatePerson(String id, PersonUpdateDto personUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updatePersonWithHttpInfo(id, personUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'PersonResponseDto',) as PersonResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update a session
|
||||
///
|
||||
/// Update a specific session identified by id.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [SessionUpdateDto] sessionUpdateDto (required):
|
||||
Future<Response> updateSessionWithHttpInfo(String id, SessionUpdateDto sessionUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/sessions/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = sessionUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a session
|
||||
///
|
||||
/// Update a specific session identified by id.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [SessionUpdateDto] sessionUpdateDto (required):
|
||||
Future<SessionResponseDto?> updateSession(String id, SessionUpdateDto sessionUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateSessionWithHttpInfo(id, sessionUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'SessionResponseDto',) as SessionResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update a stack
|
||||
///
|
||||
/// Update an existing stack by its ID.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [StackUpdateDto] stackUpdateDto (required):
|
||||
Future<Response> updateStackWithHttpInfo(String id, StackUpdateDto stackUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/stacks/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = stackUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a stack
|
||||
///
|
||||
/// Update an existing stack by its ID.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [StackUpdateDto] stackUpdateDto (required):
|
||||
Future<StackResponseDto?> updateStack(String id, StackUpdateDto stackUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateStackWithHttpInfo(id, stackUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'StackResponseDto',) as StackResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update a tag
|
||||
///
|
||||
/// Update an existing tag identified by its ID.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [TagUpdateDto] tagUpdateDto (required):
|
||||
Future<Response> updateTagWithHttpInfo(String id, TagUpdateDto tagUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/tags/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = tagUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a tag
|
||||
///
|
||||
/// Update an existing tag identified by its ID.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [TagUpdateDto] tagUpdateDto (required):
|
||||
Future<TagResponseDto?> updateTag(String id, TagUpdateDto tagUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateTagWithHttpInfo(id, tagUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'TagResponseDto',) as TagResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update a user
|
||||
///
|
||||
/// Update an existing user.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UserAdminUpdateDto] userAdminUpdateDto (required):
|
||||
Future<Response> updateUserAdminWithHttpInfo(String id, UserAdminUpdateDto userAdminUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/users/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = userAdminUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a user
|
||||
///
|
||||
/// Update an existing user.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UserAdminUpdateDto] userAdminUpdateDto (required):
|
||||
Future<UserAdminResponseDto?> updateUserAdmin(String id, UserAdminUpdateDto userAdminUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateUserAdminWithHttpInfo(id, userAdminUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'UserAdminResponseDto',) as UserAdminResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update user preferences
|
||||
///
|
||||
/// Update the preferences of a specific user.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required):
|
||||
Future<Response> updateUserPreferencesAdminWithHttpInfo(String id, UserPreferencesUpdateDto userPreferencesUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/users/{id}/preferences'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = userPreferencesUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update user preferences
|
||||
///
|
||||
/// Update the preferences of a specific user.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required):
|
||||
Future<UserPreferencesResponseDto?> updateUserPreferencesAdmin(String id, UserPreferencesUpdateDto userPreferencesUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateUserPreferencesAdminWithHttpInfo(id, userPreferencesUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'UserPreferencesResponseDto',) as UserPreferencesResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update a workflow
|
||||
///
|
||||
/// Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [WorkflowUpdateDto] workflowUpdateDto (required):
|
||||
Future<Response> updateWorkflowWithHttpInfo(String id, WorkflowUpdateDto workflowUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/workflows/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = workflowUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update a workflow
|
||||
///
|
||||
/// Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [WorkflowUpdateDto] workflowUpdateDto (required):
|
||||
Future<WorkflowResponseDto?> updateWorkflow(String id, WorkflowUpdateDto workflowUpdateDto, { Future<void>? abortTrigger, }) async {
|
||||
final response = await updateWorkflowWithHttpInfo(id, workflowUpdateDto, abortTrigger: abortTrigger,);
|
||||
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), 'WorkflowResponseDto',) as WorkflowResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1194,6 +1194,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update an existing user.",
|
||||
"operationId": "updateUserAdmin",
|
||||
"parameters": [
|
||||
@@ -1243,7 +1244,8 @@
|
||||
],
|
||||
"summary": "Update a user",
|
||||
"tags": [
|
||||
"Users (admin)"
|
||||
"Users (admin)",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-admin-only": true,
|
||||
"x-immich-history": [
|
||||
@@ -1258,10 +1260,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateUserAdmin"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "adminUser.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/admin/users/{id}/calendar-heatmap": {
|
||||
@@ -1416,6 +1423,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update the preferences of a specific user.",
|
||||
"operationId": "updateUserPreferencesAdmin",
|
||||
"parameters": [
|
||||
@@ -1465,7 +1473,8 @@
|
||||
],
|
||||
"summary": "Update user preferences",
|
||||
"tags": [
|
||||
"Users (admin)"
|
||||
"Users (admin)",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-admin-only": true,
|
||||
"x-immich-history": [
|
||||
@@ -1480,10 +1489,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateUserPreferencesAdmin"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "adminUser.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/admin/users/{id}/restore": {
|
||||
@@ -2863,6 +2877,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Updates the name and permissions of an API key by its ID. The current user must own this API key.",
|
||||
"operationId": "updateApiKey",
|
||||
"parameters": [
|
||||
@@ -2912,7 +2927,8 @@
|
||||
],
|
||||
"summary": "Update an API key",
|
||||
"tags": [
|
||||
"API keys"
|
||||
"API keys",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -2926,10 +2942,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateApiKey"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "apiKey.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/assets": {
|
||||
@@ -3080,6 +3101,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Updates multiple assets at the same time.",
|
||||
"operationId": "updateAssets",
|
||||
"parameters": [],
|
||||
@@ -3111,7 +3133,8 @@
|
||||
],
|
||||
"summary": "Update assets",
|
||||
"tags": [
|
||||
"Assets"
|
||||
"Assets",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -3125,10 +3148,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateAssets"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/assets/bulk-upload-check": {
|
||||
@@ -3557,6 +3585,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update information of a specific asset.",
|
||||
"operationId": "updateAsset",
|
||||
"parameters": [
|
||||
@@ -3606,7 +3635,8 @@
|
||||
],
|
||||
"summary": "Update an asset",
|
||||
"tags": [
|
||||
"Assets"
|
||||
"Assets",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -3620,10 +3650,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateAsset"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/assets/{id}/edits": {
|
||||
@@ -6329,6 +6364,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update an existing external library.",
|
||||
"operationId": "updateLibrary",
|
||||
"parameters": [
|
||||
@@ -6378,7 +6414,8 @@
|
||||
],
|
||||
"summary": "Update a library",
|
||||
"tags": [
|
||||
"Libraries"
|
||||
"Libraries",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-admin-only": true,
|
||||
"x-immich-history": [
|
||||
@@ -6393,10 +6430,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateLibrary"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "library.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/libraries/{id}/scan": {
|
||||
@@ -7165,6 +7207,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update an existing memory by its ID.",
|
||||
"operationId": "updateMemory",
|
||||
"parameters": [
|
||||
@@ -7214,7 +7257,8 @@
|
||||
],
|
||||
"summary": "Update a memory",
|
||||
"tags": [
|
||||
"Memories"
|
||||
"Memories",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -7228,10 +7272,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateMemory"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "memory.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/memories/{id}/assets": {
|
||||
@@ -8711,6 +8760,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update an individual person.",
|
||||
"operationId": "updatePerson",
|
||||
"parameters": [
|
||||
@@ -8760,7 +8810,8 @@
|
||||
],
|
||||
"summary": "Update person",
|
||||
"tags": [
|
||||
"People"
|
||||
"People",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -8774,10 +8825,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updatePerson"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "person.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/people/{id}/merge": {
|
||||
@@ -11529,6 +11585,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update a specific session identified by id.",
|
||||
"operationId": "updateSession",
|
||||
"parameters": [
|
||||
@@ -11578,7 +11635,8 @@
|
||||
],
|
||||
"summary": "Update a session",
|
||||
"tags": [
|
||||
"Sessions"
|
||||
"Sessions",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -11592,10 +11650,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateSession"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "session.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/sessions/{id}/lock": {
|
||||
@@ -12545,6 +12608,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update an existing stack by its ID.",
|
||||
"operationId": "updateStack",
|
||||
"parameters": [
|
||||
@@ -12594,7 +12658,8 @@
|
||||
],
|
||||
"summary": "Update a stack",
|
||||
"tags": [
|
||||
"Stacks"
|
||||
"Stacks",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -12608,10 +12673,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateStack"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "stack.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/stacks/{id}/assets/{assetId}": {
|
||||
@@ -13648,6 +13718,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update an existing tag identified by its ID.",
|
||||
"operationId": "updateTag",
|
||||
"parameters": [
|
||||
@@ -13697,7 +13768,8 @@
|
||||
],
|
||||
"summary": "Update a tag",
|
||||
"tags": [
|
||||
"Tags"
|
||||
"Tags",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -13711,10 +13783,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateTag"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "tag.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/tags/{id}/assets": {
|
||||
@@ -14517,6 +14594,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update the current user making the API request.",
|
||||
"operationId": "updateMyUser",
|
||||
"parameters": [],
|
||||
@@ -14555,7 +14633,8 @@
|
||||
],
|
||||
"summary": "Update current user",
|
||||
"tags": [
|
||||
"Users"
|
||||
"Users",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -14569,10 +14648,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateMyUser"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "user.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/users/me/calendar-heatmap": {
|
||||
@@ -15003,6 +15087,7 @@
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update the preferences of the current user.",
|
||||
"operationId": "updateMyPreferences",
|
||||
"parameters": [],
|
||||
@@ -15041,7 +15126,8 @@
|
||||
],
|
||||
"summary": "Update my preferences",
|
||||
"tags": [
|
||||
"Users"
|
||||
"Users",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
@@ -15055,10 +15141,15 @@
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateMyPreferences"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "userPreference.update",
|
||||
"x-immich-state": "Stable"
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/users/profile-image": {
|
||||
@@ -15680,6 +15771,7 @@
|
||||
"x-immich-permission": "workflow.read"
|
||||
},
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"description": "Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.",
|
||||
"operationId": "updateWorkflow",
|
||||
"parameters": [
|
||||
@@ -15729,15 +15821,22 @@
|
||||
],
|
||||
"summary": "Update a workflow",
|
||||
"tags": [
|
||||
"Workflows"
|
||||
"Workflows",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v3.0.0",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Deprecated",
|
||||
"replacementId": "updateWorkflow"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "workflow.update"
|
||||
"x-immich-permission": "workflow.update",
|
||||
"x-immich-state": "Deprecated"
|
||||
}
|
||||
},
|
||||
"/workflows/{id}/share": {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
"format": "prettier --cache --check i18n/",
|
||||
"format:fix": "prettier --cache --write --list-different i18n"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800",
|
||||
"packageManager": "pnpm@11.4.0",
|
||||
"engines": {
|
||||
"pnpm": ">=10.0.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25
|
||||
WORKDIR /usr/src/app
|
||||
COPY package* pnpm* .pnpmfile.cjs ./
|
||||
COPY ./packages ./packages/
|
||||
WORKDIR /usr/src/app/packages/e2e-auth-server
|
||||
RUN corepack enable
|
||||
ADD package.json *.ts ./
|
||||
RUN pnpm install
|
||||
RUN pnpm install --frozen-lockfile
|
||||
EXPOSE 2286
|
||||
CMD ["pnpm", "run", "start"]
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"oidc-provider": "^9.0.0",
|
||||
"tsx": "^4.20.6"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.4"
|
||||
"packageManager": "pnpm@11.4.0"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"packageManager": "pnpm@10.33.4",
|
||||
"packageManager": "pnpm@11.4.0",
|
||||
"devDependencies": {
|
||||
"@extism/js-pdk": "^1.1.1",
|
||||
"@immich/sdk": "workspace:*",
|
||||
|
||||
Generated
+468
-428
File diff suppressed because it is too large
Load Diff
+19
-22
@@ -8,31 +8,28 @@ packages:
|
||||
- web
|
||||
- .github
|
||||
- packages/*
|
||||
ignoredBuiltDependencies:
|
||||
- '@nestjs/core'
|
||||
- '@parcel/watcher'
|
||||
- '@scarf/scarf'
|
||||
- '@swc/core'
|
||||
- canvas
|
||||
- core-js
|
||||
- core-js-pure
|
||||
- cpu-features
|
||||
- es5-ext
|
||||
- esbuild
|
||||
- msgpackr-extract
|
||||
- postman-code-generators
|
||||
- protobufjs
|
||||
- ssh2
|
||||
- utimes
|
||||
onlyBuiltDependencies:
|
||||
- sharp
|
||||
- '@tailwindcss/oxide'
|
||||
- bcrypt
|
||||
allowBuilds:
|
||||
'@nestjs/core': false
|
||||
'@parcel/watcher': false
|
||||
'@scarf/scarf': false
|
||||
'@swc/core': false
|
||||
bcrypt: true
|
||||
canvas: false
|
||||
core-js: false
|
||||
cpu-features: false
|
||||
es5-ext: false
|
||||
esbuild: false
|
||||
msgpackr-extract: false
|
||||
protobufjs: false
|
||||
sharp: true
|
||||
ssh2: false
|
||||
utimes: false
|
||||
'@tailwindcss/oxide': true
|
||||
core-js-pure: false
|
||||
postman-code-generators: false
|
||||
overrides:
|
||||
canvas: 3.2.3
|
||||
sharp: ^0.34.5
|
||||
# pending docusaurus 3.10.1
|
||||
webpackbar: ^7.0.0
|
||||
packageExtensions:
|
||||
nestjs-kysely:
|
||||
dependencies:
|
||||
|
||||
+4
-4
@@ -20,8 +20,8 @@ RUN --mount=type=cache,id=pnpm-server,target=/buildcache/pnpm-store \
|
||||
--mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \
|
||||
--mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
|
||||
--mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter immich --frozen-lockfile build && \
|
||||
SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter immich build && \
|
||||
SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --prod --no-optional deploy /output/server-pruned
|
||||
|
||||
FROM builder AS web
|
||||
|
||||
@@ -37,7 +37,7 @@ RUN --mount=type=cache,id=pnpm-web,target=/buildcache/pnpm-store \
|
||||
--mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \
|
||||
--mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
|
||||
--mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter immich-web --frozen-lockfile --force install && \
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter immich-web install --frozen-lockfile --force && \
|
||||
pnpm --filter @immich/sdk --filter immich-web build
|
||||
|
||||
FROM builder AS cli
|
||||
@@ -48,7 +48,7 @@ RUN --mount=type=cache,id=pnpm-cli,target=/buildcache/pnpm-store \
|
||||
--mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \
|
||||
--mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
|
||||
--mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \
|
||||
pnpm --filter @immich/sdk --filter @immich/cli --frozen-lockfile install && \
|
||||
pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && \
|
||||
pnpm --filter @immich/sdk --filter @immich/cli build && \
|
||||
pnpm --filter @immich/cli --prod --no-optional deploy /output/cli-pruned
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { ApiKeyCreateDto, ApiKeyCreateResponseDto, ApiKeyResponseDto, ApiKeyUpdateDto } from 'src/dtos/api-key.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -62,7 +62,11 @@ export class ApiKeyController {
|
||||
@Endpoint({
|
||||
summary: 'Update an API key',
|
||||
description: 'Updates the name and permissions of an API key by its ID. The current user must own this API key.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateApiKey' }),
|
||||
})
|
||||
updateApiKey(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -72,6 +76,17 @@ export class ApiKeyController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.ApiKeyUpdate })
|
||||
updateApiKeyV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: ApiKeyUpdateDto,
|
||||
): Promise<ApiKeyResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.ApiKeyDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import {
|
||||
@@ -59,12 +59,24 @@ export class AssetController {
|
||||
@Endpoint({
|
||||
summary: 'Update assets',
|
||||
description: 'Updates multiple assets at the same time.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateAssets' }),
|
||||
})
|
||||
updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
|
||||
return this.service.updateAll(auth, dto);
|
||||
}
|
||||
|
||||
@Patch()
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.AssetUpdate })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
updateAssetsV3(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
|
||||
return this.service.updateAll(auth, dto);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
@Authenticated({ permission: Permission.AssetDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@@ -131,7 +143,11 @@ export class AssetController {
|
||||
@Endpoint({
|
||||
summary: 'Update an asset',
|
||||
description: 'Update information of a specific asset.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateAsset' }),
|
||||
})
|
||||
updateAsset(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -141,6 +157,17 @@ export class AssetController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.AssetUpdate })
|
||||
updateAssetV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UpdateAssetDto,
|
||||
): Promise<AssetResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Get(':id/metadata')
|
||||
@Authenticated({ permission: Permission.AssetRead })
|
||||
@Endpoint({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import {
|
||||
CreateLibraryDto,
|
||||
@@ -57,12 +57,23 @@ export class LibraryController {
|
||||
@Endpoint({
|
||||
summary: 'Update a library',
|
||||
description: 'Update an existing external library.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateLibrary' }),
|
||||
})
|
||||
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||
return this.service.update(id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.LibraryUpdate, admin: true })
|
||||
updateLibraryV3(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||
return this.service.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.LibraryDelete, admin: true })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -71,7 +71,11 @@ export class MemoryController {
|
||||
@Endpoint({
|
||||
summary: 'Update a memory',
|
||||
description: 'Update an existing memory by its ID.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateMemory' }),
|
||||
})
|
||||
updateMemory(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -81,6 +85,17 @@ export class MemoryController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.MemoryUpdate })
|
||||
updateMemoryV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: MemoryUpdateDto,
|
||||
): Promise<MemoryResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.MemoryDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
HttpStatus,
|
||||
Next,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { NextFunction, Response } from 'express';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
@@ -106,7 +107,11 @@ export class PersonController {
|
||||
@Endpoint({
|
||||
summary: 'Update person',
|
||||
description: 'Update an individual person.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updatePerson' }),
|
||||
})
|
||||
updatePerson(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -116,6 +121,17 @@ export class PersonController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.PersonUpdate })
|
||||
updatePersonV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: PersonUpdateDto,
|
||||
): Promise<PersonResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.PersonDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { SessionCreateDto, SessionCreateResponseDto, SessionResponseDto, SessionUpdateDto } from 'src/dtos/session.dto';
|
||||
@@ -52,7 +52,11 @@ export class SessionController {
|
||||
@Endpoint({
|
||||
summary: 'Update a session',
|
||||
description: 'Update a specific session identified by id.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateSession' }),
|
||||
})
|
||||
updateSession(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -62,6 +66,17 @@ export class SessionController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.SessionUpdate })
|
||||
updateSessionV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: SessionUpdateDto,
|
||||
): Promise<SessionResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.SessionDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -65,7 +65,11 @@ export class StackController {
|
||||
@Endpoint({
|
||||
summary: 'Update a stack',
|
||||
description: 'Update an existing stack by its ID.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateStack' }),
|
||||
})
|
||||
updateStack(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -75,6 +79,17 @@ export class StackController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.StackUpdate })
|
||||
updateStackV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: StackUpdateDto,
|
||||
): Promise<StackResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.StackDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -81,12 +81,23 @@ export class TagController {
|
||||
@Endpoint({
|
||||
summary: 'Update a tag',
|
||||
description: 'Update an existing tag identified by its ID.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v3', { replacementId: 'updateTag' }),
|
||||
})
|
||||
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise<TagResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.TagUpdate })
|
||||
updateTagV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: TagUpdateDto,
|
||||
): Promise<TagResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.TagDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AssetStatsDto, AssetStatsResponseDto } from 'src/dtos/asset.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -61,7 +61,11 @@ export class UserAdminController {
|
||||
@Endpoint({
|
||||
summary: 'Update a user',
|
||||
description: 'Update an existing user.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateUserAdmin' }),
|
||||
})
|
||||
updateUserAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -71,6 +75,17 @@ export class UserAdminController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
|
||||
updateUserAdminV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UserAdminUpdateDto,
|
||||
): Promise<UserAdminResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
|
||||
@Endpoint({
|
||||
@@ -143,7 +158,11 @@ export class UserAdminController {
|
||||
@Endpoint({
|
||||
summary: 'Update user preferences',
|
||||
description: 'Update the preferences of a specific user.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateUserPreferencesAdmin' }),
|
||||
})
|
||||
updateUserPreferencesAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -153,6 +172,17 @@ export class UserAdminController {
|
||||
return this.service.updatePreferences(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/preferences')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
|
||||
updateUserPreferencesAdminV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UserPreferencesUpdateDto,
|
||||
): Promise<UserPreferencesResponseDto> {
|
||||
return this.service.updatePreferences(auth, id, dto);
|
||||
}
|
||||
|
||||
@Post(':id/restore')
|
||||
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
HttpStatus,
|
||||
Next,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBody, ApiConsumes, ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { NextFunction, Response } from 'express';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -78,12 +79,23 @@ export class UserController {
|
||||
@Endpoint({
|
||||
summary: 'Update current user',
|
||||
description: 'Update the current user making the API request.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateMyUser' }),
|
||||
})
|
||||
updateMyUser(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
|
||||
return this.service.updateMe(auth, dto);
|
||||
}
|
||||
|
||||
@Patch('me')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.UserUpdate })
|
||||
updateMyUserV3(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
|
||||
return this.service.updateMe(auth, dto);
|
||||
}
|
||||
|
||||
@Get('me/preferences')
|
||||
@Authenticated({ permission: Permission.UserPreferenceRead })
|
||||
@Endpoint({
|
||||
@@ -100,7 +112,11 @@ export class UserController {
|
||||
@Endpoint({
|
||||
summary: 'Update my preferences',
|
||||
description: 'Update the preferences of the current user.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3', { replacementId: 'updateMyPreferences' }),
|
||||
})
|
||||
updateMyPreferences(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -109,6 +125,16 @@ export class UserController {
|
||||
return this.service.updateMyPreferences(auth, dto);
|
||||
}
|
||||
|
||||
@Patch('me/preferences')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.UserPreferenceUpdate })
|
||||
updateMyPreferencesV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: UserPreferencesUpdateDto,
|
||||
): Promise<UserPreferencesResponseDto> {
|
||||
return this.service.updateMyPreferences(auth, dto);
|
||||
}
|
||||
|
||||
@Get('me/license')
|
||||
@Authenticated({ permission: Permission.UserLicenseRead })
|
||||
@Endpoint({
|
||||
|
||||
@@ -95,15 +95,15 @@ describe(WorkflowController.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /workflows/:id', () => {
|
||||
describe('PATCH /workflows/:id', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).put(`/workflows/${factory.uuid()}`).send({});
|
||||
await request(ctx.getHttpServer()).patch(`/workflows/${factory.uuid()}`).send({});
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`should require id to be a uuid`, async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.put(`/workflows/invalid`)
|
||||
.patch(`/workflows/invalid`)
|
||||
.set('Authorization', `Bearer token`)
|
||||
.send({});
|
||||
expect(status).toBe(400);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import {
|
||||
@@ -81,7 +81,7 @@ export class WorkflowController {
|
||||
summary: 'Update a workflow',
|
||||
description:
|
||||
'Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.',
|
||||
history: HistoryBuilder.v3(),
|
||||
history: new HistoryBuilder().added('v3.0.0').deprecated('v3', { replacementId: 'updateWorkflow' }),
|
||||
})
|
||||
updateWorkflow(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -91,6 +91,17 @@ export class WorkflowController {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiExcludeEndpoint()
|
||||
@Authenticated({ permission: Permission.WorkflowUpdate })
|
||||
updateWorkflowV3(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: WorkflowUpdateDto,
|
||||
): Promise<WorkflowResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.WorkflowDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
|
||||
@@ -392,6 +392,27 @@ export const columns = {
|
||||
'asset.height',
|
||||
'asset.isEdited',
|
||||
],
|
||||
syncAlbumAsset: [
|
||||
'asset.id',
|
||||
'asset.ownerId',
|
||||
'asset.originalFileName',
|
||||
'asset.thumbhash',
|
||||
'asset.checksum',
|
||||
'asset.fileCreatedAt',
|
||||
'asset.fileModifiedAt',
|
||||
'asset.createdAt',
|
||||
'asset.localDateTime',
|
||||
'asset.type',
|
||||
'asset.deletedAt',
|
||||
'asset.visibility',
|
||||
'asset.duration',
|
||||
'asset.livePhotoVideoId',
|
||||
'asset.stackId',
|
||||
'asset.libraryId',
|
||||
'asset.width',
|
||||
'asset.height',
|
||||
'asset.isEdited',
|
||||
],
|
||||
syncPartnerAsset: [
|
||||
'asset.id',
|
||||
'asset.ownerId',
|
||||
|
||||
@@ -69,7 +69,6 @@ select
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
"asset"."isFavorite",
|
||||
"asset"."visibility",
|
||||
"asset"."duration",
|
||||
"asset"."livePhotoVideoId",
|
||||
@@ -78,15 +77,19 @@ select
|
||||
"asset"."width",
|
||||
"asset"."height",
|
||||
"asset"."isEdited",
|
||||
case
|
||||
when "asset"."ownerId" = $1 then "asset"."isFavorite"
|
||||
else $2
|
||||
end as "isFavorite",
|
||||
"album_asset"."updateId"
|
||||
from
|
||||
"album_asset" as "album_asset"
|
||||
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
||||
where
|
||||
"album_asset"."updateId" < $1
|
||||
and "album_asset"."updateId" <= $2
|
||||
and "album_asset"."updateId" >= $3
|
||||
and "album_asset"."albumId" = $4
|
||||
"album_asset"."updateId" < $3
|
||||
and "album_asset"."updateId" <= $4
|
||||
and "album_asset"."updateId" >= $5
|
||||
and "album_asset"."albumId" = $6
|
||||
order by
|
||||
"album_asset"."updateId" asc
|
||||
|
||||
@@ -103,7 +106,6 @@ select
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
"asset"."isFavorite",
|
||||
"asset"."visibility",
|
||||
"asset"."duration",
|
||||
"asset"."livePhotoVideoId",
|
||||
@@ -112,16 +114,20 @@ select
|
||||
"asset"."width",
|
||||
"asset"."height",
|
||||
"asset"."isEdited",
|
||||
case
|
||||
when "asset"."ownerId" = $1 then "asset"."isFavorite"
|
||||
else $2
|
||||
end as "isFavorite",
|
||||
"asset"."updateId"
|
||||
from
|
||||
"asset" as "asset"
|
||||
inner join "album_asset" on "album_asset"."assetId" = "asset"."id"
|
||||
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||
where
|
||||
"asset"."updateId" < $1
|
||||
and "asset"."updateId" > $2
|
||||
and "album_asset"."updateId" <= $3
|
||||
and "album_user"."userId" = $4
|
||||
"asset"."updateId" < $3
|
||||
and "asset"."updateId" > $4
|
||||
and "album_asset"."updateId" <= $5
|
||||
and "album_user"."userId" = $6
|
||||
order by
|
||||
"asset"."updateId" asc
|
||||
|
||||
@@ -139,7 +145,6 @@ select
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
"asset"."isFavorite",
|
||||
"asset"."visibility",
|
||||
"asset"."duration",
|
||||
"asset"."livePhotoVideoId",
|
||||
@@ -147,15 +152,19 @@ select
|
||||
"asset"."libraryId",
|
||||
"asset"."width",
|
||||
"asset"."height",
|
||||
"asset"."isEdited"
|
||||
"asset"."isEdited",
|
||||
case
|
||||
when "asset"."ownerId" = $1 then "asset"."isFavorite"
|
||||
else $2
|
||||
end as "isFavorite"
|
||||
from
|
||||
"album_asset" as "album_asset"
|
||||
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
||||
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||
where
|
||||
"album_asset"."updateId" < $1
|
||||
and "album_asset"."updateId" > $2
|
||||
and "album_user"."userId" = $3
|
||||
"album_asset"."updateId" < $3
|
||||
and "album_asset"."updateId" > $4
|
||||
and "album_user"."userId" = $5
|
||||
order by
|
||||
"album_asset"."updateId" asc
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export class MetadataRepository {
|
||||
inferTimezoneFromDatestamps: true,
|
||||
inferTimezoneFromTimeStamp: true,
|
||||
useMWG: true,
|
||||
numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength', 'FileSize'],
|
||||
numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength', 'FileSize', 'Rotation'],
|
||||
/* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */
|
||||
geoTz: (lat, lon) => geotz.find(lat, lon)[0],
|
||||
geolocation: true,
|
||||
|
||||
@@ -195,11 +195,20 @@ class AlbumSync extends BaseSync {
|
||||
}
|
||||
|
||||
class AlbumAssetSync extends BaseSync {
|
||||
@GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true })
|
||||
getBackfill(options: SyncBackfillOptions, albumId: string) {
|
||||
@GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID, DummyValue.UUID], stream: true })
|
||||
getBackfill(options: SyncBackfillOptions, albumId: string, userId: string) {
|
||||
return this.backfillQuery('album_asset', options)
|
||||
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
||||
.select(columns.syncAsset)
|
||||
.select(columns.syncAlbumAsset)
|
||||
.select((eb) =>
|
||||
eb
|
||||
.case()
|
||||
.when('asset.ownerId', '=', userId)
|
||||
.then(eb.ref('asset.isFavorite'))
|
||||
.else(eb.val(false))
|
||||
.end()
|
||||
.as('isFavorite'),
|
||||
)
|
||||
.select('album_asset.updateId')
|
||||
.where('album_asset.albumId', '=', albumId)
|
||||
.stream();
|
||||
@@ -210,7 +219,16 @@ class AlbumAssetSync extends BaseSync {
|
||||
const userId = options.userId;
|
||||
return this.upsertQuery('asset', options)
|
||||
.innerJoin('album_asset', 'album_asset.assetId', 'asset.id')
|
||||
.select(columns.syncAsset)
|
||||
.select(columns.syncAlbumAsset)
|
||||
.select((eb) =>
|
||||
eb
|
||||
.case()
|
||||
.when('asset.ownerId', '=', userId)
|
||||
.then(eb.ref('asset.isFavorite'))
|
||||
.else(eb.val(false))
|
||||
.end()
|
||||
.as('isFavorite'),
|
||||
)
|
||||
.select('asset.updateId')
|
||||
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about
|
||||
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||
@@ -224,7 +242,16 @@ class AlbumAssetSync extends BaseSync {
|
||||
return this.upsertQuery('album_asset', options)
|
||||
.select('album_asset.updateId')
|
||||
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
||||
.select(columns.syncAsset)
|
||||
.select(columns.syncAlbumAsset)
|
||||
.select((eb) =>
|
||||
eb
|
||||
.case()
|
||||
.when('asset.ownerId', '=', userId)
|
||||
.then(eb.ref('asset.isFavorite'))
|
||||
.else(eb.val(false))
|
||||
.end()
|
||||
.as('isFavorite'),
|
||||
)
|
||||
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||
.where('album_user.userId', '=', userId)
|
||||
.stream();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
// Delete cross-owner memory assets
|
||||
await sql`
|
||||
DELETE FROM memory_asset
|
||||
USING memory, asset
|
||||
WHERE memory_asset."memoriesId" = memory.id
|
||||
AND memory_asset."assetId" = asset.id
|
||||
AND memory."ownerId" != asset."ownerId"
|
||||
`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// Not implemented: the deleted rows were cross-owner entries
|
||||
}
|
||||
@@ -348,17 +348,25 @@ describe(AlbumService.name, () => {
|
||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId, 'asset-2']), false);
|
||||
});
|
||||
|
||||
it('should throw an error if the userId is the ownerId', async () => {
|
||||
const album = AlbumFactory.create();
|
||||
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||
mocks.user.get.mockResolvedValue(owner);
|
||||
await expect(
|
||||
sut.create(AuthFactory.create(owner), {
|
||||
albumName: 'Empty album',
|
||||
albumUsers: [{ userId: owner.id, role: AlbumUserRole.Editor }],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(mocks.album.create).not.toHaveBeenCalled();
|
||||
it('should deduplicate owner from albumUsers on create', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
const album = AlbumFactory.from().build();
|
||||
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
||||
mocks.user.getMetadata.mockResolvedValue([]);
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set());
|
||||
|
||||
await sut.create(auth, {
|
||||
albumName: 'Empty album',
|
||||
albumUsers: [{ userId: auth.user.id, role: AlbumUserRole.Editor }],
|
||||
});
|
||||
|
||||
expect(mocks.user.get).not.toHaveBeenCalled();
|
||||
expect(mocks.album.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ albumName: 'Empty album' }),
|
||||
[],
|
||||
[{ userId: auth.user.id, role: AlbumUserRole.Owner }],
|
||||
auth.user.id,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export class AlbumService extends BaseService {
|
||||
}
|
||||
|
||||
async create(auth: AuthDto, dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||
const albumUsers = dto.albumUsers || [];
|
||||
const albumUsers = (dto.albumUsers || []).filter(({ userId }) => userId !== auth.user.id);
|
||||
|
||||
for (const { userId } of albumUsers) {
|
||||
const exists = await this.userRepository.get(userId, {});
|
||||
@@ -106,10 +106,6 @@ export class AlbumService extends BaseService {
|
||||
this.logger.debug('Album creation failed: user not found');
|
||||
throw new BadRequestException('Invalid user');
|
||||
}
|
||||
|
||||
if (userId == auth.user.id) {
|
||||
throw new BadRequestException('Cannot share album with owner');
|
||||
}
|
||||
}
|
||||
|
||||
const allowedAssetIdsSet = await this.checkAccess({
|
||||
@@ -179,7 +175,7 @@ export class AlbumService extends BaseService {
|
||||
const results = await addAssets(
|
||||
auth,
|
||||
{ access: this.accessRepository, bulk: this.albumRepository },
|
||||
{ parentId: id, assetIds: dto.ids },
|
||||
{ parentId: id, assetIds: dto.ids, permission: Permission.AssetShare },
|
||||
);
|
||||
|
||||
const { id: firstNewAssetId } = results.find(({ success }) => success) || {};
|
||||
|
||||
@@ -134,6 +134,27 @@ describe(MemoryService.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not link a partner asset', async () => {
|
||||
const [assetId, userId] = newUuids();
|
||||
const memory = MemoryFactory.create({ ownerId: userId });
|
||||
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set());
|
||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetId]));
|
||||
mocks.memory.create.mockResolvedValue(getForMemory(memory));
|
||||
|
||||
await expect(
|
||||
sut.create(factory.auth({ user: { id: userId } }), {
|
||||
type: memory.type,
|
||||
data: memory.data as OnThisDayData,
|
||||
memoryAt: memory.memoryAt,
|
||||
assetIds: [assetId],
|
||||
}),
|
||||
).resolves.toMatchObject({ assets: [] });
|
||||
|
||||
expect(mocks.memory.create).toHaveBeenCalledWith(expect.objectContaining({ ownerId: userId }), new Set());
|
||||
expect(mocks.access.asset.checkPartnerAccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should create a memory without assets', async () => {
|
||||
const memory = MemoryFactory.create();
|
||||
|
||||
@@ -230,6 +251,24 @@ describe(MemoryService.name, () => {
|
||||
expect(mocks.memory.addAssetIds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not link a partner asset', async () => {
|
||||
const assetId = newUuid();
|
||||
const memory = MemoryFactory.create();
|
||||
|
||||
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id]));
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set());
|
||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetId]));
|
||||
mocks.memory.get.mockResolvedValue(getForMemory(memory));
|
||||
mocks.memory.getAssetIds.mockResolvedValue(new Set());
|
||||
|
||||
await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([
|
||||
{ error: 'no_permission', id: assetId, success: false },
|
||||
]);
|
||||
|
||||
expect(mocks.memory.addAssetIds).not.toHaveBeenCalled();
|
||||
expect(mocks.access.asset.checkPartnerAccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add assets', async () => {
|
||||
const assetId = newUuid();
|
||||
const memory = MemoryFactory.create();
|
||||
|
||||
@@ -93,7 +93,7 @@ export class MemoryService extends BaseService {
|
||||
const assetIds = dto.assetIds || [];
|
||||
const allowedAssetIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.AssetShare,
|
||||
permission: Permission.AssetUpdate,
|
||||
ids: assetIds,
|
||||
});
|
||||
const memory = await this.memoryRepository.create(
|
||||
@@ -134,7 +134,11 @@ export class MemoryService extends BaseService {
|
||||
await this.requireAccess({ auth, permission: Permission.MemoryRead, ids: [id] });
|
||||
|
||||
const repos = { access: this.accessRepository, bulk: this.memoryRepository };
|
||||
const results = await addAssets(auth, repos, { parentId: id, assetIds: dto.ids });
|
||||
const results = await addAssets(auth, repos, {
|
||||
parentId: id,
|
||||
assetIds: dto.ids,
|
||||
permission: Permission.AssetUpdate,
|
||||
});
|
||||
|
||||
const hasSuccess = results.find(({ success }) => success);
|
||||
if (hasSuccess) {
|
||||
|
||||
@@ -616,6 +616,17 @@ export class MetadataService extends BaseService {
|
||||
// never use duration from sidecar
|
||||
delete sidecarTags?.Duration;
|
||||
|
||||
// don't use Exif Orientation for HEIF based images, it's usually missing or invalid.
|
||||
// prefer irot (ExifTool QuickTime:Rotation) mapped to ExifOrientation.
|
||||
if (mimeTypes.isHeifImage(asset.originalPath)) {
|
||||
const orientation = this.getHeifOrientation(mediaTags);
|
||||
if (orientation === null) {
|
||||
delete mediaTags.Orientation;
|
||||
} else {
|
||||
mediaTags.Orientation = orientation;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tags: { ...mediaTags, ...videoResult?.tags, ...sidecarTags },
|
||||
audio: videoResult?.audio,
|
||||
@@ -1110,4 +1121,24 @@ export class MetadataService extends BaseService {
|
||||
|
||||
return { tags, audio, video, packets, format };
|
||||
}
|
||||
|
||||
private getHeifOrientation(exifTags: ImmichTags): ExifOrientation | null {
|
||||
// https://exiftool.org/TagNames/QuickTime.html#ItemPropCont
|
||||
const rotation = typeof exifTags.Rotation === 'number' ? exifTags.Rotation : undefined;
|
||||
switch (rotation) {
|
||||
case 0: {
|
||||
return ExifOrientation.Horizontal;
|
||||
}
|
||||
case 1: {
|
||||
return ExifOrientation.Rotate270CW;
|
||||
}
|
||||
case 2: {
|
||||
return ExifOrientation.Rotate180;
|
||||
}
|
||||
case 3: {
|
||||
return ExifOrientation.Rotate90CW;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,7 @@ export class SyncService extends BaseService {
|
||||
const backfill = this.syncRepository.albumAsset.getBackfill(
|
||||
{ ...options, afterUpdateId: startId, beforeUpdateId: endId },
|
||||
album.id,
|
||||
options.userId,
|
||||
);
|
||||
|
||||
for await (const { updateId, ...data } of backfill) {
|
||||
|
||||
@@ -275,6 +275,19 @@ describe(TagService.name, () => {
|
||||
expect(mocks.tag.getAssetIds).toHaveBeenCalledWith('tag-1', ['asset-1', 'asset-2']);
|
||||
expect(mocks.tag.addAssetIds).toHaveBeenCalledWith('tag-1', ['asset-2']);
|
||||
});
|
||||
|
||||
it('should not tag a partner asset', async () => {
|
||||
mocks.tag.getAssetIds.mockResolvedValue(new Set());
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set());
|
||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
|
||||
await expect(sut.addAssets(authStub.admin, 'tag-1', { ids: ['asset-1'] })).resolves.toEqual([
|
||||
{ id: 'asset-1', success: false, error: BulkIdErrorReason.NO_PERMISSION },
|
||||
]);
|
||||
|
||||
expect(mocks.tag.addAssetIds).not.toHaveBeenCalled();
|
||||
expect(mocks.access.asset.checkPartnerAccess).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAssets', () => {
|
||||
|
||||
@@ -104,7 +104,7 @@ export class TagService extends BaseService {
|
||||
const results = await addAssets(
|
||||
auth,
|
||||
{ access: this.accessRepository, bulk: this.tagRepository },
|
||||
{ parentId: id, assetIds: dto.ids },
|
||||
{ parentId: id, assetIds: dto.ids, permission: Permission.AssetUpdate },
|
||||
);
|
||||
|
||||
for (const { id: assetId, success } of results) {
|
||||
|
||||
@@ -33,14 +33,14 @@ export const getAssetFiles = (files: AssetFile[]) => ({
|
||||
export const addAssets = async (
|
||||
auth: AuthDto,
|
||||
repositories: { access: AccessRepository; bulk: IBulkAsset },
|
||||
dto: { parentId: string; assetIds: string[] },
|
||||
dto: { parentId: string; assetIds: string[]; permission: Permission },
|
||||
) => {
|
||||
const { access, bulk } = repositories;
|
||||
const existingAssetIds = await bulk.getAssetIds(dto.parentId, dto.assetIds);
|
||||
const notPresentAssetIds = dto.assetIds.filter((id) => !existingAssetIds.has(id));
|
||||
const allowedAssetIds = await checkAccess(access, {
|
||||
auth,
|
||||
permission: Permission.AssetShare,
|
||||
permission: dto.permission,
|
||||
ids: notPresentAssetIds,
|
||||
});
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ const possiblyAnimatedImage: Record<string, string[]> = Object.fromEntries(
|
||||
Object.entries(image).filter(([key]) => possiblyAnimatedImageExtensions.has(key)),
|
||||
);
|
||||
|
||||
const heifImageExtensions = new Set(['.avif', '.heic', '.heif', '.hif']);
|
||||
const heifImage: Record<string, string[]> = Object.fromEntries(
|
||||
Object.entries(image).filter(([key]) => heifImageExtensions.has(key)),
|
||||
);
|
||||
|
||||
const extensionOverrides: Record<string, string> = {
|
||||
'image/jpeg': '.jpg',
|
||||
};
|
||||
@@ -147,6 +152,7 @@ export const mimeTypes = {
|
||||
isAsset: (filename: string) => isType(filename, image) || isType(filename, video),
|
||||
isImage: (filename: string) => isType(filename, image),
|
||||
isWebSupportedImage: (filename: string) => isType(filename, webSupportedImage),
|
||||
isHeifImage: (filename: string) => isType(filename, heifImage),
|
||||
isPossiblyAnimatedImage: (filename: string) => isType(filename, possiblyAnimatedImage),
|
||||
isProfile: (filename: string) => isType(filename, profile),
|
||||
isSidecar: (filename: string) => isType(filename, sidecar),
|
||||
|
||||
@@ -270,7 +270,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
||||
it('should sync asset updates for an album shared with you', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user2.id, isFavorite: false });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user2.id, originalFileName: 'before' });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await wait(2);
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
@@ -281,9 +281,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset.id,
|
||||
}),
|
||||
data: expect.objectContaining({ id: asset.id, originalFileName: 'before' }),
|
||||
type: SyncEntityType.AlbumAssetCreateV2,
|
||||
},
|
||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||
@@ -291,24 +289,56 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
||||
|
||||
await ctx.syncAckAll(auth, response);
|
||||
|
||||
// update the asset
|
||||
const assetRepository = ctx.get(AssetRepository);
|
||||
await assetRepository.update({
|
||||
id: asset.id,
|
||||
isFavorite: true,
|
||||
});
|
||||
await assetRepository.update({ id: asset.id, originalFileName: 'after' });
|
||||
|
||||
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||
expect(updateResponse).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset.id,
|
||||
isFavorite: true,
|
||||
}),
|
||||
data: expect.objectContaining({ id: asset.id, originalFileName: 'after' }),
|
||||
type: SyncEntityType.AlbumAssetUpdateV2,
|
||||
},
|
||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should hide isFavorite for album assets owned by another user', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user2.id, isFavorite: true });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Viewer });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||
expect(response).toEqual([
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({ id: asset.id, isFavorite: false }),
|
||||
type: SyncEntityType.AlbumAssetCreateV2,
|
||||
},
|
||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sync isFavorite for album assets owned by the requesting user', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: auth.user.id, isFavorite: true });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Viewer });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||
expect(response).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ id: asset.id, isFavorite: true }),
|
||||
type: SyncEntityType.AlbumAssetCreateV2,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -278,4 +278,21 @@ describe(SyncRequestType.PartnerAssetsV2, () => {
|
||||
await ctx.syncAckAll(auth, newResponse);
|
||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||
});
|
||||
|
||||
it('should hide isFavorite for partner assets', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user2.id, isFavorite: true });
|
||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({ id: asset.id, isFavorite: false }),
|
||||
type: SyncEntityType.PartnerAssetV2,
|
||||
},
|
||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"@formatjs/icu-messageformat-parser": "^3.0.0",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@immich/sdk": "workspace:*",
|
||||
"@immich/ui": "^0.79.2",
|
||||
"@immich/ui": "^0.80.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.4.0",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@noble/hashes": "^2.2.0",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
import UserAvatar from '../shared-components/UserAvatar.svelte';
|
||||
import AlbumListItemDetails from './AlbumListItemDetails.svelte';
|
||||
import DetailPanelPeople from '$lib/components/asset-viewer/DetailPanelPeople.svelte';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
@@ -97,6 +98,8 @@
|
||||
const handleRefreshPeople = async () => {
|
||||
asset = await getAssetInfo({ id: asset.id });
|
||||
assetViewerManager.closeEditFacesPanel();
|
||||
faceManager.clear();
|
||||
await faceManager.getAssetFaces(asset.id);
|
||||
};
|
||||
|
||||
const getAssetFolderHref = (asset: AssetResponseDto) => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import ImageThumbnail from '$lib/components/assets/thumbnail/ImageThumbnail.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import FaceCreateTagModal from '$lib/modals/CreateFaceModal.svelte';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { getNaturalSize, scaleToFit } from '$lib/utils/container-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -326,6 +327,7 @@
|
||||
});
|
||||
|
||||
await assetViewerManager.setAssetId(assetId);
|
||||
faceManager.clear();
|
||||
} catch (error) {
|
||||
handleError(error, 'Error tagging face');
|
||||
} finally {
|
||||
|
||||
@@ -67,6 +67,7 @@ class FaceManager {
|
||||
|
||||
clear() {
|
||||
this.#cleared = true;
|
||||
assetCacheManager.clearFaceCache();
|
||||
this.#data = [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user