mirror of
https://github.com/immich-app/immich.git
synced 2026-06-22 06:42:27 -07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39fe991451 | |||
| cbe34d7931 | |||
| 06c8d5a183 | |||
| ad9817c582 | |||
| 14f6f2c04f | |||
| 327521fa27 | |||
| 3be803d0c0 | |||
| a364b56b1c | |||
| f9db76433e | |||
| 3f2e51c5d4 | |||
| 430a2bbfd3 | |||
| fbb0bc6e39 |
@@ -237,7 +237,7 @@ jobs:
|
||||
run: flutter build ios --config-only --no-codesign
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0
|
||||
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
|
||||
with:
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true
|
||||
|
||||
@@ -13,7 +13,9 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
||||
:::info Android verification
|
||||
Below are the SHA-256 fingerprints for the certificates signing the android applications.
|
||||
|
||||
- Playstore / Github releases:
|
||||
- Google Play releases:
|
||||
`5A:22:C1:83:47:54:05:F5:49:C4:EB:9F:B2:6C:2E:93:A3:EF:9C:57:66:15:0A:7A:F3:8C:8D:3F:E5:15:CC:D6`
|
||||
- GitHub releases:
|
||||
`86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20`
|
||||
- F-Droid releases:
|
||||
`FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF`
|
||||
|
||||
@@ -730,8 +730,8 @@ describe('/albums', () => {
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('User already added'));
|
||||
expect(status).toBe(200);
|
||||
expect(body.albumUsers.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should not be able to add existing user to shared album', async () => {
|
||||
@@ -745,8 +745,8 @@ describe('/albums', () => {
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('User already added'));
|
||||
expect(status).toBe(200);
|
||||
expect(body.albumUsers.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+1
-14
@@ -4,20 +4,7 @@ The Immich mobile app is a Flutter-based solution leveraging the Isar Database f
|
||||
|
||||
## Setup
|
||||
|
||||
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
|
||||
2. Change to the immich directory and trust the mise config with `mise trust`.
|
||||
3. Install tools with mise: `mise install`.
|
||||
4. Run `flutter pub get` to install the dependencies.
|
||||
5. Run `make translation` to generate the translation file.
|
||||
6. Run `flutter run` to start the app.
|
||||
|
||||
## Translation
|
||||
|
||||
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run
|
||||
|
||||
```bash
|
||||
make translation
|
||||
```
|
||||
See [setup](https://docs.immich.app/developer/setup) for how to set up the mobile build environment.
|
||||
|
||||
## Static Analysis
|
||||
|
||||
|
||||
@@ -23,6 +23,6 @@ class ImmichApp : Application() {
|
||||
// as the previous start might have been killed without unlocking.
|
||||
if (BackgroundEngineLock.connectEngines > 0) return@postDelayed
|
||||
BackgroundWorkerApiImpl.enqueueBackgroundWorker(this)
|
||||
}, 5000)
|
||||
}, 15000)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-4
@@ -15,6 +15,7 @@ import androidx.work.ListenableWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import app.alextran.immich.MainActivity
|
||||
import app.alextran.immich.R
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import io.flutter.FlutterInjector
|
||||
@@ -61,6 +62,11 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
}
|
||||
|
||||
override fun startWork(): ListenableFuture<Result> {
|
||||
if (BackgroundWorkerPreferences(ctx).isLocked() && BackgroundEngineLock.connectEngines > 0) {
|
||||
Log.i(TAG, "Foreground engine active, skipping background worker")
|
||||
return Futures.immediateFuture(Result.success())
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starting background upload worker")
|
||||
|
||||
if (!loader.initialized()) {
|
||||
@@ -77,6 +83,10 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
showNotification(notificationConfig.first, notificationConfig.second)
|
||||
|
||||
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||
if (isStopped || isComplete) {
|
||||
return@ensureInitializationCompleteAsync
|
||||
}
|
||||
|
||||
engine = FlutterEngine(ctx)
|
||||
FlutterEngineCache.getInstance().put(BackgroundWorkerApiImpl.ENGINE_CACHE_KEY, engine!!)
|
||||
|
||||
@@ -143,11 +153,17 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
return
|
||||
}
|
||||
|
||||
val api = flutterApi
|
||||
if (api == null) {
|
||||
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
||||
complete(Result.failure())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
||||
if (flutterApi != null) {
|
||||
flutterApi?.cancel {
|
||||
complete(Result.failure())
|
||||
}
|
||||
api.cancel {
|
||||
complete(Result.failure())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[tools]
|
||||
"aqua:flutter/flutter" = "3.44.2"
|
||||
"aqua:flutter/flutter" = "3.44.1"
|
||||
java = "21.0.2"
|
||||
|
||||
[tools."github:CQLabs/homebrew-dcm"]
|
||||
|
||||
+5
-5
@@ -6,7 +6,7 @@ version: 3.0.0-rc.1+3049
|
||||
|
||||
environment:
|
||||
sdk: '>=3.12.0 <4.0.0'
|
||||
flutter: 3.44.2
|
||||
flutter: 3.44.1
|
||||
|
||||
dependencies:
|
||||
async: ^2.13.1
|
||||
@@ -35,7 +35,7 @@ dependencies:
|
||||
flutter_web_auth_2: ^5.0.2
|
||||
fluttertoast: ^8.2.14
|
||||
geolocator: ^14.0.2
|
||||
home_widget: ^0.9.0
|
||||
home_widget: ^0.8.1
|
||||
hooks_riverpod: ^2.6.1
|
||||
http: ^1.6.0
|
||||
image_picker: ^1.2.1
|
||||
@@ -44,7 +44,7 @@ dependencies:
|
||||
intl: ^0.20.2
|
||||
local_auth: ^2.3.0
|
||||
logging: ^1.3.0
|
||||
maplibre_gl: ^0.26.0
|
||||
maplibre_gl: ^0.22.0
|
||||
native_video_player:
|
||||
git:
|
||||
url: https://github.com/immich-app/native_video_player
|
||||
@@ -68,10 +68,10 @@ dependencies:
|
||||
sliver_tools: ^0.2.12
|
||||
stream_transform: ^2.1.1
|
||||
sqlite3: ^3.3.2
|
||||
sqlite_async: 0.14.3
|
||||
sqlite_async: 0.14.2
|
||||
sqlite3_connection_pool: ^0.2.6
|
||||
thumbhash: 0.1.0+1
|
||||
timezone: ^0.11.0
|
||||
timezone: ^0.9.4
|
||||
url_launcher: ^6.3.2
|
||||
uuid: ^4.5.3
|
||||
wakelock_plus: ^1.3.3
|
||||
|
||||
@@ -16508,7 +16508,9 @@
|
||||
},
|
||||
"albumThumbnailAssetId": {
|
||||
"description": "Thumbnail asset ID",
|
||||
"format": "uuid",
|
||||
"nullable": true,
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"albumUsers": {
|
||||
@@ -16551,6 +16553,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isActivityEnabled": {
|
||||
@@ -16793,6 +16797,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "API key ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -17001,6 +17007,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isTrashed": {
|
||||
@@ -17379,6 +17387,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
@@ -17494,6 +17504,8 @@
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Asset media ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
@@ -17562,6 +17574,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -17809,7 +17823,9 @@
|
||||
},
|
||||
"duplicateId": {
|
||||
"description": "Duplicate group ID",
|
||||
"format": "uuid",
|
||||
"nullable": true,
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"duration": {
|
||||
@@ -17845,6 +17861,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
@@ -17923,6 +17941,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"people": {
|
||||
@@ -18020,10 +18040,14 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"primaryAssetId": {
|
||||
"description": "Primary asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -18159,6 +18183,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
@@ -18337,6 +18363,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -18441,6 +18469,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -18600,6 +18630,8 @@
|
||||
"assetIds": {
|
||||
"description": "Asset IDs in this archive",
|
||||
"items": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
@@ -18785,6 +18817,8 @@
|
||||
},
|
||||
"duplicateId": {
|
||||
"description": "Duplicate group ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"suggestedKeepAssetIds": {
|
||||
@@ -19101,6 +19135,8 @@
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Integrity report item id",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
@@ -19275,6 +19311,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Library ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"importPaths": {
|
||||
@@ -19290,6 +19328,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"refreshedAt": {
|
||||
@@ -19444,6 +19484,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -19634,6 +19676,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"lat": {
|
||||
@@ -19834,6 +19878,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isSaved": {
|
||||
@@ -19849,6 +19895,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"seenAt": {
|
||||
@@ -20310,6 +20358,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Notification ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"level": {
|
||||
@@ -20754,6 +20804,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -20989,6 +21041,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -21244,6 +21298,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Plugin ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"methods": {
|
||||
@@ -22639,6 +22695,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Version history entry ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
@@ -22743,6 +22801,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Session ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isPendingSyncReset": {
|
||||
@@ -22800,6 +22860,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Session ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isPendingSyncReset": {
|
||||
@@ -23022,6 +23084,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Shared link ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -23047,6 +23111,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23388,10 +23454,14 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"primaryAssetId": {
|
||||
"description": "Primary asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23665,6 +23735,8 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23677,10 +23749,14 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23694,10 +23770,14 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23711,10 +23791,14 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23728,6 +23812,8 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
@@ -23735,6 +23821,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23760,6 +23848,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isActivityEnabled": {
|
||||
@@ -23775,6 +23865,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"thumbnailAssetId": {
|
||||
@@ -23818,6 +23910,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isActivityEnabled": {
|
||||
@@ -23860,6 +23954,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23872,6 +23968,8 @@
|
||||
"properties": {
|
||||
"editId": {
|
||||
"description": "Edit ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23887,10 +23985,14 @@
|
||||
},
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "Edit ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"parameters": {
|
||||
@@ -23918,6 +24020,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"city": {
|
||||
@@ -24095,6 +24199,8 @@
|
||||
"properties": {
|
||||
"assetFaceId": {
|
||||
"description": "Asset face ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -24107,6 +24213,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"boundingBoxX1": {
|
||||
@@ -24135,6 +24243,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset face ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"imageHeight": {
|
||||
@@ -24177,6 +24287,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"boundingBoxX1": {
|
||||
@@ -24213,6 +24325,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset face ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"imageHeight": {
|
||||
@@ -24261,6 +24375,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -24278,6 +24394,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -24326,6 +24444,8 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"boxScore": {
|
||||
@@ -24335,6 +24455,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "OCR entry ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isVisible": {
|
||||
@@ -24461,6 +24583,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isEdited": {
|
||||
@@ -24495,6 +24619,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"stackId": {
|
||||
@@ -24599,6 +24725,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isEdited": {
|
||||
@@ -24633,6 +24761,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"stackId": {
|
||||
@@ -24711,6 +24841,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isAdmin": {
|
||||
@@ -24845,10 +24977,14 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"memoryId": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -24862,10 +24998,14 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"memoryId": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -24879,6 +25019,8 @@
|
||||
"properties": {
|
||||
"memoryId": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -24919,6 +25061,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isSaved": {
|
||||
@@ -24934,6 +25078,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"seenAt": {
|
||||
@@ -24983,10 +25129,14 @@
|
||||
"properties": {
|
||||
"sharedById": {
|
||||
"description": "Shared by ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"sharedWithId": {
|
||||
"description": "Shared with ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25004,10 +25154,14 @@
|
||||
},
|
||||
"sharedById": {
|
||||
"description": "Shared by ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"sharedWithId": {
|
||||
"description": "Shared with ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25022,6 +25176,8 @@
|
||||
"properties": {
|
||||
"personId": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25059,6 +25215,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -25075,6 +25233,8 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
@@ -25140,6 +25300,8 @@
|
||||
"properties": {
|
||||
"stackId": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25159,14 +25321,20 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"primaryAssetId": {
|
||||
"description": "Primary asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
@@ -25209,6 +25377,8 @@
|
||||
"properties": {
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25224,6 +25394,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25240,6 +25412,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
@@ -25283,6 +25457,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -26466,6 +26642,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Tag ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -26989,6 +27167,8 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"userName": {
|
||||
@@ -27631,6 +27811,8 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Workflow ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ FROM builder AS plugins
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise
|
||||
COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./mise.toml ./mise.toml
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
FROM ghcr.io/immich-app/base-server-dev:202606161235@sha256:9f88b07acc8b7bf37a1dd3d5a19193f664443eaaab4e08e9f9341414c5e4b23f AS dev
|
||||
|
||||
|
||||
COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise
|
||||
COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise
|
||||
|
||||
RUN echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \
|
||||
echo "store-dir=/buildcache/pnpm-store" >> /usr/local/etc/npmrc && \
|
||||
|
||||
@@ -51,6 +51,7 @@ export default typescriptEslint.config([
|
||||
'unicorn/no-array-sort': 'off',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/switch-exhaustiveness-check': ['error', { considerDefaultExhaustiveForUnions: true }],
|
||||
'require-await': 'off',
|
||||
'@typescript-eslint/require-await': 'error',
|
||||
curly: 2,
|
||||
|
||||
@@ -28,9 +28,13 @@ export class SchemaCheck extends CommandRunner {
|
||||
}
|
||||
|
||||
case 'missing': {
|
||||
console.log(` - ${migration.name} exists, but has not been applied to the database`);
|
||||
console.log(` - ${migration.name} exists on disk, but has not been applied to the database`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'applied': {
|
||||
break; // happy path, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PersonPathType,
|
||||
RawExtractedFormat,
|
||||
StorageFolder,
|
||||
UserPathType,
|
||||
} from 'src/enum';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
@@ -327,13 +328,19 @@ export class StorageCore {
|
||||
case AssetFileType.EncodedVideo:
|
||||
case AssetFileType.Thumbnail:
|
||||
case AssetFileType.Preview:
|
||||
case AssetFileType.Sidecar: {
|
||||
case AssetFileType.Sidecar:
|
||||
case AssetPathType.EncodedVideo: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: pathType as AssetFileType, path: newPath });
|
||||
}
|
||||
|
||||
case PersonPathType.Face: {
|
||||
return this.personRepository.update({ id, thumbnailPath: newPath });
|
||||
}
|
||||
|
||||
case UserPathType.Profile: {
|
||||
this.logger.warn('Unexpected path type:', pathType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,21 +100,21 @@ const AlbumUserResponseSchema = z
|
||||
|
||||
const ContributorCountResponseSchema = z
|
||||
.object({
|
||||
userId: z.string().describe('User ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
assetCount: z.int().min(0).describe('Number of assets contributed'),
|
||||
})
|
||||
.meta({ id: 'ContributorCountResponseDto' });
|
||||
|
||||
export const AlbumResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Album ID'),
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
albumName: z.string().describe('Album name'),
|
||||
description: z.string().describe('Album description'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
createdAt: z.string().meta({ format: 'date-time' }).describe('Creation date'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'),
|
||||
albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'),
|
||||
albumThumbnailAssetId: z.uuidv4().nullable().describe('Thumbnail asset ID'),
|
||||
shared: z.boolean().describe('Is shared album'),
|
||||
albumUsers: z
|
||||
.array(AlbumUserResponseSchema)
|
||||
|
||||
@@ -21,7 +21,7 @@ const ApiKeyUpdateSchema = z
|
||||
|
||||
const ApiKeyResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('API key ID'),
|
||||
id: z.uuidv4().describe('API key ID'),
|
||||
name: z.string().describe('API key name'),
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
updatedAt: isoDatetimeToDate.describe('Last update date'),
|
||||
|
||||
@@ -16,7 +16,7 @@ const AssetIdErrorReasonSchema = z
|
||||
/** @deprecated Use `BulkIdResponseDto` instead */
|
||||
const AssetIdsResponseSchema = z
|
||||
.object({
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
success: z.boolean().describe('Whether operation succeeded'),
|
||||
error: AssetIdErrorReasonSchema.optional(),
|
||||
})
|
||||
@@ -43,7 +43,7 @@ export const BulkIdsSchema = z
|
||||
|
||||
const BulkIdResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('ID'),
|
||||
id: z.uuidv4().describe('ID'),
|
||||
success: z.boolean().describe('Whether operation succeeded'),
|
||||
error: BulkIdErrorReasonSchema.optional(),
|
||||
errorMessage: z.string().optional(),
|
||||
|
||||
@@ -11,7 +11,7 @@ const AssetMediaStatusSchema = z.enum(AssetMediaStatus).describe('Upload status'
|
||||
const AssetMediaResponseSchema = z
|
||||
.object({
|
||||
status: AssetMediaStatusSchema,
|
||||
id: z.string().describe('Asset media ID'),
|
||||
id: z.uuidv4().describe('Asset media ID'),
|
||||
})
|
||||
.meta({ id: 'AssetMediaResponseDto' });
|
||||
|
||||
@@ -34,7 +34,7 @@ const AssetRejectReasonSchema = z
|
||||
|
||||
const AssetBulkUploadCheckResultSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Asset ID'),
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
action: AssetUploadActionSchema,
|
||||
reason: AssetRejectReasonSchema.optional(),
|
||||
assetId: z.string().optional().describe('Existing asset ID if duplicate'),
|
||||
|
||||
@@ -24,7 +24,7 @@ import z from 'zod';
|
||||
|
||||
const SanitizedAssetResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Asset ID'),
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
type: AssetTypeSchema,
|
||||
thumbhash: z
|
||||
.string()
|
||||
@@ -52,8 +52,8 @@ export class SanitizedAssetResponseDto extends createZodDto(SanitizedAssetRespon
|
||||
|
||||
const AssetStackResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Stack ID'),
|
||||
primaryAssetId: z.string().describe('Primary asset ID'),
|
||||
id: z.uuidv4().describe('Stack ID'),
|
||||
primaryAssetId: z.uuidv4().describe('Primary asset ID'),
|
||||
assetCount: z.int().min(0).describe('Number of assets in stack'),
|
||||
})
|
||||
.meta({ id: 'AssetStackResponseDto' });
|
||||
@@ -65,7 +65,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend(
|
||||
.string()
|
||||
.meta({ format: 'date-time' })
|
||||
.describe('The UTC timestamp when the asset was originally uploaded to Immich.'),
|
||||
ownerId: z.string().describe('Owner user ID'),
|
||||
ownerId: z.uuidv4().describe('Owner user ID'),
|
||||
owner: UserResponseSchema.optional(),
|
||||
libraryId: z
|
||||
.uuidv4()
|
||||
@@ -103,7 +103,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend(
|
||||
people: z.array(PersonResponseSchema).optional(),
|
||||
checksum: z.string().describe('Base64 encoded SHA1 hash'),
|
||||
stack: AssetStackResponseSchema.nullish(),
|
||||
duplicateId: z.string().nullish().describe('Duplicate group ID'),
|
||||
duplicateId: z.uuidv4().nullish().describe('Duplicate group ID'),
|
||||
resized: z
|
||||
.boolean()
|
||||
.optional()
|
||||
|
||||
@@ -148,7 +148,7 @@ const AssetMetadataResponseSchema = z
|
||||
.meta({ id: 'AssetMetadataResponseDto' });
|
||||
|
||||
const AssetMetadataBulkResponseSchema = AssetMetadataResponseSchema.extend({
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
}).meta({ id: 'AssetMetadataBulkResponseDto' });
|
||||
|
||||
const AssetCopySchema = z
|
||||
|
||||
@@ -29,7 +29,7 @@ const LoginCredentialSchema = z
|
||||
const LoginResponseSchema = z
|
||||
.object({
|
||||
accessToken: z.string().describe('Access token'),
|
||||
userId: z.string().describe('User ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userEmail: toEmail.describe('User email'),
|
||||
name: z.string().describe('User name'),
|
||||
profileImagePath: z.string().describe('Profile image path'),
|
||||
|
||||
@@ -14,7 +14,7 @@ const DownloadInfoSchema = z
|
||||
const DownloadArchiveInfoSchema = z
|
||||
.object({
|
||||
size: z.int().describe('Archive size in bytes'),
|
||||
assetIds: z.array(z.string()).describe('Asset IDs in this archive'),
|
||||
assetIds: z.array(z.uuidv4()).describe('Asset IDs in this archive'),
|
||||
})
|
||||
.meta({ id: 'DownloadArchiveInfo' });
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import z from 'zod';
|
||||
|
||||
const DuplicateResponseSchema = z
|
||||
.object({
|
||||
duplicateId: z.string().describe('Duplicate group ID'),
|
||||
duplicateId: z.uuidv4().describe('Duplicate group ID'),
|
||||
assets: z.array(AssetResponseSchema).describe('Duplicate assets'),
|
||||
suggestedKeepAssetIds: z.array(z.uuidv4()).describe('Suggested asset IDs to keep based on file size and EXIF data'),
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ const IntegrityDeleteReportSchema = z.object({ type: IntegrityReport }).meta({ i
|
||||
export class IntegrityDeleteReportDto extends createZodDto(IntegrityDeleteReportSchema) {}
|
||||
|
||||
const IntegrityReportResponseItemSchema = z.object({
|
||||
id: z.string().describe('Integrity report item id'),
|
||||
id: z.uuidv4().describe('Integrity report item id'),
|
||||
type: IntegrityReportSchema,
|
||||
path: z.string().describe('Integrity report item path'),
|
||||
});
|
||||
|
||||
@@ -62,8 +62,8 @@ const ValidateLibraryResponseSchema = z
|
||||
|
||||
const LibraryResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Library ID'),
|
||||
ownerId: z.string().describe('Owner user ID'),
|
||||
id: z.uuidv4().describe('Library ID'),
|
||||
ownerId: z.uuidv4().describe('Owner user ID'),
|
||||
name: z.string().describe('Library name'),
|
||||
assetCount: z.int().describe('Number of assets'),
|
||||
importPaths: z.array(z.string()).describe('Import paths'),
|
||||
|
||||
@@ -30,7 +30,7 @@ const MapMarkerSchema = z
|
||||
|
||||
const MapMarkerResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Asset ID'),
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
lat: z.number().meta({ format: 'double' }).describe('Latitude'),
|
||||
lon: z.number().meta({ format: 'double' }).describe('Longitude'),
|
||||
city: z.string().nullable().describe('City name'),
|
||||
|
||||
@@ -59,7 +59,7 @@ const MemoryStatisticsResponseSchema = z
|
||||
|
||||
const MemoryResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Memory ID'),
|
||||
id: z.uuidv4().describe('Memory ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
updatedAt: isoDatetimeToDate.describe('Last update date'),
|
||||
deletedAt: isoDatetimeToDate.optional().describe('Deletion date'),
|
||||
@@ -67,7 +67,7 @@ const MemoryResponseSchema = z
|
||||
seenAt: isoDatetimeToDate.optional().describe('Date when memory was seen'),
|
||||
showAt: isoDatetimeToDate.optional().describe('Date when memory should be shown'),
|
||||
hideAt: isoDatetimeToDate.optional().describe('Date when memory should be hidden'),
|
||||
ownerId: z.string().describe('Owner user ID'),
|
||||
ownerId: z.uuidv4().describe('Owner user ID'),
|
||||
type: MemoryTypeSchema,
|
||||
data: OnThisDaySchema,
|
||||
isSaved: z.boolean().describe('Is memory saved'),
|
||||
|
||||
@@ -24,7 +24,7 @@ const TemplateSchema = z
|
||||
|
||||
const NotificationSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Notification ID'),
|
||||
id: z.uuidv4().describe('Notification ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
level: NotificationLevelSchema,
|
||||
type: NotificationTypeSchema,
|
||||
|
||||
@@ -33,7 +33,7 @@ const PersonUpdateSchema = PersonCreateSchema.extend({
|
||||
}).meta({ id: 'PersonUpdateDto' });
|
||||
|
||||
const PeopleUpdateItemSchema = PersonUpdateSchema.extend({
|
||||
id: z.string().describe('Person ID'),
|
||||
id: z.uuidv4().describe('Person ID'),
|
||||
}).meta({ id: 'PeopleUpdateItem' });
|
||||
|
||||
const PeopleUpdateSchema = z
|
||||
@@ -60,7 +60,7 @@ const PersonSearchSchema = z
|
||||
|
||||
export const PersonResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Person ID'),
|
||||
id: z.uuidv4().describe('Person ID'),
|
||||
name: z.string().describe('Person name'),
|
||||
// TODO: use `isoDateToDate` when using `ZodSerializerDto` on the controllers.
|
||||
birthDate: z.string().meta({ format: 'date' }).describe('Person date of birth').nullable(),
|
||||
|
||||
@@ -32,7 +32,7 @@ const PluginMethodResponseSchema = z
|
||||
|
||||
const PluginResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Plugin ID'),
|
||||
id: z.uuidv4().describe('Plugin ID'),
|
||||
name: z.string().describe('Plugin name'),
|
||||
title: z.string().describe('Plugin title'),
|
||||
description: z.string().describe('Plugin description'),
|
||||
|
||||
@@ -73,7 +73,7 @@ const ServerVersionResponseSchema = z
|
||||
|
||||
const ServerVersionHistoryResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Version history entry ID'),
|
||||
id: z.uuidv4().describe('Version history entry ID'),
|
||||
createdAt: isoDatetimeToDate.describe('When this version was first seen'),
|
||||
version: z.string().describe('Version string'),
|
||||
})
|
||||
@@ -81,7 +81,7 @@ const ServerVersionHistoryResponseSchema = z
|
||||
|
||||
const UsageByUserSchema = z
|
||||
.object({
|
||||
userId: z.string().describe('User ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userName: z.string().describe('User name'),
|
||||
photos: z.int().describe('Number of photos'),
|
||||
videos: z.int().describe('Number of videos'),
|
||||
|
||||
@@ -18,7 +18,7 @@ const SessionUpdateSchema = z
|
||||
|
||||
const SessionResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Session ID'),
|
||||
id: z.uuidv4().describe('Session ID'),
|
||||
createdAt: z.string().describe('Creation date'),
|
||||
updatedAt: z.string().describe('Last update date'),
|
||||
expiresAt: z.string().optional().describe('Expiration date'),
|
||||
|
||||
@@ -53,10 +53,10 @@ const SharedLinkLoginSchema = z
|
||||
|
||||
const SharedLinkResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Shared link ID'),
|
||||
id: z.uuidv4().describe('Shared link ID'),
|
||||
description: z.string().nullable().describe('Link description'),
|
||||
password: z.string().nullable().describe('Has password'),
|
||||
userId: z.string().describe('Owner user ID'),
|
||||
userId: z.uuidv4().describe('Owner user ID'),
|
||||
key: z.string().describe('Encryption key (base64url)'),
|
||||
type: SharedLinkTypeSchema,
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
|
||||
@@ -24,8 +24,8 @@ const StackUpdateSchema = z
|
||||
|
||||
const StackResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Stack ID'),
|
||||
primaryAssetId: z.string().describe('Primary asset ID'),
|
||||
id: z.uuidv4().describe('Stack ID'),
|
||||
primaryAssetId: z.uuidv4().describe('Primary asset ID'),
|
||||
assets: z.array(AssetResponseSchema),
|
||||
})
|
||||
.describe('Stack response')
|
||||
|
||||
+50
-50
@@ -19,7 +19,7 @@ import z from 'zod';
|
||||
|
||||
const SyncUserV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('User ID'),
|
||||
id: z.uuidv4().describe('User ID'),
|
||||
name: z.string().describe('User name'),
|
||||
email: z.string().describe('User email'),
|
||||
avatarColor: UserAvatarColorSchema.nullish(),
|
||||
@@ -40,27 +40,27 @@ const SyncAuthUserV1Schema = SyncUserV1Schema.merge(
|
||||
}),
|
||||
).meta({ id: 'SyncAuthUserV1' });
|
||||
|
||||
const SyncUserDeleteV1Schema = z.object({ userId: z.string().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' });
|
||||
const SyncUserDeleteV1Schema = z.object({ userId: z.uuidv4().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' });
|
||||
|
||||
const SyncPartnerV1Schema = z
|
||||
.object({
|
||||
sharedById: z.string().describe('Shared by ID'),
|
||||
sharedWithId: z.string().describe('Shared with ID'),
|
||||
sharedById: z.uuidv4().describe('Shared by ID'),
|
||||
sharedWithId: z.uuidv4().describe('Shared with ID'),
|
||||
inTimeline: z.boolean().describe('In timeline'),
|
||||
})
|
||||
.meta({ id: 'SyncPartnerV1' });
|
||||
|
||||
const SyncPartnerDeleteV1Schema = z
|
||||
.object({
|
||||
sharedById: z.string().describe('Shared by ID'),
|
||||
sharedWithId: z.string().describe('Shared with ID'),
|
||||
sharedById: z.uuidv4().describe('Shared by ID'),
|
||||
sharedWithId: z.uuidv4().describe('Shared with ID'),
|
||||
})
|
||||
.meta({ id: 'SyncPartnerDeleteV1' });
|
||||
|
||||
const SyncAssetV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Asset ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
originalFileName: z.string().describe('Original file name'),
|
||||
thumbhash: z.string().nullable().describe('Thumbhash'),
|
||||
checksum: z.string().describe('Checksum'),
|
||||
@@ -84,8 +84,8 @@ const SyncAssetV1Schema = z
|
||||
|
||||
const SyncAssetV2Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Asset ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
originalFileName: z.string().describe('Original file name'),
|
||||
thumbhash: z.string().nullable().describe('Thumbhash'),
|
||||
checksum: z.string().describe('Checksum'),
|
||||
@@ -123,12 +123,12 @@ export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {}
|
||||
export class SyncAssetV2 extends createZodDto(SyncAssetV2Schema) {}
|
||||
|
||||
const SyncAssetDeleteV1Schema = z
|
||||
.object({ assetId: z.string().describe('Asset ID') })
|
||||
.object({ assetId: z.uuidv4().describe('Asset ID') })
|
||||
.meta({ id: 'SyncAssetDeleteV1' });
|
||||
|
||||
const SyncAssetExifV1Schema = z
|
||||
.object({
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
description: z.string().nullable().describe('Description'),
|
||||
exifImageWidth: z.int().nullable().describe('Exif image width'),
|
||||
exifImageHeight: z.int().nullable().describe('Exif image height'),
|
||||
@@ -158,7 +158,7 @@ const SyncAssetExifV1Schema = z
|
||||
|
||||
const SyncAssetMetadataV1Schema = z
|
||||
.object({
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
key: z.string().describe('Key'),
|
||||
value: z.record(z.string(), z.unknown()).describe('Value'),
|
||||
})
|
||||
@@ -166,15 +166,15 @@ const SyncAssetMetadataV1Schema = z
|
||||
|
||||
const SyncAssetMetadataDeleteV1Schema = z
|
||||
.object({
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
key: z.string().describe('Key'),
|
||||
})
|
||||
.meta({ id: 'SyncAssetMetadataDeleteV1' });
|
||||
|
||||
const SyncAssetEditV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Edit ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
id: z.uuidv4().describe('Edit ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
action: AssetEditActionSchema,
|
||||
parameters: z.record(z.string(), z.unknown()).describe('Edit parameters'),
|
||||
sequence: z.int().describe('Edit sequence'),
|
||||
@@ -182,7 +182,7 @@ const SyncAssetEditV1Schema = z
|
||||
.meta({ id: 'SyncAssetEditV1' });
|
||||
|
||||
const SyncAssetEditDeleteV1Schema = z
|
||||
.object({ editId: z.string().describe('Edit ID') })
|
||||
.object({ editId: z.uuidv4().describe('Edit ID') })
|
||||
.meta({ id: 'SyncAssetEditDeleteV1' });
|
||||
|
||||
@ExtraModel()
|
||||
@@ -199,28 +199,28 @@ export class SyncAssetEditV1 extends createZodDto(SyncAssetEditV1Schema) {}
|
||||
class SyncAssetEditDeleteV1 extends createZodDto(SyncAssetEditDeleteV1Schema) {}
|
||||
|
||||
const SyncAlbumDeleteV1Schema = z
|
||||
.object({ albumId: z.string().describe('Album ID') })
|
||||
.object({ albumId: z.uuidv4().describe('Album ID') })
|
||||
.meta({ id: 'SyncAlbumDeleteV1' });
|
||||
|
||||
const SyncAlbumUserDeleteV1Schema = z
|
||||
.object({
|
||||
albumId: z.string().describe('Album ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
})
|
||||
.meta({ id: 'SyncAlbumUserDeleteV1' });
|
||||
|
||||
const SyncAlbumUserV1Schema = z
|
||||
.object({
|
||||
albumId: z.string().describe('Album ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
role: AlbumUserRoleSchema,
|
||||
})
|
||||
.meta({ id: 'SyncAlbumUserV1' });
|
||||
|
||||
const SyncAlbumV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Album ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
name: z.string().describe('Album name'),
|
||||
description: z.string().describe('Album description'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
@@ -233,7 +233,7 @@ const SyncAlbumV1Schema = z
|
||||
|
||||
const SyncAlbumV2Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Album ID'),
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
name: z.string().describe('Album name'),
|
||||
description: z.string().describe('Album description'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
@@ -246,15 +246,15 @@ const SyncAlbumV2Schema = z
|
||||
|
||||
const SyncAlbumToAssetV1Schema = z
|
||||
.object({
|
||||
albumId: z.string().describe('Album ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncAlbumToAssetV1' });
|
||||
|
||||
const SyncAlbumToAssetDeleteV1Schema = z
|
||||
.object({
|
||||
albumId: z.string().describe('Album ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncAlbumToAssetDeleteV1' });
|
||||
|
||||
@@ -284,11 +284,11 @@ export function syncAlbumV2ToV1(
|
||||
|
||||
const SyncMemoryV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Memory ID'),
|
||||
id: z.uuidv4().describe('Memory ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||
deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
type: MemoryTypeSchema,
|
||||
data: z.record(z.string(), z.unknown()).describe('Data'),
|
||||
isSaved: z.boolean().describe('Is saved'),
|
||||
@@ -300,43 +300,43 @@ const SyncMemoryV1Schema = z
|
||||
.meta({ id: 'SyncMemoryV1' });
|
||||
|
||||
const SyncMemoryDeleteV1Schema = z
|
||||
.object({ memoryId: z.string().describe('Memory ID') })
|
||||
.object({ memoryId: z.uuidv4().describe('Memory ID') })
|
||||
.meta({ id: 'SyncMemoryDeleteV1' });
|
||||
|
||||
const SyncMemoryAssetV1Schema = z
|
||||
.object({
|
||||
memoryId: z.string().describe('Memory ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
memoryId: z.uuidv4().describe('Memory ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncMemoryAssetV1' });
|
||||
|
||||
const SyncMemoryAssetDeleteV1Schema = z
|
||||
.object({
|
||||
memoryId: z.string().describe('Memory ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
memoryId: z.uuidv4().describe('Memory ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncMemoryAssetDeleteV1' });
|
||||
|
||||
const SyncStackV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Stack ID'),
|
||||
id: z.uuidv4().describe('Stack ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||
primaryAssetId: z.string().describe('Primary asset ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
primaryAssetId: z.uuidv4().describe('Primary asset ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
})
|
||||
.meta({ id: 'SyncStackV1' });
|
||||
|
||||
const SyncStackDeleteV1Schema = z
|
||||
.object({ stackId: z.string().describe('Stack ID') })
|
||||
.object({ stackId: z.uuidv4().describe('Stack ID') })
|
||||
.meta({ id: 'SyncStackDeleteV1' });
|
||||
|
||||
const SyncPersonV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Person ID'),
|
||||
id: z.uuidv4().describe('Person ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
name: z.string().describe('Person name'),
|
||||
birthDate: isoDatetimeToDate.nullable().describe('Birth date'),
|
||||
isHidden: z.boolean().describe('Is hidden'),
|
||||
@@ -347,13 +347,13 @@ const SyncPersonV1Schema = z
|
||||
.meta({ id: 'SyncPersonV1' });
|
||||
|
||||
const SyncPersonDeleteV1Schema = z
|
||||
.object({ personId: z.string().describe('Person ID') })
|
||||
.object({ personId: z.uuidv4().describe('Person ID') })
|
||||
.meta({ id: 'SyncPersonDeleteV1' });
|
||||
|
||||
const SyncAssetFaceV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('Asset face ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
id: z.uuidv4().describe('Asset face ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
personId: z.string().nullable().describe('Person ID'),
|
||||
imageWidth: z.int().describe('Image width'),
|
||||
imageHeight: z.int().describe('Image height'),
|
||||
@@ -371,12 +371,12 @@ const SyncAssetFaceV2Schema = SyncAssetFaceV1Schema.extend({
|
||||
}).meta({ id: 'SyncAssetFaceV2' });
|
||||
|
||||
const SyncAssetFaceDeleteV1Schema = z
|
||||
.object({ assetFaceId: z.string().describe('Asset face ID') })
|
||||
.object({ assetFaceId: z.uuidv4().describe('Asset face ID') })
|
||||
.meta({ id: 'SyncAssetFaceDeleteV1' });
|
||||
|
||||
const SyncUserMetadataV1Schema = z
|
||||
.object({
|
||||
userId: z.string().describe('User ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
key: UserMetadataKeySchema,
|
||||
value: z.record(z.string(), z.unknown()).describe('User metadata value'),
|
||||
})
|
||||
@@ -384,7 +384,7 @@ const SyncUserMetadataV1Schema = z
|
||||
|
||||
const SyncUserMetadataDeleteV1Schema = z
|
||||
.object({
|
||||
userId: z.string().describe('User ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
key: UserMetadataKeySchema,
|
||||
})
|
||||
.meta({ id: 'SyncUserMetadataDeleteV1' });
|
||||
@@ -404,8 +404,8 @@ class SyncMemoryAssetDeleteV1 extends createZodDto(SyncMemoryAssetDeleteV1Schema
|
||||
|
||||
const SyncAssetOcrV1Schema = z
|
||||
.object({
|
||||
id: z.string().describe('OCR entry ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
id: z.uuidv4().describe('OCR entry ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
|
||||
x1: z.number().meta({ format: 'double' }).describe('Top-left X coordinate (normalized 0–1)'),
|
||||
y1: z.number().meta({ format: 'double' }).describe('Top-left Y coordinate (normalized 0–1)'),
|
||||
|
||||
@@ -40,7 +40,7 @@ const TagBulkAssetsResponseSchema = z
|
||||
|
||||
export const TagResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Tag ID'),
|
||||
id: z.uuidv4().describe('Tag ID'),
|
||||
parentId: z.string().optional().describe('Parent tag ID'),
|
||||
name: z.string().describe('Tag name'),
|
||||
value: z.string().describe('Tag value (full path)'),
|
||||
|
||||
@@ -11,7 +11,7 @@ export class CreateProfileImageDto {
|
||||
|
||||
const CreateProfileImageResponseSchema = z
|
||||
.object({
|
||||
userId: z.string().describe('User ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
profileChangedAt: isoDatetimeToDate.describe('Profile image change date'),
|
||||
profileImagePath: z.string().describe('Profile image file path'),
|
||||
})
|
||||
|
||||
@@ -58,7 +58,7 @@ const WorkflowUpdateSchema = z
|
||||
|
||||
const WorkflowResponseSchema = z
|
||||
.object({
|
||||
id: z.string().describe('Workflow ID'),
|
||||
id: z.uuidv4().describe('Workflow ID'),
|
||||
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
|
||||
name: z.string().nullable().describe('Workflow name'),
|
||||
description: z.string().nullable().describe('Workflow description'),
|
||||
|
||||
@@ -281,17 +281,20 @@ export class MaintenanceWorkerService {
|
||||
|
||||
async runAction(action: SetMaintenanceModeDto) {
|
||||
switch (action.action) {
|
||||
case MaintenanceAction.Start: {
|
||||
case MaintenanceAction.Start:
|
||||
case MaintenanceAction.SelectDatabaseRestore: {
|
||||
return;
|
||||
}
|
||||
case MaintenanceAction.End: {
|
||||
return this.endMaintenance();
|
||||
}
|
||||
case MaintenanceAction.SelectDatabaseRestore: {
|
||||
return;
|
||||
case MaintenanceAction.RestoreDatabase: {
|
||||
return this.runRestoreDatabase(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runRestoreDatabase(action: SetMaintenanceModeDto) {
|
||||
const lock = await this.databaseRepository.tryLock(DatabaseLock.MaintenanceOperation);
|
||||
if (!lock) {
|
||||
return;
|
||||
|
||||
@@ -243,14 +243,16 @@ const getEnv = (): EnvData => {
|
||||
};
|
||||
|
||||
let vectorExtension: VectorExtension | undefined;
|
||||
switch (dto.DB_VECTOR_EXTENSION) {
|
||||
case 'pgvector': {
|
||||
vectorExtension = DatabaseExtension.Vector;
|
||||
break;
|
||||
}
|
||||
case 'vectorchord': {
|
||||
vectorExtension = DatabaseExtension.VectorChord;
|
||||
break;
|
||||
if (dto.DB_VECTOR_EXTENSION) {
|
||||
switch (dto.DB_VECTOR_EXTENSION) {
|
||||
case 'pgvector': {
|
||||
vectorExtension = DatabaseExtension.Vector;
|
||||
break;
|
||||
}
|
||||
case 'vectorchord': {
|
||||
vectorExtension = DatabaseExtension.VectorChord;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -478,8 +478,10 @@ export class MediaRepository {
|
||||
case 'av1': {
|
||||
return this.parseEnum(Av1Profile, profile);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private compareStreams(a: FfprobeStream, b: FfprobeStream): number {
|
||||
|
||||
@@ -472,17 +472,19 @@ describe(AlbumService.name, () => {
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw an error if the userId is already added', async () => {
|
||||
it('should skip if the userId is already added', async () => {
|
||||
const userId = newUuid();
|
||||
const album = AlbumFactory.from().albumUser({ userId }).build();
|
||||
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||
await expect(
|
||||
sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] })).resolves.toEqual(
|
||||
expect.objectContaining({ id: album.id }),
|
||||
);
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
expect(mocks.user.get).not.toHaveBeenCalled();
|
||||
expect(mocks.albumUser.create).not.toHaveBeenCalled();
|
||||
expect(mocks.event.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw an error if the userId does not exist', async () => {
|
||||
@@ -498,7 +500,7 @@ describe(AlbumService.name, () => {
|
||||
expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {});
|
||||
});
|
||||
|
||||
it('should throw an error if the userId is the ownerId', async () => {
|
||||
it('should skip if the userId is the ownerId', async () => {
|
||||
const album = AlbumFactory.create();
|
||||
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||
@@ -507,9 +509,11 @@ describe(AlbumService.name, () => {
|
||||
sut.addUsers(AuthFactory.create(owner), album.id, {
|
||||
albumUsers: [{ userId: owner.id }],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
).resolves.toEqual(expect.objectContaining({ id: album.id }));
|
||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||
expect(mocks.user.get).not.toHaveBeenCalled();
|
||||
expect(mocks.albumUser.create).not.toHaveBeenCalled();
|
||||
expect(mocks.event.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add valid shared users', async () => {
|
||||
@@ -534,6 +538,35 @@ describe(AlbumService.name, () => {
|
||||
senderName: owner.name,
|
||||
});
|
||||
});
|
||||
|
||||
it('should add new users when already-added users are included', async () => {
|
||||
const existingUserId = newUuid();
|
||||
const album = AlbumFactory.from().albumUser({ userId: existingUserId }).build();
|
||||
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||
const user = UserFactory.create();
|
||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
mocks.albumUser.create.mockResolvedValue(AlbumUserFactory.from().album(album).user(user).build());
|
||||
|
||||
await sut.addUsers(AuthFactory.create(owner), album.id, {
|
||||
albumUsers: [{ userId: existingUserId }, { userId: user.id }],
|
||||
});
|
||||
|
||||
expect(mocks.user.get).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.user.get).toHaveBeenCalledWith(user.id, {});
|
||||
expect(mocks.albumUser.create).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.albumUser.create).toHaveBeenCalledWith({
|
||||
userId: user.id,
|
||||
albumId: album.id,
|
||||
});
|
||||
expect(mocks.event.emit).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', {
|
||||
id: album.id,
|
||||
userId: user.id,
|
||||
senderName: owner.name,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeUser', () => {
|
||||
|
||||
@@ -290,7 +290,7 @@ export class AlbumService extends BaseService {
|
||||
|
||||
const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
|
||||
if (exists) {
|
||||
throw new BadRequestException('User already added');
|
||||
continue;
|
||||
}
|
||||
|
||||
const user = await this.userRepository.get(userId, {});
|
||||
|
||||
@@ -163,6 +163,9 @@ export class DatabaseBackupService {
|
||||
);
|
||||
|
||||
switch (bin) {
|
||||
case 'pg_dump': {
|
||||
break;
|
||||
}
|
||||
case 'pg_dumpall': {
|
||||
args.push('--database');
|
||||
break;
|
||||
|
||||
@@ -257,6 +257,8 @@ export class JobService extends BaseService {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// no default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,6 +498,9 @@ export class LibraryService extends BaseService {
|
||||
const stat = stats[i];
|
||||
const action = this.checkExistingAsset(asset, stat);
|
||||
switch (action) {
|
||||
case AssetSyncResult.DO_NOTHING: {
|
||||
break;
|
||||
}
|
||||
case AssetSyncResult.OFFLINE: {
|
||||
if (asset.status === AssetStatus.Trashed) {
|
||||
trashedAssetIdsToOffline.push(asset.id);
|
||||
|
||||
@@ -1138,7 +1138,9 @@ export class MetadataService extends BaseService {
|
||||
case 3: {
|
||||
return ExifOrientation.Rotate90CW;
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
brokenAssetClass?: ClassValue;
|
||||
dimmed?: boolean;
|
||||
albumUsers?: UserResponseDto[];
|
||||
onClick?: (asset: TimelineAsset) => void;
|
||||
onClick?: (asset: TimelineAsset, event?: MouseEvent) => void;
|
||||
onPreview?: (asset: TimelineAsset) => void;
|
||||
onSelect?: (asset: TimelineAsset) => void;
|
||||
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
|
||||
@@ -93,12 +93,12 @@
|
||||
}
|
||||
};
|
||||
|
||||
const callClickHandlers = () => {
|
||||
const callClickHandlers = (e?: MouseEvent) => {
|
||||
if (selected) {
|
||||
onIconClickedHandler();
|
||||
onIconClickedHandler(e);
|
||||
return;
|
||||
}
|
||||
onClick?.($state.snapshot(asset));
|
||||
onClick?.($state.snapshot(asset), e);
|
||||
};
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
callClickHandlers();
|
||||
callClickHandlers(e);
|
||||
};
|
||||
|
||||
const onMouseEnter = () => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Combobox from '$lib/components/shared-components/Combobox.svelte';
|
||||
import { defaultLang } from '$lib/constants';
|
||||
import { lang } from '$lib/stores/preferences.store';
|
||||
import { getClosestAvailableLocale, langCodes, langs } from '$lib/utils/i18n';
|
||||
import { convertBCP47, getClosestAvailableLocale, langCodes, langs } from '$lib/utils/i18n';
|
||||
import { Label, Text } from '@immich/ui';
|
||||
import { locale as i18nLocale, t } from 'svelte-i18n';
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
let { showSettingDescription = false }: Props = $props();
|
||||
|
||||
const langOptions = langs.map((lang) => ({ label: lang.name, value: lang.code }));
|
||||
const langOptions = langs.map((lang) => ({ label: lang.name, value: convertBCP47(lang.code) }));
|
||||
|
||||
const defaultLangOption = { label: defaultLang.name, value: defaultLang.code };
|
||||
|
||||
const handleLanguageChange = async (newLang: string | undefined) => {
|
||||
if (newLang) {
|
||||
$lang = newLang;
|
||||
await i18nLocale.set(newLang);
|
||||
await i18nLocale.set(convertBCP47(newLang));
|
||||
await invalidateAll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
groupTitle: string,
|
||||
asset: TimelineAsset,
|
||||
) => void,
|
||||
event?: MouseEvent,
|
||||
) => void;
|
||||
}
|
||||
|
||||
@@ -685,9 +686,9 @@
|
||||
{asset}
|
||||
{albumUsers}
|
||||
{groupIndex}
|
||||
onClick={(asset) => {
|
||||
onClick={(asset, event) => {
|
||||
if (typeof onThumbnailClick === 'function') {
|
||||
onThumbnailClick(asset, timelineManager, timelineDay, _onClick);
|
||||
onThumbnailClick(asset, timelineManager, timelineDay, _onClick, event);
|
||||
} else {
|
||||
_onClick(timelineManager, timelineDay.getAssets(), timelineDay.groupTitle, asset);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import { browser } from '$app/environment';
|
||||
import { defaultLang } from '$lib/constants';
|
||||
import { getPreferredLocale } from '$lib/utils/i18n';
|
||||
import { convertBCP47, getPreferredLocale } from '$lib/utils/i18n';
|
||||
|
||||
// Locale to use for formatting dates, numbers, etc.
|
||||
export const locale = persisted('locale', 'default', {
|
||||
serializer: {
|
||||
parse: (text) => text || 'default',
|
||||
parse: (text) => convertBCP47(text) || 'default',
|
||||
stringify: (object) => object ?? '',
|
||||
},
|
||||
});
|
||||
@@ -14,7 +14,7 @@ export const locale = persisted('locale', 'default', {
|
||||
const preferredLocale = browser ? getPreferredLocale() : undefined;
|
||||
export const lang = persisted<string>('lang', preferredLocale || defaultLang.code, {
|
||||
serializer: {
|
||||
parse: (text) => text,
|
||||
parse: (text) => convertBCP47(text),
|
||||
stringify: (object) => object ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
import { alwaysLoadOriginalFile, lang } from '$lib/stores/preferences.store';
|
||||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { langs } from '$lib/utils/i18n';
|
||||
import { convertBCP47, langs } from '$lib/utils/i18n';
|
||||
|
||||
interface DownloadRequestOptions<T = unknown> {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
@@ -47,7 +47,7 @@ interface DateFormatter {
|
||||
export const initLanguage = async () => {
|
||||
const preferenceLang = get(lang);
|
||||
for (const { code, loader } of langs) {
|
||||
register(code, loader);
|
||||
register(convertBCP47(code), loader);
|
||||
}
|
||||
|
||||
await init({ fallbackLocale: preferenceLang === 'dev' ? 'dev' : defaultLang.code, initialLocale: preferenceLang });
|
||||
|
||||
@@ -19,7 +19,7 @@ const fileCodes = Object.keys(modules)
|
||||
.map((path) => path.match(/\/(\w+)\.json$/)?.[1])
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
const convertBCP47 = (code: string) => code.replaceAll('_', '-');
|
||||
export const convertBCP47 = (code: string) => code.replaceAll('_', '-');
|
||||
|
||||
export const langCodes = fileCodes.map((code) => convertBCP47(code));
|
||||
|
||||
|
||||
@@ -13,38 +13,6 @@ export interface PlacesGroup {
|
||||
places: AssetResponseDto[];
|
||||
}
|
||||
|
||||
export interface PlacesGroupOptionMetadata {
|
||||
id: PlacesGroupBy;
|
||||
isDisabled: () => boolean;
|
||||
}
|
||||
|
||||
export const groupOptionsMetadata: PlacesGroupOptionMetadata[] = [
|
||||
{
|
||||
id: PlacesGroupBy.None,
|
||||
isDisabled: () => false,
|
||||
},
|
||||
{
|
||||
id: PlacesGroupBy.Country,
|
||||
isDisabled: () => false,
|
||||
},
|
||||
];
|
||||
|
||||
export const findGroupOptionMetadata = (groupBy: string) => {
|
||||
// Default is no grouping
|
||||
const defaultGroupOption = groupOptionsMetadata[0];
|
||||
return groupOptionsMetadata.find(({ id }) => groupBy === id) ?? defaultGroupOption;
|
||||
};
|
||||
|
||||
export const getSelectedPlacesGroupOption = (settings: PlacesViewSettings) => {
|
||||
const defaultGroupOption = PlacesGroupBy.None;
|
||||
const albumGroupOption = settings.groupBy ?? defaultGroupOption;
|
||||
|
||||
if (findGroupOptionMetadata(albumGroupOption).isDisabled()) {
|
||||
return defaultGroupOption;
|
||||
}
|
||||
return albumGroupOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* ----------------------------
|
||||
* Places Groups Collapse/Expand
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Dropdown from '$lib/elements/Dropdown.svelte';
|
||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||
import { PlacesGroupBy, placesViewSettings } from '$lib/stores/preferences.store';
|
||||
import {
|
||||
type PlacesGroupOptionMetadata,
|
||||
collapseAllPlacesGroups,
|
||||
expandAllPlacesGroups,
|
||||
findGroupOptionMetadata,
|
||||
getSelectedPlacesGroupOption,
|
||||
groupOptionsMetadata,
|
||||
} from '$lib/utils/places-utils';
|
||||
import { IconButton } from '@immich/ui';
|
||||
import {
|
||||
mdiFolderArrowUpOutline,
|
||||
mdiFolderRemoveOutline,
|
||||
mdiUnfoldLessHorizontal,
|
||||
mdiUnfoldMoreHorizontal,
|
||||
} from '@mdi/js';
|
||||
import { collapseAllPlacesGroups, expandAllPlacesGroups } from '$lib/utils/places-utils';
|
||||
import { IconButton, Select } from '@immich/ui';
|
||||
import { mdiUnfoldLessHorizontal, mdiUnfoldMoreHorizontal } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
placesGroups: string[];
|
||||
@@ -27,48 +14,26 @@
|
||||
|
||||
let { placesGroups, searchQuery = $bindable() }: Props = $props();
|
||||
|
||||
const handleChangeGroupBy = ({ id }: PlacesGroupOptionMetadata) => {
|
||||
$placesViewSettings.groupBy = id;
|
||||
};
|
||||
|
||||
let groupIcon = $derived.by(() => {
|
||||
return selectedGroupOption.id === PlacesGroupBy.None ? mdiFolderRemoveOutline : mdiFolderArrowUpOutline; // OR mdiFolderArrowDownOutline
|
||||
});
|
||||
|
||||
let selectedGroupOption = $derived(findGroupOptionMetadata($placesViewSettings.groupBy));
|
||||
|
||||
let placesGroupByNames: Record<PlacesGroupBy, string> = $derived({
|
||||
[PlacesGroupBy.None]: $t('group_no'),
|
||||
[PlacesGroupBy.Country]: $t('group_country'),
|
||||
});
|
||||
let options = $derived([
|
||||
{ value: PlacesGroupBy.None, label: $t('group_no') },
|
||||
{ value: PlacesGroupBy.Country, label: $t('group_country') },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<!-- Search Places -->
|
||||
<div class="hidden h-10 md:block xl:w-60 2xl:w-80">
|
||||
<SearchBar placeholder={$t('search_places')} bind:name={searchQuery} showLoadingSpinner={false} />
|
||||
</div>
|
||||
|
||||
<!-- Group Places -->
|
||||
<Dropdown
|
||||
position="bottom-right"
|
||||
title={$t('group_places_by')}
|
||||
options={Object.values(groupOptionsMetadata)}
|
||||
selectedOption={selectedGroupOption}
|
||||
onSelect={handleChangeGroupBy}
|
||||
render={({ id, isDisabled }) => ({
|
||||
title: placesGroupByNames[id],
|
||||
icon: groupIcon,
|
||||
disabled: isDisabled(),
|
||||
})}
|
||||
/>
|
||||
<div title={$t('group_places_by')}>
|
||||
<Select {options} bind:value={$placesViewSettings.groupBy} class="w-fit min-w-50" />
|
||||
</div>
|
||||
|
||||
{#if getSelectedPlacesGroupOption($placesViewSettings) !== PlacesGroupBy.None}
|
||||
<span in:fly={{ x: -50, duration: 250 }}>
|
||||
{#if $placesViewSettings.groupBy !== PlacesGroupBy.None}
|
||||
<span transition:slide={{ axis: 'x', duration: 250 }}>
|
||||
<!-- Expand Countries Groups -->
|
||||
<div class="hidden gap-0 xl:flex">
|
||||
<div class="block">
|
||||
<IconButton
|
||||
title={$t('expand_all')}
|
||||
onclick={() => expandAllPlacesGroups()}
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
@@ -81,7 +46,6 @@
|
||||
<!-- Collapse Countries Groups -->
|
||||
<div class="block">
|
||||
<IconButton
|
||||
title={$t('collapse_all')}
|
||||
onclick={() => collapseAllPlacesGroups(placesGroups)}
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { groupBy } from 'lodash-es';
|
||||
import PlacesCardGroup from './PlacesCardGroup.svelte';
|
||||
|
||||
import { type PlacesGroup, getSelectedPlacesGroupOption } from '$lib/utils/places-utils';
|
||||
import { type PlacesGroup } from '$lib/utils/places-utils';
|
||||
import { Icon } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -78,9 +78,8 @@
|
||||
: places;
|
||||
});
|
||||
|
||||
const placesGroupOption: string = $derived(getSelectedPlacesGroupOption(userSettings));
|
||||
const groupingFunction = $derived(groupOptions[placesGroupOption] ?? groupOptions[PlacesGroupBy.None]);
|
||||
const groupedPlaces: PlacesGroup[] = $derived(groupingFunction(filteredPlaces));
|
||||
const groupingFunction = $derived(groupOptions[userSettings.groupBy] ?? groupOptions[PlacesGroupBy.None]);
|
||||
const groupedPlaces = $derived(groupingFunction(filteredPlaces));
|
||||
|
||||
$effect(() => {
|
||||
searchResultCount = filteredPlaces.length;
|
||||
@@ -93,7 +92,7 @@
|
||||
|
||||
{#if places.length > 0}
|
||||
<!-- Album Cards -->
|
||||
{#if placesGroupOption === PlacesGroupBy.None}
|
||||
{#if userSettings.groupBy === PlacesGroupBy.None}
|
||||
<PlacesCardGroup places={groupedPlaces[0].places} />
|
||||
{:else}
|
||||
{#each groupedPlaces as placeGroup (placeGroup.id)}
|
||||
|
||||
@@ -118,7 +118,12 @@
|
||||
groupTitle: string,
|
||||
asset: TimelineAsset,
|
||||
) => void,
|
||||
event?: MouseEvent,
|
||||
) => {
|
||||
if (event?.shiftKey) {
|
||||
onClick(timelineManager, timelineDay.getAssets(), timelineDay.groupTitle, asset);
|
||||
return;
|
||||
}
|
||||
if (hasGps(asset)) {
|
||||
locationUpdated = true;
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -16,7 +16,7 @@ const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
version: {
|
||||
name: process.env.IMMICH_BUILD || Date.now().toString(),
|
||||
name: process.env.IMMICH_BUILD || process.env.npm_package_version || 'local',
|
||||
},
|
||||
paths: {
|
||||
relative: false,
|
||||
|
||||
Reference in New Issue
Block a user