mirror of
https://github.com/immich-app/immich.git
synced 2026-04-28 12:13:09 -07:00
Compare commits
6 Commits
feat/partn
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b6165bd3 | ||
|
|
2624f3884f | ||
|
|
f9b7ce9407 | ||
|
|
013ea37a0d | ||
|
|
b2b4385271 | ||
|
|
081c75bb21 |
@@ -5,79 +5,66 @@ export const errorDto = {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Authentication required',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
unauthorizedWithMessage: (message: string) => ({
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message,
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
forbidden: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: expect.any(String),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
missingPermission: (permission: string) => ({
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: `Missing required permission: ${permission}`,
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
wrongPassword: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'Wrong password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidToken: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid user token',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidShareKey: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid share key',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
passwordRequired: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Password required',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
badRequest: (message: any = null) => ({
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: message ?? expect.anything(),
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
noPermission: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: expect.stringContaining('Not found or no'),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
incorrectLogin: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Incorrect email or password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
alreadyHasAdmin: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'The server already has an admin',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidEmail: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: ['email must be an email'],
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
12
mobile/openapi/lib/api/people_api.dart
generated
12
mobile/openapi/lib/api/people_api.dart
generated
@@ -183,15 +183,15 @@ class PeopleApi {
|
||||
/// * [String] closestPersonId:
|
||||
/// Closest person ID for similarity search
|
||||
///
|
||||
/// * [num] page:
|
||||
/// * [int] page:
|
||||
/// Page number for pagination
|
||||
///
|
||||
/// * [num] size:
|
||||
/// * [int] size:
|
||||
/// Number of items per page
|
||||
///
|
||||
/// * [bool] withHidden:
|
||||
/// Include hidden people
|
||||
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
||||
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/people';
|
||||
|
||||
@@ -244,15 +244,15 @@ class PeopleApi {
|
||||
/// * [String] closestPersonId:
|
||||
/// Closest person ID for similarity search
|
||||
///
|
||||
/// * [num] page:
|
||||
/// * [int] page:
|
||||
/// Page number for pagination
|
||||
///
|
||||
/// * [num] size:
|
||||
/// * [int] size:
|
||||
/// Number of items per page
|
||||
///
|
||||
/// * [bool] withHidden:
|
||||
/// Include hidden people
|
||||
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
||||
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async {
|
||||
final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
|
||||
12
mobile/openapi/lib/api/search_api.dart
generated
12
mobile/openapi/lib/api/search_api.dart
generated
@@ -404,10 +404,10 @@ class SearchApi {
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [num] rating:
|
||||
/// * [int] rating:
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// * [num] size:
|
||||
/// * [int] size:
|
||||
/// Number of results to return
|
||||
///
|
||||
/// * [String] state:
|
||||
@@ -443,7 +443,7 @@ class SearchApi {
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
/// Include EXIF data in response
|
||||
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, num? rating, num? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/large-assets';
|
||||
|
||||
@@ -619,10 +619,10 @@ class SearchApi {
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [num] rating:
|
||||
/// * [int] rating:
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// * [num] size:
|
||||
/// * [int] size:
|
||||
/// Number of results to return
|
||||
///
|
||||
/// * [String] state:
|
||||
@@ -658,7 +658,7 @@ class SearchApi {
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
/// Include EXIF data in response
|
||||
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, num? rating, num? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||
final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
|
||||
@@ -37,12 +37,15 @@ class AssetBulkUpdateDto {
|
||||
|
||||
/// Relative time offset in seconds
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? dateTimeRelative;
|
||||
int? dateTimeRelative;
|
||||
|
||||
/// Asset description
|
||||
///
|
||||
@@ -213,7 +216,7 @@ class AssetBulkUpdateDto {
|
||||
|
||||
return AssetBulkUpdateDto(
|
||||
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||
dateTimeRelative: num.parse('${json[r'dateTimeRelative']}'),
|
||||
dateTimeRelative: mapValueOfType<int>(json, r'dateTimeRelative'),
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
||||
ids: json[r'ids'] is Iterable
|
||||
|
||||
@@ -24,22 +24,26 @@ class AssetEditActionItemDtoParameters {
|
||||
/// Height of the crop
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num height;
|
||||
/// Maximum value: 9007199254740991
|
||||
int height;
|
||||
|
||||
/// Width of the crop
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num width;
|
||||
/// Maximum value: 9007199254740991
|
||||
int width;
|
||||
|
||||
/// Top-Left X coordinate of crop
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num x;
|
||||
/// Maximum value: 9007199254740991
|
||||
int x;
|
||||
|
||||
/// Top-Left Y coordinate of crop
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num y;
|
||||
/// Maximum value: 9007199254740991
|
||||
int y;
|
||||
|
||||
/// Rotation angle in degrees
|
||||
num angle;
|
||||
@@ -88,10 +92,10 @@ class AssetEditActionItemDtoParameters {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionItemDtoParameters(
|
||||
height: num.parse('${json[r'height']}'),
|
||||
width: num.parse('${json[r'width']}'),
|
||||
x: num.parse('${json[r'x']}'),
|
||||
y: num.parse('${json[r'y']}'),
|
||||
height: mapValueOfType<int>(json, r'height')!,
|
||||
width: mapValueOfType<int>(json, r'width')!,
|
||||
x: mapValueOfType<int>(json, r'x')!,
|
||||
y: mapValueOfType<int>(json, r'y')!,
|
||||
angle: num.parse('${json[r'angle']}'),
|
||||
axis: MirrorAxis.fromJson(json[r'axis'])!,
|
||||
);
|
||||
|
||||
14
mobile/openapi/lib/model/asset_response_dto.dart
generated
14
mobile/openapi/lib/model/asset_response_dto.dart
generated
@@ -80,7 +80,8 @@ class AssetResponseDto {
|
||||
/// Asset height
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num? height;
|
||||
/// Maximum value: 9007199254740991
|
||||
int? height;
|
||||
|
||||
/// Asset ID
|
||||
String id;
|
||||
@@ -165,7 +166,8 @@ class AssetResponseDto {
|
||||
/// Asset width
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num? width;
|
||||
/// Maximum value: 9007199254740991
|
||||
int? width;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||
@@ -346,9 +348,7 @@ class AssetResponseDto {
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
||||
hasMetadata: mapValueOfType<bool>(json, r'hasMetadata')!,
|
||||
height: json[r'height'] == null
|
||||
? null
|
||||
: num.parse('${json[r'height']}'),
|
||||
height: mapValueOfType<int>(json, r'height'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||
isEdited: mapValueOfType<bool>(json, r'isEdited')!,
|
||||
@@ -372,9 +372,7 @@ class AssetResponseDto {
|
||||
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility'])!,
|
||||
width: json[r'width'] == null
|
||||
? null
|
||||
: num.parse('${json[r'width']}'),
|
||||
width: mapValueOfType<int>(json, r'width'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
20
mobile/openapi/lib/model/crop_parameters.dart
generated
20
mobile/openapi/lib/model/crop_parameters.dart
generated
@@ -22,22 +22,26 @@ class CropParameters {
|
||||
/// Height of the crop
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num height;
|
||||
/// Maximum value: 9007199254740991
|
||||
int height;
|
||||
|
||||
/// Width of the crop
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num width;
|
||||
/// Maximum value: 9007199254740991
|
||||
int width;
|
||||
|
||||
/// Top-Left X coordinate of crop
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num x;
|
||||
/// Maximum value: 9007199254740991
|
||||
int x;
|
||||
|
||||
/// Top-Left Y coordinate of crop
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num y;
|
||||
/// Maximum value: 9007199254740991
|
||||
int y;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is CropParameters &&
|
||||
@@ -75,10 +79,10 @@ class CropParameters {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return CropParameters(
|
||||
height: num.parse('${json[r'height']}'),
|
||||
width: num.parse('${json[r'width']}'),
|
||||
x: num.parse('${json[r'x']}'),
|
||||
y: num.parse('${json[r'y']}'),
|
||||
height: mapValueOfType<int>(json, r'height')!,
|
||||
width: mapValueOfType<int>(json, r'width')!,
|
||||
x: mapValueOfType<int>(json, r'x')!,
|
||||
y: mapValueOfType<int>(json, r'y')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -27,7 +27,8 @@ class DatabaseBackupConfig {
|
||||
/// Keep last amount
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num keepLastAmount;
|
||||
/// Maximum value: 9007199254740991
|
||||
int keepLastAmount;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig &&
|
||||
@@ -64,7 +65,7 @@ class DatabaseBackupConfig {
|
||||
return DatabaseBackupConfig(
|
||||
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
keepLastAmount: num.parse('${json[r'keepLastAmount']}'),
|
||||
keepLastAmount: mapValueOfType<int>(json, r'keepLastAmount')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -22,7 +22,10 @@ class DatabaseBackupDto {
|
||||
String filename;
|
||||
|
||||
/// Backup file size
|
||||
num filesize;
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int filesize;
|
||||
|
||||
/// Backup timezone
|
||||
String timezone;
|
||||
@@ -61,7 +64,7 @@ class DatabaseBackupDto {
|
||||
|
||||
return DatabaseBackupDto(
|
||||
filename: mapValueOfType<String>(json, r'filename')!,
|
||||
filesize: num.parse('${json[r'filesize']}'),
|
||||
filesize: mapValueOfType<int>(json, r'filesize')!,
|
||||
timezone: mapValueOfType<String>(json, r'timezone')!,
|
||||
);
|
||||
}
|
||||
|
||||
32
mobile/openapi/lib/model/exif_response_dto.dart
generated
32
mobile/openapi/lib/model/exif_response_dto.dart
generated
@@ -52,12 +52,14 @@ class ExifResponseDto {
|
||||
/// Image height in pixels
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num? exifImageHeight;
|
||||
/// Maximum value: 9007199254740991
|
||||
int? exifImageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num? exifImageWidth;
|
||||
/// Maximum value: 9007199254740991
|
||||
int? exifImageWidth;
|
||||
|
||||
/// Exposure time
|
||||
String? exposureTime;
|
||||
@@ -75,7 +77,10 @@ class ExifResponseDto {
|
||||
num? focalLength;
|
||||
|
||||
/// ISO sensitivity
|
||||
num? iso;
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int? iso;
|
||||
|
||||
/// GPS latitude
|
||||
num? latitude;
|
||||
@@ -102,7 +107,10 @@ class ExifResponseDto {
|
||||
String? projectionType;
|
||||
|
||||
/// Rating
|
||||
num? rating;
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int? rating;
|
||||
|
||||
/// State/province name
|
||||
String? state;
|
||||
@@ -292,12 +300,8 @@ class ExifResponseDto {
|
||||
country: mapValueOfType<String>(json, r'country'),
|
||||
dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''),
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
exifImageHeight: json[r'exifImageHeight'] == null
|
||||
? null
|
||||
: num.parse('${json[r'exifImageHeight']}'),
|
||||
exifImageWidth: json[r'exifImageWidth'] == null
|
||||
? null
|
||||
: num.parse('${json[r'exifImageWidth']}'),
|
||||
exifImageHeight: mapValueOfType<int>(json, r'exifImageHeight'),
|
||||
exifImageWidth: mapValueOfType<int>(json, r'exifImageWidth'),
|
||||
exposureTime: mapValueOfType<String>(json, r'exposureTime'),
|
||||
fNumber: json[r'fNumber'] == null
|
||||
? null
|
||||
@@ -306,9 +310,7 @@ class ExifResponseDto {
|
||||
focalLength: json[r'focalLength'] == null
|
||||
? null
|
||||
: num.parse('${json[r'focalLength']}'),
|
||||
iso: json[r'iso'] == null
|
||||
? null
|
||||
: num.parse('${json[r'iso']}'),
|
||||
iso: mapValueOfType<int>(json, r'iso'),
|
||||
latitude: json[r'latitude'] == null
|
||||
? null
|
||||
: num.parse('${json[r'latitude']}'),
|
||||
@@ -321,9 +323,7 @@ class ExifResponseDto {
|
||||
modifyDate: mapDateTime(json, r'modifyDate', r''),
|
||||
orientation: mapValueOfType<String>(json, r'orientation'),
|
||||
projectionType: mapValueOfType<String>(json, r'projectionType'),
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
||||
);
|
||||
|
||||
@@ -21,9 +21,13 @@ class MachineLearningAvailabilityChecksDto {
|
||||
/// Enabled
|
||||
bool enabled;
|
||||
|
||||
num interval;
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int interval;
|
||||
|
||||
num timeout;
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int timeout;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is MachineLearningAvailabilityChecksDto &&
|
||||
@@ -59,8 +63,8 @@ class MachineLearningAvailabilityChecksDto {
|
||||
|
||||
return MachineLearningAvailabilityChecksDto(
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
interval: num.parse('${json[r'interval']}'),
|
||||
timeout: num.parse('${json[r'timeout']}'),
|
||||
interval: mapValueOfType<int>(json, r'interval')!,
|
||||
timeout: mapValueOfType<int>(json, r'timeout')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -20,7 +20,10 @@ class MaintenanceDetectInstallStorageFolderDto {
|
||||
});
|
||||
|
||||
/// Number of files in the folder
|
||||
num files;
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int files;
|
||||
|
||||
StorageFolder folder;
|
||||
|
||||
@@ -66,7 +69,7 @@ class MaintenanceDetectInstallStorageFolderDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return MaintenanceDetectInstallStorageFolderDto(
|
||||
files: num.parse('${json[r'files']}'),
|
||||
files: mapValueOfType<int>(json, r'files')!,
|
||||
folder: StorageFolder.fromJson(json[r'folder'])!,
|
||||
readable: mapValueOfType<bool>(json, r'readable')!,
|
||||
writable: mapValueOfType<bool>(json, r'writable')!,
|
||||
|
||||
@@ -32,13 +32,15 @@ class MaintenanceStatusResponseDto {
|
||||
///
|
||||
String? error;
|
||||
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? progress;
|
||||
int? progress;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -102,7 +104,7 @@ class MaintenanceStatusResponseDto {
|
||||
action: MaintenanceAction.fromJson(json[r'action'])!,
|
||||
active: mapValueOfType<bool>(json, r'active')!,
|
||||
error: mapValueOfType<String>(json, r'error'),
|
||||
progress: num.parse('${json[r'progress']}'),
|
||||
progress: mapValueOfType<int>(json, r'progress'),
|
||||
task: mapValueOfType<String>(json, r'task'),
|
||||
);
|
||||
}
|
||||
|
||||
15
mobile/openapi/lib/model/metadata_search_dto.dart
generated
15
mobile/openapi/lib/model/metadata_search_dto.dart
generated
@@ -215,13 +215,14 @@ class MetadataSearchDto {
|
||||
/// Page number
|
||||
///
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 9007199254740991
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? page;
|
||||
int? page;
|
||||
|
||||
/// Filter by person IDs
|
||||
List<String> personIds;
|
||||
@@ -239,7 +240,7 @@ class MetadataSearchDto {
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Number of results to return
|
||||
///
|
||||
@@ -251,7 +252,7 @@ class MetadataSearchDto {
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? size;
|
||||
int? size;
|
||||
|
||||
/// Filter by state/province name
|
||||
String? state;
|
||||
@@ -724,15 +725,13 @@ class MetadataSearchDto {
|
||||
order: AssetOrder.fromJson(json[r'order']),
|
||||
originalFileName: mapValueOfType<String>(json, r'originalFileName'),
|
||||
originalPath: mapValueOfType<String>(json, r'originalPath'),
|
||||
page: num.parse('${json[r'page']}'),
|
||||
page: mapValueOfType<int>(json, r'page'),
|
||||
personIds: json[r'personIds'] is Iterable
|
||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
size: num.parse('${json[r'size']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
size: mapValueOfType<int>(json, r'size'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
|
||||
10
mobile/openapi/lib/model/random_search_dto.dart
generated
10
mobile/openapi/lib/model/random_search_dto.dart
generated
@@ -147,7 +147,7 @@ class RandomSearchDto {
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Number of results to return
|
||||
///
|
||||
@@ -159,7 +159,7 @@ class RandomSearchDto {
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? size;
|
||||
int? size;
|
||||
|
||||
/// Filter by state/province name
|
||||
String? state;
|
||||
@@ -549,10 +549,8 @@ class RandomSearchDto {
|
||||
personIds: json[r'personIds'] is Iterable
|
||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
size: num.parse('${json[r'size']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
size: mapValueOfType<int>(json, r'size'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
|
||||
5
mobile/openapi/lib/model/session_create_dto.dart
generated
5
mobile/openapi/lib/model/session_create_dto.dart
generated
@@ -39,13 +39,14 @@ class SessionCreateDto {
|
||||
/// Session duration in seconds
|
||||
///
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 9007199254740991
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? duration;
|
||||
int? duration;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto &&
|
||||
@@ -94,7 +95,7 @@ class SessionCreateDto {
|
||||
return SessionCreateDto(
|
||||
deviceOS: mapValueOfType<String>(json, r'deviceOS'),
|
||||
deviceType: mapValueOfType<String>(json, r'deviceType'),
|
||||
duration: num.parse('${json[r'duration']}'),
|
||||
duration: mapValueOfType<int>(json, r'duration'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
15
mobile/openapi/lib/model/smart_search_dto.dart
generated
15
mobile/openapi/lib/model/smart_search_dto.dart
generated
@@ -154,13 +154,14 @@ class SmartSearchDto {
|
||||
/// Page number
|
||||
///
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 9007199254740991
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? page;
|
||||
int? page;
|
||||
|
||||
/// Filter by person IDs
|
||||
List<String> personIds;
|
||||
@@ -187,7 +188,7 @@ class SmartSearchDto {
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Number of results to return
|
||||
///
|
||||
@@ -199,7 +200,7 @@ class SmartSearchDto {
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
num? size;
|
||||
int? size;
|
||||
|
||||
/// Filter by state/province name
|
||||
String? state;
|
||||
@@ -583,16 +584,14 @@ class SmartSearchDto {
|
||||
make: mapValueOfType<String>(json, r'make'),
|
||||
model: mapValueOfType<String>(json, r'model'),
|
||||
ocr: mapValueOfType<String>(json, r'ocr'),
|
||||
page: num.parse('${json[r'page']}'),
|
||||
page: mapValueOfType<int>(json, r'page'),
|
||||
personIds: json[r'personIds'] is Iterable
|
||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
query: mapValueOfType<String>(json, r'query'),
|
||||
queryAssetId: mapValueOfType<String>(json, r'queryAssetId'),
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
size: num.parse('${json[r'size']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
size: mapValueOfType<int>(json, r'size'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
|
||||
@@ -152,7 +152,7 @@ class StatisticsSearchDto {
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Filter by state/province name
|
||||
String? state;
|
||||
@@ -479,9 +479,7 @@ class StatisticsSearchDto {
|
||||
personIds: json[r'personIds'] is Iterable
|
||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
|
||||
@@ -57,7 +57,8 @@ class SystemConfigOAuthDto {
|
||||
/// Default storage quota
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num? defaultStorageQuota;
|
||||
/// Maximum value: 9007199254740991
|
||||
int? defaultStorageQuota;
|
||||
|
||||
/// Enabled
|
||||
bool enabled;
|
||||
@@ -200,9 +201,7 @@ class SystemConfigOAuthDto {
|
||||
buttonText: mapValueOfType<String>(json, r'buttonText')!,
|
||||
clientId: mapValueOfType<String>(json, r'clientId')!,
|
||||
clientSecret: mapValueOfType<String>(json, r'clientSecret')!,
|
||||
defaultStorageQuota: json[r'defaultStorageQuota'] == null
|
||||
? null
|
||||
: num.parse('${json[r'defaultStorageQuota']}'),
|
||||
defaultStorageQuota: mapValueOfType<int>(json, r'defaultStorageQuota'),
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
endSessionEndpoint: mapValueOfType<String>(json, r'endSessionEndpoint')!,
|
||||
issuerUrl: mapValueOfType<String>(json, r'issuerUrl')!,
|
||||
|
||||
@@ -34,7 +34,7 @@ class SystemConfigSmtpTransportDto {
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 65535
|
||||
num port;
|
||||
int port;
|
||||
|
||||
/// Whether to use secure connection (TLS/SSL)
|
||||
bool secure;
|
||||
@@ -87,7 +87,7 @@ class SystemConfigSmtpTransportDto {
|
||||
host: mapValueOfType<String>(json, r'host')!,
|
||||
ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!,
|
||||
password: mapValueOfType<String>(json, r'password')!,
|
||||
port: num.parse('${json[r'port']}'),
|
||||
port: mapValueOfType<int>(json, r'port')!,
|
||||
secure: mapValueOfType<bool>(json, r'secure')!,
|
||||
username: mapValueOfType<String>(json, r'username')!,
|
||||
);
|
||||
|
||||
@@ -26,7 +26,10 @@ class WorkflowActionResponseDto {
|
||||
String id;
|
||||
|
||||
/// Action order
|
||||
num order;
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int order;
|
||||
|
||||
/// Plugin action ID
|
||||
String pluginActionId;
|
||||
@@ -79,7 +82,7 @@ class WorkflowActionResponseDto {
|
||||
return WorkflowActionResponseDto(
|
||||
actionConfig: mapCastOfType<String, Object>(json, r'actionConfig'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
order: num.parse('${json[r'order']}'),
|
||||
order: mapValueOfType<int>(json, r'order')!,
|
||||
pluginActionId: mapValueOfType<String>(json, r'pluginActionId')!,
|
||||
workflowId: mapValueOfType<String>(json, r'workflowId')!,
|
||||
);
|
||||
|
||||
@@ -26,7 +26,10 @@ class WorkflowFilterResponseDto {
|
||||
String id;
|
||||
|
||||
/// Filter order
|
||||
num order;
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int order;
|
||||
|
||||
/// Plugin filter ID
|
||||
String pluginFilterId;
|
||||
@@ -79,7 +82,7 @@ class WorkflowFilterResponseDto {
|
||||
return WorkflowFilterResponseDto(
|
||||
filterConfig: mapCastOfType<String, Object>(json, r'filterConfig'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
order: num.parse('${json[r'order']}'),
|
||||
order: mapValueOfType<int>(json, r'order')!,
|
||||
pluginFilterId: mapValueOfType<String>(json, r'pluginFilterId')!,
|
||||
workflowId: mapValueOfType<String>(json, r'workflowId')!,
|
||||
);
|
||||
|
||||
@@ -7964,8 +7964,9 @@
|
||||
"description": "Page number for pagination",
|
||||
"schema": {
|
||||
"minimum": 1,
|
||||
"maximum": 9007199254740991,
|
||||
"default": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -7977,7 +7978,7 @@
|
||||
"minimum": 1,
|
||||
"maximum": 1000,
|
||||
"default": 500,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -9372,7 +9373,7 @@
|
||||
],
|
||||
"x-immich-state": "Stable",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"minimum": -1,
|
||||
"maximum": 5,
|
||||
"nullable": true
|
||||
@@ -9386,7 +9387,7 @@
|
||||
"schema": {
|
||||
"minimum": 1,
|
||||
"maximum": 1000,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -15636,7 +15637,9 @@
|
||||
},
|
||||
"dateTimeRelative": {
|
||||
"description": "Relative time offset in seconds",
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"description": "Asset description",
|
||||
@@ -16650,9 +16653,10 @@
|
||||
},
|
||||
"height": {
|
||||
"description": "Asset height",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
@@ -16795,9 +16799,10 @@
|
||||
},
|
||||
"width": {
|
||||
"description": "Asset width",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -17214,23 +17219,27 @@
|
||||
"properties": {
|
||||
"height": {
|
||||
"description": "Height of the crop",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"width": {
|
||||
"description": "Width of the crop",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"x": {
|
||||
"description": "Top-Left X coordinate of crop",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"y": {
|
||||
"description": "Top-Left Y coordinate of crop",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -17254,8 +17263,9 @@
|
||||
},
|
||||
"keepLastAmount": {
|
||||
"description": "Keep last amount",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -17288,7 +17298,9 @@
|
||||
},
|
||||
"filesize": {
|
||||
"description": "Backup file size",
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"timezone": {
|
||||
"description": "Backup timezone",
|
||||
@@ -17627,16 +17639,18 @@
|
||||
"exifImageHeight": {
|
||||
"default": null,
|
||||
"description": "Image height in pixels",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"exifImageWidth": {
|
||||
"default": null,
|
||||
"description": "Image width in pixels",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"exposureTime": {
|
||||
"default": null,
|
||||
@@ -17667,8 +17681,10 @@
|
||||
"iso": {
|
||||
"default": null,
|
||||
"description": "ISO sensitivity",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"latitude": {
|
||||
"default": null,
|
||||
@@ -17722,8 +17738,10 @@
|
||||
"rating": {
|
||||
"default": null,
|
||||
"description": "Rating",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"state": {
|
||||
"default": null,
|
||||
@@ -18150,10 +18168,14 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"interval": {
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -18203,7 +18225,9 @@
|
||||
"properties": {
|
||||
"files": {
|
||||
"description": "Number of files in the folder",
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"folder": {
|
||||
"$ref": "#/components/schemas/StorageFolder"
|
||||
@@ -18246,7 +18270,9 @@
|
||||
"type": "string"
|
||||
},
|
||||
"progress": {
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"task": {
|
||||
"type": "string"
|
||||
@@ -18723,8 +18749,9 @@
|
||||
},
|
||||
"page": {
|
||||
"description": "Page number",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"personIds": {
|
||||
"description": "Filter by person IDs",
|
||||
@@ -18744,7 +18771,7 @@
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -18766,7 +18793,7 @@
|
||||
"description": "Number of results to return",
|
||||
"maximum": 1000,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"state": {
|
||||
"description": "Filter by state/province name",
|
||||
@@ -20597,7 +20624,7 @@
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -20619,7 +20646,7 @@
|
||||
"description": "Number of results to return",
|
||||
"maximum": 1000,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"state": {
|
||||
"description": "Filter by state/province name",
|
||||
@@ -21437,8 +21464,9 @@
|
||||
},
|
||||
"duration": {
|
||||
"description": "Session duration in seconds",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -21952,8 +21980,9 @@
|
||||
},
|
||||
"page": {
|
||||
"description": "Page number",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"personIds": {
|
||||
"description": "Filter by person IDs",
|
||||
@@ -21979,7 +22008,7 @@
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -22001,7 +22030,7 @@
|
||||
"description": "Number of results to return",
|
||||
"maximum": 1000,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"state": {
|
||||
"description": "Filter by state/province name",
|
||||
@@ -22239,7 +22268,7 @@
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -24371,9 +24400,10 @@
|
||||
},
|
||||
"defaultStorageQuota": {
|
||||
"description": "Default storage quota",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Enabled",
|
||||
@@ -24548,7 +24578,7 @@
|
||||
"description": "SMTP server port",
|
||||
"maximum": 65535,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"secure": {
|
||||
"description": "Whether to use secure connection (TLS/SSL)",
|
||||
@@ -25966,7 +25996,9 @@
|
||||
},
|
||||
"order": {
|
||||
"description": "Action order",
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"pluginActionId": {
|
||||
"description": "Plugin action ID",
|
||||
@@ -26065,7 +26097,9 @@
|
||||
},
|
||||
"order": {
|
||||
"description": "Filter order",
|
||||
"type": "number"
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"pluginFilterId": {
|
||||
"description": "Plugin filter ID",
|
||||
|
||||
@@ -49,7 +49,7 @@ describe(SearchController.name, () => {
|
||||
});
|
||||
|
||||
it('should reject an invalid size', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ size: -1.5 });
|
||||
const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ size: -1 });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['[size] Too small: expected number to be >=1']));
|
||||
});
|
||||
|
||||
@@ -50,8 +50,8 @@ const SanitizedAssetResponseSchema = z
|
||||
duration: z.string().nullable().describe('Video/gif duration in hh:mm:ss.SSS format (null for static images)'),
|
||||
livePhotoVideoId: z.string().nullish().describe('Live photo video ID'),
|
||||
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
||||
width: z.number().min(0).nullable().describe('Asset width'),
|
||||
height: z.number().min(0).nullable().describe('Asset height'),
|
||||
width: z.int().min(0).nullable().describe('Asset width'),
|
||||
height: z.int().min(0).nullable().describe('Asset height'),
|
||||
})
|
||||
.meta({ id: 'SanitizedAssetResponseDto' });
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const UpdateAssetBaseSchema = z
|
||||
const AssetBulkUpdateBaseSchema = UpdateAssetBaseSchema.extend({
|
||||
ids: z.array(z.uuidv4()).describe('Asset IDs to update'),
|
||||
duplicateId: z.string().nullish().describe('Duplicate ID'),
|
||||
dateTimeRelative: z.number().optional().describe('Relative time offset in seconds'),
|
||||
dateTimeRelative: z.int().optional().describe('Relative time offset in seconds'),
|
||||
timeZone: z.string().optional().describe('Time zone (IANA timezone)'),
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import z from 'zod';
|
||||
const DatabaseBackupSchema = z
|
||||
.object({
|
||||
filename: z.string().describe('Backup filename'),
|
||||
filesize: z.number().describe('Backup file size'),
|
||||
filesize: z.int().describe('Backup file size'),
|
||||
timezone: z.string().describe('Backup timezone'),
|
||||
})
|
||||
.meta({ id: 'DatabaseBackupDto' });
|
||||
|
||||
@@ -21,10 +21,10 @@ const MirrorAxisSchema = z.enum(['horizontal', 'vertical']).describe('Axis to mi
|
||||
|
||||
const CropParametersSchema = z
|
||||
.object({
|
||||
x: z.number().min(0).describe('Top-Left X coordinate of crop'),
|
||||
y: z.number().min(0).describe('Top-Left Y coordinate of crop'),
|
||||
width: z.number().min(1).describe('Width of the crop'),
|
||||
height: z.number().min(1).describe('Height of the crop'),
|
||||
x: z.int().min(0).describe('Top-Left X coordinate of crop'),
|
||||
y: z.int().min(0).describe('Top-Left Y coordinate of crop'),
|
||||
width: z.int().min(1).describe('Width of the crop'),
|
||||
height: z.int().min(1).describe('Height of the crop'),
|
||||
})
|
||||
.meta({ id: 'CropParameters' });
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ export const ExifResponseSchema = z
|
||||
.object({
|
||||
make: z.string().nullish().default(null).describe('Camera make'),
|
||||
model: z.string().nullish().default(null).describe('Camera model'),
|
||||
exifImageWidth: z.number().min(0).nullish().default(null).describe('Image width in pixels'),
|
||||
exifImageHeight: z.number().min(0).nullish().default(null).describe('Image height in pixels'),
|
||||
exifImageWidth: z.int().min(0).nullish().default(null).describe('Image width in pixels'),
|
||||
exifImageHeight: z.int().min(0).nullish().default(null).describe('Image height in pixels'),
|
||||
fileSizeInByte: z.int().min(0).nullish().default(null).describe('File size in bytes'),
|
||||
orientation: z.string().nullish().default(null).describe('Image orientation'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
@@ -20,7 +20,7 @@ export const ExifResponseSchema = z
|
||||
lensModel: z.string().nullish().default(null).describe('Lens model'),
|
||||
fNumber: z.number().nullish().default(null).describe('F-number (aperture)'),
|
||||
focalLength: z.number().nullish().default(null).describe('Focal length in mm'),
|
||||
iso: z.number().nullish().default(null).describe('ISO sensitivity'),
|
||||
iso: z.int().nullish().default(null).describe('ISO sensitivity'),
|
||||
exposureTime: z.string().nullish().default(null).describe('Exposure time'),
|
||||
latitude: z.number().nullish().default(null).describe('GPS latitude'),
|
||||
longitude: z.number().nullish().default(null).describe('GPS longitude'),
|
||||
@@ -29,7 +29,7 @@ export const ExifResponseSchema = z
|
||||
country: z.string().nullish().default(null).describe('Country name'),
|
||||
description: z.string().nullish().default(null).describe('Image description'),
|
||||
projectionType: z.string().nullish().default(null).describe('Projection type'),
|
||||
rating: z.number().nullish().default(null).describe('Rating'),
|
||||
rating: z.int().nullish().default(null).describe('Rating'),
|
||||
})
|
||||
.describe('EXIF response')
|
||||
.meta({ id: 'ExifResponseDto' });
|
||||
|
||||
@@ -29,7 +29,7 @@ const MaintenanceStatusResponseSchema = z
|
||||
.object({
|
||||
active: z.boolean(),
|
||||
action: MaintenanceActionSchema,
|
||||
progress: z.number().optional(),
|
||||
progress: z.int().optional(),
|
||||
task: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
})
|
||||
@@ -40,7 +40,7 @@ const MaintenanceDetectInstallStorageFolderSchema = z
|
||||
folder: StorageFolderSchema,
|
||||
readable: z.boolean().describe('Whether the folder is readable'),
|
||||
writable: z.boolean().describe('Whether the folder is writable'),
|
||||
files: z.number().describe('Number of files in the folder'),
|
||||
files: z.int().describe('Number of files in the folder'),
|
||||
})
|
||||
.meta({ id: 'MaintenanceDetectInstallStorageFolderDto' });
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ const PersonSearchSchema = z
|
||||
withHidden: stringToBool.optional().describe('Include hidden people'),
|
||||
closestPersonId: z.uuidv4().optional().describe('Closest person ID for similarity search'),
|
||||
closestAssetId: z.uuidv4().optional().describe('Closest asset ID for similarity search'),
|
||||
page: z.coerce.number().min(1).default(1).describe('Page number for pagination'),
|
||||
size: z.coerce.number().min(1).max(1000).default(500).describe('Number of items per page'),
|
||||
page: z.coerce.number().int().min(1).default(1).describe('Page number for pagination'),
|
||||
size: z.coerce.number().int().min(1).max(1000).default(500).describe('Number of items per page'),
|
||||
})
|
||||
.meta({ id: 'PersonSearchDto' });
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const BaseSearchSchema = z.object({
|
||||
tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs'),
|
||||
albumIds: z.array(z.uuidv4()).optional().describe('Filter by album IDs'),
|
||||
rating: z
|
||||
.number()
|
||||
.int()
|
||||
.min(-1)
|
||||
.max(5)
|
||||
.nullish()
|
||||
@@ -52,7 +52,7 @@ const BaseSearchSchema = z.object({
|
||||
const BaseSearchWithResultsSchema = BaseSearchSchema.extend({
|
||||
withDeleted: z.boolean().optional().describe('Include deleted assets'),
|
||||
withExif: z.boolean().optional().describe('Include EXIF data in response'),
|
||||
size: z.number().min(1).max(1000).optional().describe('Number of results to return'),
|
||||
size: z.int().min(1).max(1000).optional().describe('Number of results to return'),
|
||||
});
|
||||
|
||||
const RandomSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
@@ -62,7 +62,7 @@ const RandomSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
|
||||
const LargeAssetSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
minFileSize: z.coerce.number().int().min(0).optional().describe('Minimum file size in bytes'),
|
||||
size: z.coerce.number().min(1).max(1000).optional().describe('Number of results to return'),
|
||||
size: z.coerce.number().int().min(1).max(1000).optional().describe('Number of results to return'),
|
||||
}).meta({ id: 'LargeAssetSearchDto' });
|
||||
|
||||
const MetadataSearchSchema = RandomSearchSchema.extend({
|
||||
@@ -75,7 +75,7 @@ const MetadataSearchSchema = RandomSearchSchema.extend({
|
||||
thumbnailPath: z.string().optional().describe('Filter by thumbnail file path'),
|
||||
encodedVideoPath: z.string().optional().describe('Filter by encoded video file path'),
|
||||
order: AssetOrderSchema.default(AssetOrder.Desc).optional().describe('Sort order'),
|
||||
page: z.number().min(1).optional().describe('Page number'),
|
||||
page: z.int().min(1).optional().describe('Page number'),
|
||||
}).meta({ id: 'MetadataSearchDto' });
|
||||
|
||||
const StatisticsSearchSchema = BaseSearchSchema.extend({
|
||||
@@ -86,7 +86,7 @@ const SmartSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
query: z.string().trim().optional().describe('Natural language search query'),
|
||||
queryAssetId: z.uuidv4().optional().describe('Asset ID to use as search reference'),
|
||||
language: z.string().optional().describe('Search language code'),
|
||||
page: z.number().min(1).optional().describe('Page number'),
|
||||
page: z.int().min(1).optional().describe('Page number'),
|
||||
}).meta({ id: 'SmartSearchDto' });
|
||||
|
||||
const SearchPlacesSchema = z
|
||||
|
||||
@@ -4,7 +4,7 @@ import z from 'zod';
|
||||
|
||||
const SessionCreateSchema = z
|
||||
.object({
|
||||
duration: z.number().min(1).optional().describe('Session duration in seconds'),
|
||||
duration: z.int().min(1).optional().describe('Session duration in seconds'),
|
||||
deviceType: z.string().optional().describe('Device type'),
|
||||
deviceOS: z.string().optional().describe('Device OS'),
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ const DatabaseBackupSchema = z
|
||||
.object({
|
||||
enabled: configBool.describe('Enabled'),
|
||||
cronExpression: cronExpressionSchema,
|
||||
keepLastAmount: z.number().min(1).describe('Keep last amount'),
|
||||
keepLastAmount: z.int().min(1).describe('Keep last amount'),
|
||||
})
|
||||
.meta({ id: 'DatabaseBackupConfig' });
|
||||
|
||||
@@ -130,8 +130,8 @@ const SystemConfigLoggingSchema = z
|
||||
const MachineLearningAvailabilityChecksSchema = z
|
||||
.object({
|
||||
enabled: configBool.describe('Enabled'),
|
||||
timeout: z.number(),
|
||||
interval: z.number(),
|
||||
timeout: z.int(),
|
||||
interval: z.int(),
|
||||
})
|
||||
.meta({ id: 'MachineLearningAvailabilityChecksDto' });
|
||||
|
||||
@@ -180,7 +180,7 @@ const SystemConfigOAuthSchema = z
|
||||
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethodSchema,
|
||||
timeout: z.int().min(1).describe('Timeout'),
|
||||
allowInsecureRequests: configBool.describe('Allow insecure requests'),
|
||||
defaultStorageQuota: z.number().min(0).nullable().describe('Default storage quota'),
|
||||
defaultStorageQuota: z.int().min(0).nullable().describe('Default storage quota'),
|
||||
enabled: configBool.describe('Enabled'),
|
||||
issuerUrl: z
|
||||
.string()
|
||||
@@ -254,7 +254,7 @@ const SystemConfigSmtpTransportSchema = z
|
||||
.object({
|
||||
ignoreCert: configBool.describe('Whether to ignore SSL certificate errors'),
|
||||
host: z.string().describe('SMTP server hostname'),
|
||||
port: z.number().min(0).max(65_535).describe('SMTP server port'),
|
||||
port: z.int().min(0).max(65_535).describe('SMTP server port'),
|
||||
secure: configBool.describe('Whether to use secure connection (TLS/SSL)'),
|
||||
username: z.string().describe('SMTP username'),
|
||||
password: z.string().describe('SMTP password'),
|
||||
|
||||
@@ -46,7 +46,7 @@ const WorkflowFilterResponseSchema = z
|
||||
workflowId: z.string().describe('Workflow ID'),
|
||||
pluginFilterId: z.string().describe('Plugin filter ID'),
|
||||
filterConfig: FilterConfigSchema.nullable(),
|
||||
order: z.number().describe('Filter order'),
|
||||
order: z.int().describe('Filter order'),
|
||||
})
|
||||
.meta({ id: 'WorkflowFilterResponseDto' });
|
||||
|
||||
@@ -56,7 +56,7 @@ const WorkflowActionResponseSchema = z
|
||||
workflowId: z.string().describe('Workflow ID'),
|
||||
pluginActionId: z.string().describe('Plugin action ID'),
|
||||
actionConfig: ActionConfigSchema.nullable(),
|
||||
order: z.number().describe('Action order'),
|
||||
order: z.int().describe('Action order'),
|
||||
})
|
||||
.meta({ id: 'WorkflowActionResponseDto' });
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ export enum ImmichHeader {
|
||||
SharedLinkKey = 'x-immich-share-key',
|
||||
SharedLinkSlug = 'x-immich-share-slug',
|
||||
Checksum = 'x-immich-checksum',
|
||||
Cid = 'x-immich-cid',
|
||||
}
|
||||
|
||||
export enum ImmichQuery {
|
||||
|
||||
@@ -20,14 +20,16 @@ export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
||||
const response = ctx.getResponse<Response>();
|
||||
const { status, body } = this.fromError(error);
|
||||
if (!response.headersSent) {
|
||||
response.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() });
|
||||
response.header('X-Correlation-ID', this.cls.getId());
|
||||
response.status(status).json({ ...body, statusCode: status });
|
||||
}
|
||||
}
|
||||
|
||||
handleError(res: Response, error: Error) {
|
||||
const { status, body } = this.fromError(error);
|
||||
if (!res.headersSent) {
|
||||
res.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() });
|
||||
res.header('X-Correlation-ID', this.cls.getId());
|
||||
res.status(status).json({ ...body, statusCode: status });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import { EnvSchema } from 'src/dtos/env.dto';
|
||||
import {
|
||||
DatabaseExtension,
|
||||
ImmichEnvironment,
|
||||
ImmichHeader,
|
||||
ImmichTelemetry,
|
||||
ImmichWorker,
|
||||
LogFormat,
|
||||
@@ -301,11 +300,11 @@ const getEnv = (): EnvData => {
|
||||
mount: true,
|
||||
generateId: true,
|
||||
setup: (cls, req: Request, res: Response) => {
|
||||
const headerValues = req.headers[ImmichHeader.Cid];
|
||||
const headerValues = req.headers['x-correlation-id'];
|
||||
const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues;
|
||||
const cid = headerValue || cls.get(CLS_ID);
|
||||
cls.set(CLS_ID, cid);
|
||||
res.header(ImmichHeader.Cid, cid);
|
||||
res.header('X-Correlation-ID', cid);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,43 +5,36 @@ export const errorDto = {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Authentication required',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
forbidden: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: expect.any(String),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
missingPermission: (permission: string) => ({
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: `Missing required permission: ${permission}`,
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
wrongPassword: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'Wrong password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidToken: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid user token',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidShareKey: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid share key',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidSharePassword: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
badRequest: (message: any = null) => ({
|
||||
error: 'Bad Request',
|
||||
@@ -52,18 +45,15 @@ export const errorDto = {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: expect.stringContaining('Not found or no'),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
incorrectLogin: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Incorrect email or password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
alreadyHasAdmin: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'The server already has an admin',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
const setSelectedDate = (value: DateTime | undefined) => {
|
||||
selectedPresetValue = null; // Clear preset when manually setting date
|
||||
expiresAt = value ? value.toISO() : null;
|
||||
expiresAt = value ? value.toUTC().toISO() : null;
|
||||
};
|
||||
|
||||
const selectPreset = (value: number) => {
|
||||
@@ -44,8 +44,8 @@
|
||||
expiresAt = null;
|
||||
return;
|
||||
}
|
||||
const newDate = DateTime.now().plus(value);
|
||||
expiresAt = newDate.toISO();
|
||||
const newDate = DateTime.now().plus({ milliseconds: value });
|
||||
expiresAt = newDate.toUTC().toISO();
|
||||
};
|
||||
|
||||
const isSelected = (value: number) => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getAssetMediaUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { getAssetMediaUrl } from '$lib/utils';
|
||||
import { delay, getDimensions } from '$lib/utils/asset-utils';
|
||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -24,26 +24,15 @@
|
||||
type AssetResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { Icon, IconButton, LoadingSpinner, Text } from '@immich/ui';
|
||||
import {
|
||||
mdiCamera,
|
||||
mdiCameraIris,
|
||||
mdiClose,
|
||||
mdiEye,
|
||||
mdiEyeOff,
|
||||
mdiImageOutline,
|
||||
mdiInformationOutline,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from '@mdi/js';
|
||||
import { DateTime } from 'luxon';
|
||||
import { mdiCamera, mdiCameraIris, mdiClose, mdiImageOutline, mdiInformationOutline } from '@mdi/js';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { slide } from 'svelte/transition';
|
||||
import ImageThumbnail from '../assets/thumbnail/ImageThumbnail.svelte';
|
||||
import PersonSidePanel from '../faces-page/PersonSidePanel.svelte';
|
||||
import OnEvents from '../OnEvents.svelte';
|
||||
import UserAvatar from '../shared-components/UserAvatar.svelte';
|
||||
import AlbumListItemDetails from './AlbumListItemDetails.svelte';
|
||||
import DetailPanelPeople from '$lib/components/asset-viewer/DetailPanelPeople.svelte';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
@@ -53,8 +42,6 @@
|
||||
let { asset, currentAlbum = null }: Props = $props();
|
||||
|
||||
let isOwner = $derived(authManager.authenticated && authManager.user.id === asset.ownerId);
|
||||
let people = $derived(asset.people || []);
|
||||
let unassignedFaces = $derived(asset.unassignedFaces || []);
|
||||
let latlng = $derived(
|
||||
(() => {
|
||||
const lat = asset.exifInfo?.latitude;
|
||||
@@ -162,110 +149,7 @@
|
||||
|
||||
<DetailPanelDescription {asset} {isOwner} />
|
||||
<DetailPanelRating {asset} {isOwner} />
|
||||
|
||||
{#if !authManager.isSharedLink && isOwner}
|
||||
<section class="px-4 pt-4 text-sm">
|
||||
<div class="flex h-10 w-full items-center justify-between">
|
||||
<Text size="small" color="muted">{$t('people')}</Text>
|
||||
<div class="flex gap-2 items-center">
|
||||
{#if people.some((person) => person.isHidden)}
|
||||
<IconButton
|
||||
aria-label={$t('show_hidden_people')}
|
||||
icon={assetViewerManager.isShowingHiddenPeople ? mdiEyeOff : mdiEye}
|
||||
size="medium"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
onclick={() => assetViewerManager.toggleHiddenPeople()}
|
||||
/>
|
||||
{/if}
|
||||
<IconButton
|
||||
aria-label={$t('tag_people')}
|
||||
icon={mdiPlus}
|
||||
size="medium"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
onclick={() => assetViewerManager.toggleFaceEditMode()}
|
||||
/>
|
||||
|
||||
{#if people.length > 0 || unassignedFaces.length > 0}
|
||||
<IconButton
|
||||
aria-label={$t('edit_people')}
|
||||
icon={mdiPencil}
|
||||
size="medium"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
onclick={() => assetViewerManager.openEditFacesPanel()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
{#each people as person, index (person.id)}
|
||||
{#if assetViewerManager.isShowingHiddenPeople || !person.isHidden}
|
||||
{@const isHighlighted = people[index].faces.some((f) =>
|
||||
assetViewerManager.highlightedFaces.some((b) => b.id === f.id),
|
||||
)}
|
||||
<a
|
||||
class="group w-22 outline-none"
|
||||
href={Route.viewPerson(person, { previousRoute })}
|
||||
onfocus={() => assetViewerManager.setHighlightedFaces(people[index].faces)}
|
||||
onblur={() => assetViewerManager.clearHighlightedFaces()}
|
||||
onpointerenter={() => assetViewerManager.setHighlightedFaces(people[index].faces)}
|
||||
onpointerleave={() => assetViewerManager.clearHighlightedFaces()}
|
||||
>
|
||||
<div class="relative">
|
||||
<ImageThumbnail
|
||||
curve
|
||||
shadow
|
||||
url={getPeopleThumbnailUrl(person)}
|
||||
altText={person.name}
|
||||
title={person.name}
|
||||
widthStyle="90px"
|
||||
heightStyle="90px"
|
||||
hidden={person.isHidden}
|
||||
highlighted={isHighlighted}
|
||||
class="group-focus-visible:outline-2 group-focus-visible:outline-offset-2 group-focus-visible:outline-immich-primary dark:group-focus-visible:outline-immich-dark-primary"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
|
||||
{#if person.birthDate}
|
||||
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
|
||||
{@const age = Math.floor(DateTime.fromISO(asset.localDateTime).diff(personBirthDate, 'years').years)}
|
||||
{@const ageInMonths = Math.floor(
|
||||
DateTime.fromISO(asset.localDateTime).diff(personBirthDate, 'months').months,
|
||||
)}
|
||||
{#if age >= 0}
|
||||
<p
|
||||
class="font-light"
|
||||
title={personBirthDate.toLocaleString(
|
||||
{
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
{ locale: $locale },
|
||||
)}
|
||||
>
|
||||
{#if ageInMonths <= 11}
|
||||
{$t('age_months', { values: { months: ageInMonths } })}
|
||||
{:else if ageInMonths > 12 && ageInMonths <= 23}
|
||||
{$t('age_year_months', { values: { months: ageInMonths - 12 } })}
|
||||
{:else}
|
||||
{$t('age_years', { values: { years: age } })}
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
<DetailPanelPeople {asset} {isOwner} {previousRoute} />
|
||||
|
||||
<div class="px-4 py-4">
|
||||
{#if asset.exifInfo}
|
||||
|
||||
133
web/src/lib/components/asset-viewer/DetailPanelPeople.svelte
Normal file
133
web/src/lib/components/asset-viewer/DetailPanelPeople.svelte
Normal file
@@ -0,0 +1,133 @@
|
||||
<script lang="ts">
|
||||
import ImageThumbnail from '$lib/components/assets/thumbnail/ImageThumbnail.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { type AssetResponseDto } from '@immich/sdk';
|
||||
import { IconButton, Text } from '@immich/ui';
|
||||
import { mdiEye, mdiEyeOff, mdiPencil, mdiPlus } from '@mdi/js';
|
||||
import { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
asset: AssetResponseDto;
|
||||
isOwner: boolean;
|
||||
previousRoute: string;
|
||||
};
|
||||
|
||||
const { asset, isOwner, previousRoute }: Props = $props();
|
||||
|
||||
const unassignedFaces = $derived(asset.unassignedFaces || []);
|
||||
const people = $derived(asset.people || []);
|
||||
const visiblePeople = $derived(
|
||||
people
|
||||
.filter((p) => assetViewerManager.isShowingHiddenPeople || !p.isHidden)
|
||||
.map((person) => {
|
||||
if (!person.birthDate) {
|
||||
return { formattedBirthDate: undefined, formattedAge: undefined, ...person };
|
||||
}
|
||||
const personBirthDate = DateTime.fromISO(person.birthDate);
|
||||
const ageInYears = Math.floor(DateTime.fromISO(asset.localDateTime).diff(personBirthDate, 'years').years);
|
||||
const ageInMonths = Math.floor(DateTime.fromISO(asset.localDateTime).diff(personBirthDate, 'months').months);
|
||||
|
||||
let formattedAge;
|
||||
if (ageInYears < 0) {
|
||||
return { formattedBirthDate: undefined, formattedAge: undefined, ...person };
|
||||
} else if (ageInMonths < 12) {
|
||||
formattedAge = $t('age_months', { values: { months: ageInMonths } });
|
||||
} else if (ageInMonths > 12 && ageInMonths < 24) {
|
||||
formattedAge = $t('age_year_months', { values: { months: ageInMonths - 12 } });
|
||||
} else {
|
||||
formattedAge = $t('age_years', { values: { years: ageInYears } });
|
||||
}
|
||||
|
||||
const formattedBirthDate = personBirthDate.toLocaleString(
|
||||
{
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
{ locale: $locale },
|
||||
);
|
||||
return { formattedBirthDate, formattedAge, ...person };
|
||||
}),
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if !authManager.isSharedLink && isOwner}
|
||||
<section class="px-4 pt-4 text-sm">
|
||||
<div class="flex h-10 w-full items-center justify-between">
|
||||
<Text size="small" color="muted">{$t('people')}</Text>
|
||||
<div class="flex gap-2 items-center">
|
||||
{#if people.some((person) => person.isHidden)}
|
||||
<IconButton
|
||||
aria-label={$t('show_hidden_people')}
|
||||
icon={assetViewerManager.isShowingHiddenPeople ? mdiEyeOff : mdiEye}
|
||||
size="medium"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
onclick={() => assetViewerManager.toggleHiddenPeople()}
|
||||
/>
|
||||
{/if}
|
||||
<IconButton
|
||||
aria-label={$t('tag_people')}
|
||||
icon={mdiPlus}
|
||||
size="medium"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
onclick={() => assetViewerManager.toggleFaceEditMode()}
|
||||
/>
|
||||
|
||||
{#if people.length > 0 || unassignedFaces.length > 0}
|
||||
<IconButton
|
||||
aria-label={$t('edit_people')}
|
||||
icon={mdiPencil}
|
||||
size="medium"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
onclick={() => assetViewerManager.openEditFacesPanel()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 grid {visiblePeople.length <= 6 ? 'grid-cols-3 gap-3' : 'grid-cols-4 gap-2'}">
|
||||
{#each visiblePeople as person (person.id)}
|
||||
{@const isHighlighted = person.faces.some((f) =>
|
||||
assetViewerManager.highlightedFaces.some((b) => b.id === f.id),
|
||||
)}
|
||||
<a
|
||||
class="group outline-none"
|
||||
href={Route.viewPerson(person, { previousRoute })}
|
||||
onfocus={() => assetViewerManager.setHighlightedFaces(person.faces)}
|
||||
onblur={() => assetViewerManager.clearHighlightedFaces()}
|
||||
onpointerenter={() => assetViewerManager.setHighlightedFaces(person.faces)}
|
||||
onpointerleave={() => assetViewerManager.clearHighlightedFaces()}
|
||||
>
|
||||
<ImageThumbnail
|
||||
curve
|
||||
shadow
|
||||
url={getPeopleThumbnailUrl(person)}
|
||||
altText={person.name}
|
||||
title={person.name}
|
||||
widthStyle="100%"
|
||||
hidden={person.isHidden}
|
||||
highlighted={isHighlighted}
|
||||
class="group-focus-visible:outline-2 outline-offset-2 outline-immich-primary dark:outline-immich-dark-primary"
|
||||
/>
|
||||
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
|
||||
{#if person.birthDate && person.formattedAge}
|
||||
<p class="font-light {visiblePeople.length > 6 ? 'text-xs' : ''}" title={person.formattedBirthDate!}>
|
||||
{person.formattedAge}
|
||||
</p>
|
||||
{/if}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
@@ -45,10 +45,7 @@
|
||||
|
||||
await deleteAssets(
|
||||
force,
|
||||
(assetIds) => {
|
||||
timelineManager.removeAssets(assetIds);
|
||||
eventManager.emit('AssetsDelete', assetIds);
|
||||
},
|
||||
(assetIds) => timelineManager.removeAssets(assetIds),
|
||||
selectedAssets,
|
||||
force ? undefined : (assets) => timelineManager.upsertAssets(assets),
|
||||
);
|
||||
|
||||
@@ -33,6 +33,8 @@ class MemoryManager {
|
||||
if (authManager.authenticated) {
|
||||
void this.initialize();
|
||||
}
|
||||
|
||||
this.scheduleHourlyRefresh();
|
||||
}
|
||||
|
||||
ready() {
|
||||
@@ -132,6 +134,29 @@ class MemoryManager {
|
||||
const memories = await searchMemories({ $for: asLocalTimeISO(DateTime.now()) });
|
||||
this.memories = memories.filter((memory) => memory.assets.length > 0);
|
||||
}
|
||||
|
||||
private scheduleHourlyRefresh() {
|
||||
const now = DateTime.utc();
|
||||
let nextEvent = now.set({ minute: 0, second: 5 });
|
||||
|
||||
if (nextEvent <= now) {
|
||||
nextEvent = nextEvent.plus({ hours: 1 });
|
||||
}
|
||||
|
||||
const initialDelay = nextEvent.diff(now).as('milliseconds');
|
||||
|
||||
setTimeout(() => {
|
||||
this.#loading = this.load();
|
||||
|
||||
// Schedule subsequent events hourly
|
||||
setInterval(
|
||||
() => {
|
||||
this.#loading = this.load();
|
||||
},
|
||||
60 * 60 * 1000,
|
||||
);
|
||||
}, initialDelay);
|
||||
}
|
||||
}
|
||||
|
||||
export const memoryManager = new MemoryManager();
|
||||
|
||||
@@ -80,6 +80,8 @@ websocket
|
||||
.on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event))
|
||||
.on('on_session_delete', () => eventManager.emit('SessionDelete'))
|
||||
.on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id }))
|
||||
.on('on_asset_delete', (asset) => eventManager.emit('AssetsDelete', [asset]))
|
||||
.on('on_asset_trash', (assets) => eventManager.emit('AssetsDelete', assets))
|
||||
.on('on_asset_update', (asset) => eventManager.emit('AssetUpdate', asset))
|
||||
.on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id }))
|
||||
.on('on_notification', () => notificationManager.refresh())
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
||||
import UserPageLayout from '$lib/components/layouts/UserPageLayout.svelte';
|
||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import LargeAssetData from './LargeAssetData.svelte';
|
||||
import Portal from '$lib/elements/Portal.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { getNextAsset, getPreviousAsset } from '$lib/utils/asset-utils';
|
||||
import { getNextAsset, getPreviousAsset, navigateToAsset } from '$lib/utils/asset-utils';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -17,7 +18,7 @@
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
let assets = $derived(data.assets);
|
||||
let assets = $state(data.assets);
|
||||
let asset = $derived(data.asset);
|
||||
|
||||
$effect(() => {
|
||||
@@ -36,13 +37,19 @@
|
||||
return asset;
|
||||
};
|
||||
|
||||
const onAction = (payload: Action) => {
|
||||
const preAction = async (payload: Action) => {
|
||||
if (payload.type == 'trash') {
|
||||
assets = assets.filter((a) => a.id != payload.asset.id);
|
||||
assetViewerManager.showAssetViewer(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
(await navigateToAsset(assetCursor?.nextAsset)) ||
|
||||
(await navigateToAsset(assetCursor?.previousAsset)) ||
|
||||
assetViewerManager.showAssetViewer(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onAssetsDelete = (assetIds: string[]) => {
|
||||
assets = assets.filter(({ id }) => !assetIds.includes(id));
|
||||
};
|
||||
|
||||
const onViewAsset = async (asset: AssetResponseDto) => {
|
||||
await navigate({ targetRoute: 'current', assetId: asset.id });
|
||||
};
|
||||
@@ -54,9 +61,11 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<OnEvents {onAssetsDelete} />
|
||||
|
||||
<UserPageLayout title={data.meta.title} scrollbar={true}>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
{#if assets && data.assets.length > 0}
|
||||
{#if assets && assets.length > 0}
|
||||
{#each assets as asset (asset.id)}
|
||||
<LargeAssetData {asset} {onViewAsset} />
|
||||
{/each}
|
||||
@@ -75,7 +84,7 @@
|
||||
cursor={assetCursor}
|
||||
showNavigation={assets.length > 1}
|
||||
{onRandom}
|
||||
{onAction}
|
||||
{preAction}
|
||||
onClose={() => {
|
||||
assetViewerManager.showAssetViewer(false);
|
||||
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||
|
||||
Reference in New Issue
Block a user