Compare commits

..

1 Commits

Author SHA1 Message Date
Jason Rasmussen
b0291b6ad6 fix(server): correctly identify integers 2025-09-10 22:29:03 -04:00
34 changed files with 185 additions and 258 deletions

View File

@@ -608,8 +608,8 @@ class AssetsApi {
///
/// Parameters:
///
/// * [num] count:
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
/// * [int] count:
Future<Response> getRandomWithHttpInfo({ int? count, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/assets/random';
@@ -642,8 +642,8 @@ class AssetsApi {
///
/// Parameters:
///
/// * [num] count:
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
/// * [int] count:
Future<List<AssetResponseDto>?> getRandom({ int? count, }) async {
final response = await getRandomWithHttpInfo( count: count, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));

View File

@@ -75,8 +75,8 @@ class DeprecatedApi {
///
/// Parameters:
///
/// * [num] count:
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
/// * [int] count:
Future<Response> getRandomWithHttpInfo({ int? count, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/assets/random';
@@ -109,8 +109,8 @@ class DeprecatedApi {
///
/// Parameters:
///
/// * [num] count:
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
/// * [int] count:
Future<List<AssetResponseDto>?> getRandom({ int? count, }) async {
final response = await getRandomWithHttpInfo( count: count, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));

View File

@@ -167,14 +167,14 @@ class PeopleApi {
///
/// * [String] closestPersonId:
///
/// * [num] page:
/// * [int] page:
/// Page number for pagination
///
/// * [num] size:
/// * [int] size:
/// Number of items per page
///
/// * [bool] withHidden:
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';
@@ -223,14 +223,14 @@ class PeopleApi {
///
/// * [String] closestPersonId:
///
/// * [num] page:
/// * [int] page:
/// Page number for pagination
///
/// * [num] size:
/// * [int] size:
/// Number of items per page
///
/// * [bool] withHidden:
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));

View File

@@ -348,9 +348,9 @@ class SearchApi {
///
/// * [List<String>] personIds:
///
/// * [num] rating:
/// * [int] rating:
///
/// * [num] size:
/// * [int] size:
///
/// * [String] state:
///
@@ -375,7 +375,7 @@ class SearchApi {
/// * [bool] withDeleted:
///
/// * [bool] withExif:
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, 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, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, 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';
@@ -532,9 +532,9 @@ class SearchApi {
///
/// * [List<String>] personIds:
///
/// * [num] rating:
/// * [int] rating:
///
/// * [num] size:
/// * [int] size:
///
/// * [String] state:
///
@@ -559,7 +559,7 @@ class SearchApi {
/// * [bool] withDeleted:
///
/// * [bool] withExif:
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, 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, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, 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, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, 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));

View File

@@ -40,7 +40,7 @@ class AssetBulkUpdateDto {
/// 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;
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -68,7 +68,7 @@ class AssetBulkUpdateDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? latitude;
double? latitude;
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -76,7 +76,7 @@ class AssetBulkUpdateDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? longitude;
double? longitude;
/// Minimum value: -1
/// Maximum value: 5
@@ -86,7 +86,7 @@ class AssetBulkUpdateDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? rating;
int? rating;
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -202,16 +202,16 @@ 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
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [],
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'),
longitude: num.parse('${json[r'longitude']}'),
rating: num.parse('${json[r'rating']}'),
latitude: (mapValueOfType<num>(json, r'latitude')).toDouble(),
longitude: (mapValueOfType<num>(json, r'longitude')).toDouble(),
rating: mapValueOfType<int>(json, r'rating'),
timeZone: mapValueOfType<String>(json, r'timeZone'),
visibility: AssetVisibility.fromJson(json[r'visibility']),
);

View File

@@ -23,7 +23,7 @@ class DatabaseBackupConfig {
bool enabled;
/// Minimum value: 1
num keepLastAmount;
int keepLastAmount;
@override
bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig &&
@@ -60,7 +60,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;

View File

@@ -45,25 +45,25 @@ class ExifResponseDto {
String? description;
num? exifImageHeight;
int? exifImageHeight;
num? exifImageWidth;
int? exifImageWidth;
String? exposureTime;
num? fNumber;
double? fNumber;
int? fileSizeInByte;
num? focalLength;
double? focalLength;
num? iso;
int? iso;
num? latitude;
double? latitude;
String? lensModel;
num? longitude;
double? longitude;
String? make;
@@ -75,7 +75,7 @@ class ExifResponseDto {
String? projectionType;
num? rating;
int? rating;
String? state;
@@ -263,38 +263,22 @@ 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
: num.parse('${json[r'fNumber']}'),
fNumber: (mapValueOfType<num>(json, r'fNumber'))?.toDouble(),
fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'),
focalLength: json[r'focalLength'] == null
? null
: num.parse('${json[r'focalLength']}'),
iso: json[r'iso'] == null
? null
: num.parse('${json[r'iso']}'),
latitude: json[r'latitude'] == null
? null
: num.parse('${json[r'latitude']}'),
focalLength: (mapValueOfType<num>(json, r'focalLength'))?.toDouble(),
iso: mapValueOfType<int>(json, r'iso'),
latitude: (mapValueOfType<num>(json, r'latitude'))?.toDouble(),
lensModel: mapValueOfType<String>(json, r'lensModel'),
longitude: json[r'longitude'] == null
? null
: num.parse('${json[r'longitude']}'),
longitude: (mapValueOfType<num>(json, r'longitude'))?.toDouble(),
make: mapValueOfType<String>(json, r'make'),
model: mapValueOfType<String>(json, r'model'),
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'),
);

View File

@@ -207,7 +207,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? page;
int? page;
List<String> personIds;
@@ -227,7 +227,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? rating;
int? rating;
/// Minimum value: 1
/// Maximum value: 1000
@@ -237,7 +237,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;
String? state;
@@ -685,13 +685,13 @@ class MetadataSearchDto {
order: AssetOrder.fromJson(json[r'order']) ?? AssetOrder.desc,
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: 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)

View File

@@ -17,7 +17,7 @@ class OnThisDayDto {
});
/// Minimum value: 1
num year;
int year;
@override
bool operator ==(Object other) => identical(this, other) || other is OnThisDayDto &&
@@ -46,7 +46,7 @@ class OnThisDayDto {
final json = value.cast<String, dynamic>();
return OnThisDayDto(
year: num.parse('${json[r'year']}'),
year: mapValueOfType<int>(json, r'year')!,
);
}
return null;

View File

@@ -36,9 +36,9 @@ class PlacesResponseDto {
///
String? admin2name;
num latitude;
double latitude;
num longitude;
double longitude;
String name;
@@ -91,8 +91,8 @@ class PlacesResponseDto {
return PlacesResponseDto(
admin1name: mapValueOfType<String>(json, r'admin1name'),
admin2name: mapValueOfType<String>(json, r'admin2name'),
latitude: num.parse('${json[r'latitude']}'),
longitude: num.parse('${json[r'longitude']}'),
latitude: (mapValueOfType<num>(json, r'latitude')!).toDouble(),
longitude: (mapValueOfType<num>(json, r'longitude')!).toDouble(),
name: mapValueOfType<String>(json, r'name')!,
);
}

View File

@@ -141,7 +141,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? rating;
int? rating;
/// Minimum value: 1
/// Maximum value: 1000
@@ -151,7 +151,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;
String? state;
@@ -513,8 +513,8 @@ class RandomSearchDto {
personIds: json[r'personIds'] is Iterable
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
rating: 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)

View File

@@ -43,7 +43,7 @@ class SessionCreateDto {
/// 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 &&
@@ -92,7 +92,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;

View File

@@ -148,7 +148,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? page;
int? page;
List<String> personIds;
@@ -176,7 +176,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? rating;
int? rating;
/// Minimum value: 1
/// Maximum value: 1000
@@ -186,7 +186,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;
String? state;
@@ -544,14 +544,14 @@ class SmartSearchDto {
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
model: mapValueOfType<String>(json, r'model'),
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: 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)

View File

@@ -145,7 +145,7 @@ class StatisticsSearchDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? rating;
int? rating;
String? state;
@@ -448,7 +448,7 @@ class StatisticsSearchDto {
personIds: json[r'personIds'] is Iterable
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
rating: 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)

View File

@@ -28,7 +28,7 @@ class SystemConfigSmtpTransportDto {
/// Minimum value: 0
/// Maximum value: 65535
num port;
int port;
String username;
@@ -74,7 +74,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')!,
username: mapValueOfType<String>(json, r'username')!,
);
}

View File

@@ -53,7 +53,7 @@ class UpdateAssetDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? latitude;
double? latitude;
String? livePhotoVideoId;
@@ -63,7 +63,7 @@ class UpdateAssetDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? longitude;
double? longitude;
/// Minimum value: -1
/// Maximum value: 5
@@ -73,7 +73,7 @@ class UpdateAssetDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? rating;
int? rating;
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -166,10 +166,10 @@ class UpdateAssetDto {
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
description: mapValueOfType<String>(json, r'description'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'),
latitude: (mapValueOfType<num>(json, r'latitude')).toDouble(),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
longitude: num.parse('${json[r'longitude']}'),
rating: num.parse('${json[r'rating']}'),
longitude: (mapValueOfType<num>(json, r'longitude')).toDouble(),
rating: mapValueOfType<int>(json, r'rating'),
visibility: AssetVisibility.fromJson(json[r'visibility']),
);
}

View File

@@ -2035,7 +2035,7 @@
"in": "query",
"schema": {
"minimum": 1,
"type": "number"
"type": "integer"
}
}
],
@@ -5242,7 +5242,7 @@
"schema": {
"minimum": 1,
"default": 1,
"type": "number"
"type": "integer"
}
},
{
@@ -5254,7 +5254,7 @@
"minimum": 1,
"maximum": 1000,
"default": 500,
"type": "number"
"type": "integer"
}
},
{
@@ -5955,7 +5955,7 @@
"schema": {
"minimum": -1,
"maximum": 5,
"type": "number"
"type": "integer"
}
},
{
@@ -5965,7 +5965,7 @@
"schema": {
"minimum": 1,
"maximum": 1000,
"type": "number"
"type": "integer"
}
},
{
@@ -10408,7 +10408,7 @@
"type": "string"
},
"dateTimeRelative": {
"type": "number"
"type": "integer"
},
"description": {
"type": "string"
@@ -10428,15 +10428,17 @@
"type": "boolean"
},
"latitude": {
"format": "double",
"type": "number"
},
"longitude": {
"format": "double",
"type": "number"
},
"rating": {
"maximum": 5,
"minimum": -1,
"type": "number"
"type": "integer"
},
"timeZone": {
"type": "string"
@@ -11571,7 +11573,7 @@
},
"keepLastAmount": {
"minimum": 1,
"type": "number"
"type": "integer"
}
},
"required": [
@@ -11764,12 +11766,12 @@
"exifImageHeight": {
"default": null,
"nullable": true,
"type": "number"
"type": "integer"
},
"exifImageWidth": {
"default": null,
"nullable": true,
"type": "number"
"type": "integer"
},
"exposureTime": {
"default": null,
@@ -11778,6 +11780,7 @@
},
"fNumber": {
"default": null,
"format": "double",
"nullable": true,
"type": "number"
},
@@ -11789,16 +11792,18 @@
},
"focalLength": {
"default": null,
"format": "double",
"nullable": true,
"type": "number"
},
"iso": {
"default": null,
"nullable": true,
"type": "number"
"type": "integer"
},
"latitude": {
"default": null,
"format": "double",
"nullable": true,
"type": "number"
},
@@ -11809,6 +11814,7 @@
},
"longitude": {
"default": null,
"format": "double",
"nullable": true,
"type": "number"
},
@@ -11841,7 +11847,7 @@
"rating": {
"default": null,
"nullable": true,
"type": "number"
"type": "integer"
},
"state": {
"default": null,
@@ -12594,7 +12600,7 @@
},
"page": {
"minimum": 1,
"type": "number"
"type": "integer"
},
"personIds": {
"items": {
@@ -12609,12 +12615,12 @@
"rating": {
"maximum": 5,
"minimum": -1,
"type": "number"
"type": "integer"
},
"size": {
"maximum": 1000,
"minimum": 1,
"type": "number"
"type": "integer"
},
"state": {
"nullable": true,
@@ -12891,7 +12897,7 @@
"properties": {
"year": {
"minimum": 1,
"type": "number"
"type": "integer"
}
},
"required": [
@@ -13440,9 +13446,11 @@
"type": "string"
},
"latitude": {
"format": "double",
"type": "number"
},
"longitude": {
"format": "double",
"type": "number"
},
"name": {
@@ -13566,12 +13574,12 @@
"rating": {
"maximum": 5,
"minimum": -1,
"type": "number"
"type": "integer"
},
"size": {
"maximum": 1000,
"minimum": 1,
"type": "number"
"type": "integer"
},
"state": {
"nullable": true,
@@ -14254,7 +14262,7 @@
"duration": {
"description": "session duration, in seconds",
"minimum": 1,
"type": "number"
"type": "integer"
}
},
"type": "object"
@@ -14650,7 +14658,7 @@
},
"page": {
"minimum": 1,
"type": "number"
"type": "integer"
},
"personIds": {
"items": {
@@ -14669,12 +14677,12 @@
"rating": {
"maximum": 5,
"minimum": -1,
"type": "number"
"type": "integer"
},
"size": {
"maximum": 1000,
"minimum": 1,
"type": "number"
"type": "integer"
},
"state": {
"nullable": true,
@@ -14863,7 +14871,7 @@
"rating": {
"maximum": 5,
"minimum": -1,
"type": "number"
"type": "integer"
},
"state": {
"nullable": true,
@@ -16683,7 +16691,7 @@
"port": {
"maximum": 65535,
"minimum": 0,
"type": "number"
"type": "integer"
},
"username": {
"type": "string"
@@ -17299,6 +17307,7 @@
"type": "boolean"
},
"latitude": {
"format": "double",
"type": "number"
},
"livePhotoVideoId": {
@@ -17307,12 +17316,13 @@
"type": "string"
},
"longitude": {
"format": "double",
"type": "number"
},
"rating": {
"maximum": 5,
"minimum": -1,
"type": "number"
"type": "integer"
},
"visibility": {
"allOf": [

8
pnpm-lock.yaml generated
View File

@@ -737,9 +737,6 @@ importers:
happy-dom:
specifier: ^18.0.1
version: 18.0.1
hash-wasm:
specifier: ^4.12.0
version: 4.12.0
intl-messageformat:
specifier: ^10.7.11
version: 10.7.16
@@ -6765,9 +6762,6 @@ packages:
resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hash-wasm@4.12.0:
resolution: {integrity: sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@@ -18629,8 +18623,6 @@ snapshots:
has-yarn@3.0.0: {}
hash-wasm@4.12.0: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2

View File

@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
@@ -46,17 +46,20 @@ export class UpdateAssetBase {
@ValidateGPS()
@IsLatitude()
@IsNotEmpty()
@ApiPropertyOptional({ type: 'number', format: 'double' })
latitude?: number;
@ValidateGPS()
@IsLongitude()
@IsNotEmpty()
@ApiPropertyOptional({ type: 'number', format: 'double' })
longitude?: number;
@Optional()
@IsInt()
@Max(5)
@Min(-1)
@ApiPropertyOptional({ type: 'integer' })
rating?: number;
@Optional()
@@ -74,6 +77,7 @@ export class AssetBulkUpdateDto extends UpdateAssetBase {
@IsNotSiblingOf(['dateTimeOriginal'])
@Optional()
@IsInt()
@ApiPropertyOptional({ type: 'integer' })
dateTimeRelative?: number;
@IsNotSiblingOf(['dateTimeOriginal'])
@@ -92,6 +96,7 @@ export class RandomAssetsDto {
@IsInt()
@IsPositive()
@Type(() => Number)
@ApiPropertyOptional({ type: 'integer' })
count?: number;
}

View File

@@ -1,30 +1,37 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Exif } from 'src/database';
export class ExifResponseDto {
make?: string | null = null;
model?: string | null = null;
@ApiPropertyOptional({ type: 'integer' })
exifImageWidth?: number | null = null;
@ApiPropertyOptional({ type: 'integer' })
exifImageHeight?: number | null = null;
@ApiProperty({ type: 'integer', format: 'int64' })
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
fileSizeInByte?: number | null = null;
orientation?: string | null = null;
dateTimeOriginal?: Date | null = null;
modifyDate?: Date | null = null;
timeZone?: string | null = null;
lensModel?: string | null = null;
@ApiPropertyOptional({ type: 'number', format: 'double' })
fNumber?: number | null = null;
@ApiPropertyOptional({ type: 'number', format: 'double' })
focalLength?: number | null = null;
@ApiPropertyOptional({ type: 'integer' })
iso?: number | null = null;
exposureTime?: string | null = null;
@ApiPropertyOptional({ type: 'number', format: 'double' })
latitude?: number | null = null;
@ApiPropertyOptional({ type: 'number', format: 'double' })
longitude?: number | null = null;
city?: string | null = null;
state?: string | null = null;
country?: string | null = null;
description?: string | null = null;
projectionType?: string | null = null;
@ApiPropertyOptional({ type: 'integer' })
rating?: number | null = null;
}

View File

@@ -4,12 +4,12 @@ import { IsLatitude, IsLongitude } from 'class-validator';
import { ValidateBoolean, ValidateDate } from 'src/validation';
export class MapReverseGeocodeDto {
@ApiProperty({ format: 'double' })
@ApiProperty({ type: 'number', format: 'double' })
@Type(() => Number)
@IsLatitude({ message: ({ property }) => `${property} must be a number between -90 and 90` })
lat!: number;
@ApiProperty({ format: 'double' })
@ApiProperty({ type: 'number', format: 'double' })
@Type(() => Number)
@IsLongitude({ message: ({ property }) => `${property} must be a number between -180 and 180` })
lon!: number;

View File

@@ -32,6 +32,7 @@ export class MemorySearchDto {
class OnThisDayDto {
@IsInt()
@IsPositive()
@ApiProperty({ type: 'integer' })
year!: number;
}

View File

@@ -89,14 +89,14 @@ export class PersonSearchDto {
closestAssetId?: string;
/** Page number for pagination */
@ApiPropertyOptional()
@ApiPropertyOptional({ type: 'integer' })
@IsInt()
@Min(1)
@Type(() => Number)
page: number = 1;
/** Number of items per page */
@ApiPropertyOptional()
@ApiPropertyOptional({ type: 'integer' })
@IsInt()
@Min(1)
@Max(1000)

View File

@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
import { Place } from 'src/database';
@@ -100,6 +100,7 @@ class BaseSearchDto {
@IsInt()
@Max(5)
@Min(-1)
@ApiPropertyOptional({ type: 'integer' })
rating?: number;
}
@@ -115,6 +116,7 @@ class BaseSearchWithResultsDto extends BaseSearchDto {
@Max(1000)
@Type(() => Number)
@Optional()
@ApiPropertyOptional({ type: 'integer' })
size?: number;
}
@@ -186,6 +188,7 @@ export class MetadataSearchDto extends RandomSearchDto {
@Min(1)
@Type(() => Number)
@Optional()
@ApiPropertyOptional({ type: 'integer' })
page?: number;
}
@@ -215,6 +218,7 @@ export class SmartSearchDto extends BaseSearchWithResultsDto {
@Min(1)
@Type(() => Number)
@Optional()
@ApiPropertyOptional({ type: 'integer' })
page?: number;
}
@@ -235,7 +239,9 @@ export class SearchPeopleDto {
export class PlacesResponseDto {
name!: string;
@ApiProperty({ type: 'number', format: 'double' })
latitude!: number;
@ApiProperty({ type: 'number', format: 'double' })
longitude!: number;
admin1name?: string;
admin2name?: string;

View File

@@ -1,3 +1,4 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Equals, IsInt, IsPositive, IsString } from 'class-validator';
import { Session } from 'src/database';
import { Optional, ValidateBoolean } from 'src/validation';
@@ -9,6 +10,7 @@ export class SessionCreateDto {
@IsInt()
@IsPositive()
@Optional()
@ApiPropertyOptional({ type: 'integer' })
duration?: number;
@IsString()

View File

@@ -53,6 +53,7 @@ export class DatabaseBackupConfig {
@IsInt()
@IsPositive()
@IsNotEmpty()
@ApiProperty({ type: 'integer' })
keepLastAmount!: number;
}
@@ -451,6 +452,7 @@ class SystemConfigSmtpTransportDto {
@IsNumber()
@Min(0)
@Max(65_535)
@ApiProperty({ type: 'integer' })
port!: number;
@IsString()

View File

@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsDateString, IsInt, IsPositive, ValidateNested } from 'class-validator';
import { AssetOrder, UserAvatarColor } from 'src/enum';
@@ -72,7 +72,7 @@ class DownloadUpdate implements Partial<DownloadResponse> {
@Optional()
@IsInt()
@IsPositive()
@ApiProperty({ type: 'integer' })
@ApiPropertyOptional({ type: 'integer' })
archiveSize?: number;
@ValidateBoolean({ optional: true })

View File

@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsEmail, IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
import { User, UserAdmin } from 'src/database';
@@ -93,7 +93,7 @@ export class UserAdminCreateDto {
@Optional({ nullable: true })
@IsInt()
@Min(0)
@ApiProperty({ type: 'integer', format: 'int64' })
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
quotaSizeInBytes?: number | null;
@ValidateBoolean({ optional: true })
@@ -139,7 +139,7 @@ export class UserAdminUpdateDto {
@Optional({ nullable: true })
@IsInt()
@Min(0)
@ApiProperty({ type: 'integer', format: 'int64' })
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
quotaSizeInBytes?: number | null;
@ValidateBoolean({ optional: true })

View File

@@ -46,7 +46,6 @@
"geojson": "^0.5.0",
"handlebars": "^4.7.8",
"happy-dom": "^18.0.1",
"hash-wasm": "^4.12.0",
"intl-messageformat": "^10.7.11",
"justified-layout": "^4.1.0",
"lodash-es": "^4.17.21",

View File

@@ -92,8 +92,8 @@
{#if uploadAsset.state === UploadState.STARTED}
<div class="text-black relative mt-[5px] h-[15px] w-full rounded-md bg-gray-300 dark:bg-gray-700">
<div class="h-[15px] rounded-md bg-primary/50 transition-all" style={`width: ${uploadAsset.progress}%`}></div>
<p class="absolute top-0 h-full w-full text-center text-dark text-[10px]">
<div class="h-[15px] rounded-md bg-immich-primary transition-all" style={`width: ${uploadAsset.progress}%`}></div>
<p class="absolute top-0 h-full w-full text-center text-primary text-[10px]">
{#if uploadAsset.message}
{uploadAsset.message}
{:else}

View File

@@ -7,7 +7,6 @@ import { uploadRequest } from '$lib/utils';
import { addAssetsToAlbum } from '$lib/utils/asset-utils';
import { ExecutorQueue } from '$lib/utils/executor-queue';
import { asQueryString } from '$lib/utils/shared-links';
import { hashFile } from '$lib/utils/sw-messaging';
import {
Action,
AssetMediaStatus,
@@ -155,16 +154,16 @@ async function fileUploader({
}
let responseData: { id: string; status: AssetMediaStatus; isTrashed?: boolean } | undefined;
if (!authManager.isSharedLink) {
if (crypto?.subtle?.digest && !authManager.isSharedLink) {
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_hashing') });
await tick();
try {
const checksum = await hashFile(assetFile, {
id: deviceAssetId,
onProgress: (progress, total) => {
uploadAssetsStore.updateProgress(deviceAssetId, progress, total);
},
});
const bytes = await assetFile.arrayBuffer();
const hash = await crypto.subtle.digest('SHA-1', bytes);
const checksum = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
const {
results: [checkUploadResult],
} = await checkBulkUpload({ assetBulkUploadCheckDto: { assets: [{ id: assetFile.name, checksum }] } });

View File

@@ -1,46 +1,8 @@
type OnProgress = (progress: number, total: number) => void;
type Callback = { onChecksum: (checksum: string) => void; onProgress: OnProgress };
const callbacks: Record<string, Callback> = {};
const broadcast = new BroadcastChannel('immich');
broadcast.addEventListener('message', (event) => {
const { type, id, checksum, progress, total } = event.data;
switch (type) {
case 'checksum': {
if (id && checksum) {
const callback = callbacks[id];
callback?.onChecksum(checksum);
delete callbacks[id];
}
break;
}
case 'hash.progress': {
if (id && progress && total) {
const callback = callbacks[id];
callback?.onProgress(progress, total);
}
break;
}
}
});
export const cancelImageUrl = (url: string) => {
export function cancelImageUrl(url: string) {
broadcast.postMessage({ type: 'cancel', url });
};
export const preloadImageUrl = (url: string) => {
}
export function preloadImageUrl(url: string) {
broadcast.postMessage({ type: 'preload', url });
};
export const hashFile = (file: File, { id, onProgress }: { id: string; onProgress: OnProgress }): Promise<string> => {
return new Promise((onChecksum) => {
if (callbacks[id]) {
return;
}
callbacks[id] = { onChecksum, onProgress };
broadcast.postMessage({ type: 'hash', id, file });
});
};
}

View File

@@ -1,68 +1,25 @@
import { createSHA1, sha1 } from 'hash-wasm';
import { handleCancel, handlePreload } from './request';
type HashRequest = { id: string; file: File };
const MAX_HASH_FILE_SIZE = 100 * 1024 * 1024; // 100 MiB
const broadcast = new BroadcastChannel('immich');
broadcast.addEventListener('message', async (event) => {
if (!event.data) {
return;
}
const url = new URL(event.data.url, event.origin);
switch (event.data.type) {
case 'preload': {
handlePreload(url);
break;
export const installBroadcastChannelListener = () => {
const broadcast = new BroadcastChannel('immich');
// eslint-disable-next-line unicorn/prefer-add-event-listener
broadcast.onmessage = (event) => {
if (!event.data) {
return;
}
case 'cancel': {
handleCancel(url);
break;
}
const url = new URL(event.data.url, event.origin);
case 'hash': {
await handleHash(event.data);
}
}
});
switch (event.data.type) {
case 'preload': {
handlePreload(url);
break;
}
const handleHash = async (request: HashRequest) => {
const { id, file } = request;
const checksum = file.size <= MAX_HASH_FILE_SIZE ? await hashSmallFile(request) : await hashLargeFile(request);
broadcast.postMessage({ type: 'checksum', id, checksum });
};
const hashSmallFile = async ({ file }: HashRequest): Promise<string> => {
const buffer = await file.arrayBuffer();
return sha1(new Uint8Array(buffer));
};
const hashLargeFile = async ({ id, file }: HashRequest): Promise<string> => {
const sha1 = await createSHA1();
const reader = file.stream().getReader();
let processedBytes = 0;
let lastUpdate = Date.now();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
sha1.update(value);
processedBytes += value.length;
broadcast.postMessage({
type: 'hash.progress',
id,
progress: processedBytes,
total: file.size,
});
}
return sha1.digest('hex');
case 'cancel': {
handleCancel(url);
break;
}
}
};
};

View File

@@ -2,7 +2,7 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import './broadcast-channel';
import { installBroadcastChannelListener } from './broadcast-channel';
import { prune } from './cache';
import { handleRequest } from './request';
@@ -36,3 +36,4 @@ const handleFetch = (event: FetchEvent): void => {
sw.addEventListener('install', handleInstall, { passive: true });
sw.addEventListener('activate', handleActivate, { passive: true });
sw.addEventListener('fetch', handleFetch, { passive: true });
installBroadcastChannelListener();