diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 85ec03173..33ccadba7 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index ab88670bc..a4b58763c 100644 Binary files a/mobile/openapi/lib/api.dart and b/mobile/openapi/lib/api.dart differ diff --git a/mobile/openapi/lib/api/memories_api.dart b/mobile/openapi/lib/api/memories_api.dart index f9280101e..9916029e0 100644 Binary files a/mobile/openapi/lib/api/memories_api.dart and b/mobile/openapi/lib/api/memories_api.dart differ diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 5139c5cf6..cf6784fb8 100644 Binary files a/mobile/openapi/lib/api_client.dart and b/mobile/openapi/lib/api_client.dart differ diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index b34e9210c..1d197a8f9 100644 Binary files a/mobile/openapi/lib/api_helper.dart and b/mobile/openapi/lib/api_helper.dart differ diff --git a/mobile/openapi/lib/model/memory_search_order.dart b/mobile/openapi/lib/model/memory_search_order.dart new file mode 100644 index 000000000..bdf5b5989 Binary files /dev/null and b/mobile/openapi/lib/model/memory_search_order.dart differ diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 6076b43bf..ed3d7a0ba 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -4268,6 +4268,24 @@ "type": "boolean" } }, + { + "name": "order", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/MemorySearchOrder" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "description": "Number of memories to return", + "schema": { + "minimum": 1, + "type": "integer" + } + }, { "name": "type", "required": false, @@ -4381,6 +4399,24 @@ "type": "boolean" } }, + { + "name": "order", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/MemorySearchOrder" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "description": "Number of memories to return", + "schema": { + "minimum": 1, + "type": "integer" + } + }, { "name": "type", "required": false, @@ -12780,6 +12816,14 @@ ], "type": "object" }, + "MemorySearchOrder": { + "enum": [ + "asc", + "desc", + "random" + ], + "type": "string" + }, "MemoryStatisticsResponseDto": { "properties": { "total": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 1ed65e4f2..a0f43c620 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -2956,10 +2956,12 @@ export function reverseGeocode({ lat, lon }: { /** * This endpoint requires the `memory.read` permission. */ -export function searchMemories({ $for, isSaved, isTrashed, $type }: { +export function searchMemories({ $for, isSaved, isTrashed, order, size, $type }: { $for?: string; isSaved?: boolean; isTrashed?: boolean; + order?: MemorySearchOrder; + size?: number; $type?: MemoryType; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -2969,6 +2971,8 @@ export function searchMemories({ $for, isSaved, isTrashed, $type }: { "for": $for, isSaved, isTrashed, + order, + size, "type": $type }))}`, { ...opts @@ -2992,10 +2996,12 @@ export function createMemory({ memoryCreateDto }: { /** * This endpoint requires the `memory.statistics` permission. */ -export function memoriesStatistics({ $for, isSaved, isTrashed, $type }: { +export function memoriesStatistics({ $for, isSaved, isTrashed, order, size, $type }: { $for?: string; isSaved?: boolean; isTrashed?: boolean; + order?: MemorySearchOrder; + size?: number; $type?: MemoryType; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3005,6 +3011,8 @@ export function memoriesStatistics({ $for, isSaved, isTrashed, $type }: { "for": $for, isSaved, isTrashed, + order, + size, "type": $type }))}`, { ...opts @@ -4991,6 +4999,11 @@ export enum JobCommand { Empty = "empty", ClearFailed = "clear-failed" } +export enum MemorySearchOrder { + Asc = "asc", + Desc = "desc", + Random = "random" +} export enum MemoryType { OnThisDay = "on_this_day" } diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index a79511c73..8a86e6669 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -4,7 +4,7 @@ import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; import { Memory } from 'src/database'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { MemoryType } from 'src/enum'; +import { AssetOrderWithRandom, MemoryType } from 'src/enum'; import { ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; class MemoryBaseDto { @@ -27,6 +27,15 @@ export class MemorySearchDto { @ValidateBoolean({ optional: true }) isSaved?: boolean; + + @IsInt() + @IsPositive() + @Type(() => Number) + @ApiProperty({ type: 'integer', description: 'Number of memories to return' }) + size?: number; + + @ValidateEnum({ enum: AssetOrderWithRandom, name: 'MemorySearchOrder', optional: true }) + order?: AssetOrderWithRandom; } class OnThisDayDto { diff --git a/server/src/enum.ts b/server/src/enum.ts index 0755f75f7..a98fee011 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -71,6 +71,14 @@ export enum MemoryType { OnThisDay = 'on_this_day', } +export enum AssetOrderWithRandom { + // Include existing values + Asc = AssetOrder.Asc, + Desc = AssetOrder.Desc, + /** Randomly Ordered */ + Random = 'random', +} + export enum Permission { All = 'all', diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts index c4a914453..e62c08383 100644 --- a/server/src/repositories/memory.repository.ts +++ b/server/src/repositories/memory.repository.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql, Updateable } from 'kysely'; +import { Insertable, Kysely, OrderByDirection, sql, Updateable } from 'kysely'; import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { DateTime } from 'luxon'; import { InjectKysely } from 'nestjs-kysely'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { MemorySearchDto } from 'src/dtos/memory.dto'; -import { AssetVisibility } from 'src/enum'; +import { AssetOrderWithRandom, AssetVisibility } from 'src/enum'; import { DB } from 'src/schema'; import { MemoryTable } from 'src/schema/tables/memory.table'; import { IBulkAsset } from 'src/types'; @@ -72,7 +72,12 @@ export class MemoryRepository implements IBulkAsset { ).as('assets'), ) .selectAll('memory') - .orderBy('memoryAt', 'desc') + .$call((qb) => + dto.order === AssetOrderWithRandom.Random + ? qb.orderBy(sql`RANDOM()`) + : qb.orderBy('memoryAt', (dto.order?.toLowerCase() || 'desc') as OrderByDirection), + ) + .$if(dto.size !== undefined, (qb) => qb.limit(dto.size!)) .execute(); }