mirror of
https://github.com/immich-app/immich.git
synced 2026-04-28 20:18:48 -07:00
Compare commits
2 Commits
fix/player
...
refactor/n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16a83f65b | ||
|
|
2325a359a6 |
@@ -566,20 +566,6 @@ describe('/asset', () => {
|
||||
expect(status).toEqual(200);
|
||||
});
|
||||
|
||||
it('should set the negative rating', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.put(`/assets/${user1Assets[0].id}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ rating: -1 });
|
||||
expect(body).toMatchObject({
|
||||
id: user1Assets[0].id,
|
||||
exifInfo: expect.objectContaining({
|
||||
rating: -1,
|
||||
}),
|
||||
});
|
||||
expect(status).toEqual(200);
|
||||
});
|
||||
|
||||
it('should return tagged people', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.put(`/assets/${user1Assets[0].id}`)
|
||||
|
||||
8
mobile/openapi/lib/api/search_api.dart
generated
8
mobile/openapi/lib/api/search_api.dart
generated
@@ -404,7 +404,7 @@ class SearchApi {
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [num] rating:
|
||||
/// * [int] rating:
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// * [num] size:
|
||||
@@ -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, 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 {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/large-assets';
|
||||
|
||||
@@ -619,7 +619,7 @@ class SearchApi {
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [num] rating:
|
||||
/// * [int] rating:
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// * [num] size:
|
||||
@@ -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, 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 {
|
||||
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));
|
||||
|
||||
@@ -94,7 +94,7 @@ class AssetBulkUpdateDto {
|
||||
|
||||
/// Rating in range [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 5
|
||||
int? rating;
|
||||
|
||||
|
||||
9
mobile/openapi/lib/model/exif_response_dto.dart
generated
9
mobile/openapi/lib/model/exif_response_dto.dart
generated
@@ -102,7 +102,10 @@ class ExifResponseDto {
|
||||
String? projectionType;
|
||||
|
||||
/// Rating
|
||||
num? rating;
|
||||
///
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 5
|
||||
int? rating;
|
||||
|
||||
/// State/province name
|
||||
String? state;
|
||||
@@ -321,9 +324,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'),
|
||||
);
|
||||
|
||||
@@ -237,9 +237,9 @@ class MetadataSearchDto {
|
||||
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Number of results to return
|
||||
///
|
||||
@@ -729,9 +729,7 @@ class MetadataSearchDto {
|
||||
? (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']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
size: num.parse('${json[r'size']}'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
|
||||
8
mobile/openapi/lib/model/random_search_dto.dart
generated
8
mobile/openapi/lib/model/random_search_dto.dart
generated
@@ -145,9 +145,9 @@ class RandomSearchDto {
|
||||
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Number of results to return
|
||||
///
|
||||
@@ -549,9 +549,7 @@ 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']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
size: num.parse('${json[r'size']}'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
|
||||
8
mobile/openapi/lib/model/smart_search_dto.dart
generated
8
mobile/openapi/lib/model/smart_search_dto.dart
generated
@@ -185,9 +185,9 @@ class SmartSearchDto {
|
||||
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Number of results to return
|
||||
///
|
||||
@@ -589,9 +589,7 @@ class SmartSearchDto {
|
||||
: const [],
|
||||
query: mapValueOfType<String>(json, r'query'),
|
||||
queryAssetId: mapValueOfType<String>(json, r'queryAssetId'),
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
size: num.parse('${json[r'size']}'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
tagIds: json[r'tagIds'] is Iterable
|
||||
|
||||
@@ -150,9 +150,9 @@ class StatisticsSearchDto {
|
||||
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// 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)
|
||||
|
||||
2
mobile/openapi/lib/model/update_asset_dto.dart
generated
2
mobile/openapi/lib/model/update_asset_dto.dart
generated
@@ -79,7 +79,7 @@ class UpdateAssetDto {
|
||||
|
||||
/// Rating in range [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 5
|
||||
int? rating;
|
||||
|
||||
|
||||
@@ -9336,12 +9336,17 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"minimum": -1,
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 5,
|
||||
"nullable": true
|
||||
}
|
||||
@@ -15687,7 +15692,7 @@
|
||||
"rating": {
|
||||
"description": "Rating in range [1-5], or null for unrated",
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
@@ -15703,6 +15708,11 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
@@ -17735,8 +17745,10 @@
|
||||
"rating": {
|
||||
"default": null,
|
||||
"description": "Rating",
|
||||
"maximum": 5,
|
||||
"minimum": 1,
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
"type": "integer"
|
||||
},
|
||||
"state": {
|
||||
"default": null,
|
||||
@@ -18755,9 +18767,9 @@
|
||||
"rating": {
|
||||
"description": "Filter by rating [1-5], or null for unrated",
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"minimum": 1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -18771,6 +18783,11 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
@@ -20596,9 +20613,9 @@
|
||||
"rating": {
|
||||
"description": "Filter by rating [1-5], or null for unrated",
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"minimum": 1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -20612,6 +20629,11 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
@@ -21990,9 +22012,9 @@
|
||||
"rating": {
|
||||
"description": "Filter by rating [1-5], or null for unrated",
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"minimum": 1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -22006,6 +22028,11 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
@@ -22250,9 +22277,9 @@
|
||||
"rating": {
|
||||
"description": "Filter by rating [1-5], or null for unrated",
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"minimum": 1,
|
||||
"nullable": true,
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
@@ -22266,6 +22293,11 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
@@ -25195,7 +25227,7 @@
|
||||
"rating": {
|
||||
"description": "Rating in range [1-5], or null for unrated",
|
||||
"maximum": 5,
|
||||
"minimum": -1,
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "integer",
|
||||
"x-immich-history": [
|
||||
@@ -25211,6 +25243,11 @@
|
||||
"version": "v2.6.0",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is deprecated and will be removed in the next major version."
|
||||
},
|
||||
{
|
||||
"version": "v3",
|
||||
"state": "Updated",
|
||||
"description": "Using -1 as a rating is no longer valid."
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
|
||||
@@ -230,7 +230,7 @@ describe(AssetController.name, () => {
|
||||
|
||||
it('should leave correct ratings as-is', async () => {
|
||||
const assetId = factory.uuid();
|
||||
for (const test of [{ rating: -1 }, { rating: 1 }, { rating: 5 }]) {
|
||||
for (const test of [{ rating: 1 }, { rating: 5 }]) {
|
||||
const { status } = await request(ctx.getHttpServer()).put(`/assets/${assetId}`).send(test);
|
||||
expect(service.update).toHaveBeenCalledWith(undefined, assetId, test);
|
||||
expect(status).toBe(200);
|
||||
|
||||
@@ -14,9 +14,8 @@ const UpdateAssetBaseSchema = z
|
||||
latitude: latitudeSchema.optional().describe('Latitude coordinate'),
|
||||
longitude: longitudeSchema.optional().describe('Longitude coordinate'),
|
||||
rating: z
|
||||
.number()
|
||||
.int()
|
||||
.min(-1)
|
||||
.min(0)
|
||||
.max(5)
|
||||
.transform((value) => (value === 0 ? null : value))
|
||||
.nullish()
|
||||
@@ -26,6 +25,7 @@ const UpdateAssetBaseSchema = z
|
||||
.added('v1')
|
||||
.stable('v2')
|
||||
.updated('v2.6.0', 'Using -1 as a rating is deprecated and will be removed in the next major version.')
|
||||
.updated('v3', 'Using -1 as a rating is no longer valid.')
|
||||
.getExtensions(),
|
||||
}),
|
||||
description: z.string().optional().describe('Asset description'),
|
||||
|
||||
@@ -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().min(1).max(5).nullish().default(null).describe('Rating'),
|
||||
})
|
||||
.describe('EXIF response')
|
||||
.meta({ id: 'ExifResponseDto' });
|
||||
|
||||
@@ -34,8 +34,8 @@ 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()
|
||||
.min(-1)
|
||||
.int()
|
||||
.min(1)
|
||||
.max(5)
|
||||
.nullish()
|
||||
.describe('Filter by rating [1-5], or null for unrated')
|
||||
@@ -44,6 +44,7 @@ const BaseSearchSchema = z.object({
|
||||
.added('v1')
|
||||
.stable('v2')
|
||||
.updated('v2.6.0', 'Using -1 as a rating is deprecated and will be removed in the next major version.')
|
||||
.updated('v3', 'Using -1 as a rating is no longer valid.')
|
||||
.getExtensions(),
|
||||
}),
|
||||
ocr: z.string().optional().describe('Filter by OCR text content'),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`UPDATE "asset_exif" SET "rating" = NULL WHERE "rating" = -1;`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
@@ -1436,20 +1436,6 @@ describe(MetadataService.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle valid negative rating value', async () => {
|
||||
const asset = AssetFactory.create();
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
|
||||
mockReadTags({ Rating: -1 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: asset.id });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rating: -1,
|
||||
}),
|
||||
{ lockedPropertiesBehavior: 'skip' },
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle livePhotoCID not set', async () => {
|
||||
const asset = AssetFactory.create();
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
|
||||
|
||||
@@ -304,7 +304,7 @@ export class MetadataService extends BaseService {
|
||||
// comments
|
||||
description: String(exifTags.ImageDescription || exifTags.Description || '').trim(),
|
||||
profileDescription: exifTags.ProfileDescription || null,
|
||||
rating: exifTags.Rating === 0 ? null : validateRange(exifTags.Rating, -1, 5),
|
||||
rating: exifTags.Rating === 0 ? null : validateRange(exifTags.Rating, 1, 5),
|
||||
|
||||
// grouping
|
||||
livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null,
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('duplicate utils', () => {
|
||||
model: null,
|
||||
latitude: undefined,
|
||||
city: '',
|
||||
rating: 0,
|
||||
rating: null,
|
||||
});
|
||||
// fileSizeInByte (1000) + make ('Canon') = 2 truthy values
|
||||
// model (null), latitude (undefined), city (''), rating (0) are all falsy
|
||||
|
||||
Reference in New Issue
Block a user