From b845184c80295603e3a02dfad84ff2755d417aff Mon Sep 17 00:00:00 2001
From: Jason Rasmussen <jason@rasm.me>
Date: Wed, 30 Apr 2025 14:23:32 -0400
Subject: [PATCH] chore: remove old memory lane implementation (#18000)

---
 mobile/openapi/README.md                      |   2 -
 mobile/openapi/lib/api.dart                   |   1 -
 mobile/openapi/lib/api/assets_api.dart        |  57 ----------
 mobile/openapi/lib/api_client.dart            |   2 -
 .../lib/model/memory_lane_response_dto.dart   | 107 ------------------
 open-api/immich-openapi-specs.json            |  74 ------------
 open-api/typescript-sdk/src/fetch-client.ts   |  18 ---
 server/src/controllers/asset.controller.ts    |   9 +-
 server/src/dtos/asset-response.dto.ts         |   7 --
 server/src/services/asset.service.spec.ts     |  59 +---------
 server/src/services/asset.service.ts          |  29 +----
 11 files changed, 3 insertions(+), 362 deletions(-)
 delete mode 100644 mobile/openapi/lib/model/memory_lane_response_dto.dart

diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md
index a4fbbf371a..7c8afb09e4 100644
--- a/mobile/openapi/README.md
+++ b/mobile/openapi/README.md
@@ -100,7 +100,6 @@ Class | Method | HTTP request | Description
 *AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | getAllUserAssetsByDeviceId
 *AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | 
 *AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | 
-*AssetsApi* | [**getMemoryLane**](doc//AssetsApi.md#getmemorylane) | **GET** /assets/memory-lane | 
 *AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | 
 *AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | 
 *AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | replaceAsset
@@ -353,7 +352,6 @@ Class | Method | HTTP request | Description
  - [MemoriesResponse](doc//MemoriesResponse.md)
  - [MemoriesUpdate](doc//MemoriesUpdate.md)
  - [MemoryCreateDto](doc//MemoryCreateDto.md)
- - [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md)
  - [MemoryResponseDto](doc//MemoryResponseDto.md)
  - [MemoryType](doc//MemoryType.md)
  - [MemoryUpdateDto](doc//MemoryUpdateDto.md)
diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart
index 78fa31691d..ab9b251e01 100644
--- a/mobile/openapi/lib/api.dart
+++ b/mobile/openapi/lib/api.dart
@@ -156,7 +156,6 @@ part 'model/map_reverse_geocode_response_dto.dart';
 part 'model/memories_response.dart';
 part 'model/memories_update.dart';
 part 'model/memory_create_dto.dart';
-part 'model/memory_lane_response_dto.dart';
 part 'model/memory_response_dto.dart';
 part 'model/memory_type.dart';
 part 'model/memory_update_dto.dart';
diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart
index f52c70b37f..f744988449 100644
--- a/mobile/openapi/lib/api/assets_api.dart
+++ b/mobile/openapi/lib/api/assets_api.dart
@@ -404,63 +404,6 @@ class AssetsApi {
     return null;
   }
 
-  /// Performs an HTTP 'GET /assets/memory-lane' operation and returns the [Response].
-  /// Parameters:
-  ///
-  /// * [int] day (required):
-  ///
-  /// * [int] month (required):
-  Future<Response> getMemoryLaneWithHttpInfo(int day, int month,) async {
-    // ignore: prefer_const_declarations
-    final apiPath = r'/assets/memory-lane';
-
-    // ignore: prefer_final_locals
-    Object? postBody;
-
-    final queryParams = <QueryParam>[];
-    final headerParams = <String, String>{};
-    final formParams = <String, String>{};
-
-      queryParams.addAll(_queryParams('', 'day', day));
-      queryParams.addAll(_queryParams('', 'month', month));
-
-    const contentTypes = <String>[];
-
-
-    return apiClient.invokeAPI(
-      apiPath,
-      'GET',
-      queryParams,
-      postBody,
-      headerParams,
-      formParams,
-      contentTypes.isEmpty ? null : contentTypes.first,
-    );
-  }
-
-  /// Parameters:
-  ///
-  /// * [int] day (required):
-  ///
-  /// * [int] month (required):
-  Future<List<MemoryLaneResponseDto>?> getMemoryLane(int day, int month,) async {
-    final response = await getMemoryLaneWithHttpInfo(day, month,);
-    if (response.statusCode >= HttpStatus.badRequest) {
-      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
-    }
-    // When a remote server returns no body with a status of 204, we shall not decode it.
-    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
-    // FormatException when trying to decode an empty string.
-    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
-      final responseBody = await _decodeBodyBytes(response);
-      return (await apiClient.deserializeAsync(responseBody, 'List<MemoryLaneResponseDto>') as List)
-        .cast<MemoryLaneResponseDto>()
-        .toList(growable: false);
-
-    }
-    return null;
-  }
-
   /// This property was deprecated in v1.116.0
   ///
   /// Note: This method returns the HTTP [Response].
diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart
index 9d0e80e58b..ec5eb09729 100644
--- a/mobile/openapi/lib/api_client.dart
+++ b/mobile/openapi/lib/api_client.dart
@@ -368,8 +368,6 @@ class ApiClient {
           return MemoriesUpdate.fromJson(value);
         case 'MemoryCreateDto':
           return MemoryCreateDto.fromJson(value);
-        case 'MemoryLaneResponseDto':
-          return MemoryLaneResponseDto.fromJson(value);
         case 'MemoryResponseDto':
           return MemoryResponseDto.fromJson(value);
         case 'MemoryType':
diff --git a/mobile/openapi/lib/model/memory_lane_response_dto.dart b/mobile/openapi/lib/model/memory_lane_response_dto.dart
deleted file mode 100644
index 27248d05c1..0000000000
--- a/mobile/openapi/lib/model/memory_lane_response_dto.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-//
-// AUTO-GENERATED FILE, DO NOT MODIFY!
-//
-// @dart=2.18
-
-// ignore_for_file: unused_element, unused_import
-// ignore_for_file: always_put_required_named_parameters_first
-// ignore_for_file: constant_identifier_names
-// ignore_for_file: lines_longer_than_80_chars
-
-part of openapi.api;
-
-class MemoryLaneResponseDto {
-  /// Returns a new [MemoryLaneResponseDto] instance.
-  MemoryLaneResponseDto({
-    this.assets = const [],
-    required this.yearsAgo,
-  });
-
-  List<AssetResponseDto> assets;
-
-  int yearsAgo;
-
-  @override
-  bool operator ==(Object other) => identical(this, other) || other is MemoryLaneResponseDto &&
-    _deepEquality.equals(other.assets, assets) &&
-    other.yearsAgo == yearsAgo;
-
-  @override
-  int get hashCode =>
-    // ignore: unnecessary_parenthesis
-    (assets.hashCode) +
-    (yearsAgo.hashCode);
-
-  @override
-  String toString() => 'MemoryLaneResponseDto[assets=$assets, yearsAgo=$yearsAgo]';
-
-  Map<String, dynamic> toJson() {
-    final json = <String, dynamic>{};
-      json[r'assets'] = this.assets;
-      json[r'yearsAgo'] = this.yearsAgo;
-    return json;
-  }
-
-  /// Returns a new [MemoryLaneResponseDto] instance and imports its values from
-  /// [value] if it's a [Map], null otherwise.
-  // ignore: prefer_constructors_over_static_methods
-  static MemoryLaneResponseDto? fromJson(dynamic value) {
-    upgradeDto(value, "MemoryLaneResponseDto");
-    if (value is Map) {
-      final json = value.cast<String, dynamic>();
-
-      return MemoryLaneResponseDto(
-        assets: AssetResponseDto.listFromJson(json[r'assets']),
-        yearsAgo: mapValueOfType<int>(json, r'yearsAgo')!,
-      );
-    }
-    return null;
-  }
-
-  static List<MemoryLaneResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
-    final result = <MemoryLaneResponseDto>[];
-    if (json is List && json.isNotEmpty) {
-      for (final row in json) {
-        final value = MemoryLaneResponseDto.fromJson(row);
-        if (value != null) {
-          result.add(value);
-        }
-      }
-    }
-    return result.toList(growable: growable);
-  }
-
-  static Map<String, MemoryLaneResponseDto> mapFromJson(dynamic json) {
-    final map = <String, MemoryLaneResponseDto>{};
-    if (json is Map && json.isNotEmpty) {
-      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
-      for (final entry in json.entries) {
-        final value = MemoryLaneResponseDto.fromJson(entry.value);
-        if (value != null) {
-          map[entry.key] = value;
-        }
-      }
-    }
-    return map;
-  }
-
-  // maps a json object with a list of MemoryLaneResponseDto-objects as value to a dart map
-  static Map<String, List<MemoryLaneResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
-    final map = <String, List<MemoryLaneResponseDto>>{};
-    if (json is Map && json.isNotEmpty) {
-      // ignore: parameter_assignments
-      json = json.cast<String, dynamic>();
-      for (final entry in json.entries) {
-        map[entry.key] = MemoryLaneResponseDto.listFromJson(entry.value, growable: growable,);
-      }
-    }
-    return map;
-  }
-
-  /// The list of required keys that must be present in a JSON.
-  static const requiredKeys = <String>{
-    'assets',
-    'yearsAgo',
-  };
-}
-
diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index adad1308c2..0951177c72 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -1726,62 +1726,6 @@
         ]
       }
     },
-    "/assets/memory-lane": {
-      "get": {
-        "operationId": "getMemoryLane",
-        "parameters": [
-          {
-            "name": "day",
-            "required": true,
-            "in": "query",
-            "schema": {
-              "minimum": 1,
-              "maximum": 31,
-              "type": "integer"
-            }
-          },
-          {
-            "name": "month",
-            "required": true,
-            "in": "query",
-            "schema": {
-              "minimum": 1,
-              "maximum": 12,
-              "type": "integer"
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "content": {
-              "application/json": {
-                "schema": {
-                  "items": {
-                    "$ref": "#/components/schemas/MemoryLaneResponseDto"
-                  },
-                  "type": "array"
-                }
-              }
-            },
-            "description": ""
-          }
-        },
-        "security": [
-          {
-            "bearer": []
-          },
-          {
-            "cookie": []
-          },
-          {
-            "api_key": []
-          }
-        ],
-        "tags": [
-          "Assets"
-        ]
-      }
-    },
     "/assets/random": {
       "get": {
         "deprecated": true,
@@ -10117,24 +10061,6 @@
         ],
         "type": "object"
       },
-      "MemoryLaneResponseDto": {
-        "properties": {
-          "assets": {
-            "items": {
-              "$ref": "#/components/schemas/AssetResponseDto"
-            },
-            "type": "array"
-          },
-          "yearsAgo": {
-            "type": "integer"
-          }
-        },
-        "required": [
-          "assets",
-          "yearsAgo"
-        ],
-        "type": "object"
-      },
       "MemoryResponseDto": {
         "properties": {
           "assets": {
diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts
index 2684d2558f..20fb72b486 100644
--- a/open-api/typescript-sdk/src/fetch-client.ts
+++ b/open-api/typescript-sdk/src/fetch-client.ts
@@ -462,10 +462,6 @@ export type AssetJobsDto = {
     assetIds: string[];
     name: AssetJobName;
 };
-export type MemoryLaneResponseDto = {
-    assets: AssetResponseDto[];
-    yearsAgo: number;
-};
 export type AssetStatsResponseDto = {
     images: number;
     total: number;
@@ -1866,20 +1862,6 @@ export function runAssetJobs({ assetJobsDto }: {
         body: assetJobsDto
     })));
 }
-export function getMemoryLane({ day, month }: {
-    day: number;
-    month: number;
-}, opts?: Oazapfts.RequestOpts) {
-    return oazapfts.ok(oazapfts.fetchJson<{
-        status: 200;
-        data: MemoryLaneResponseDto[];
-    }>(`/assets/memory-lane${QS.query(QS.explode({
-        day,
-        month
-    }))}`, {
-        ...opts
-    }));
-}
 /**
  * This property was deprecated in v1.116.0
  */
diff --git a/server/src/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts
index 9a7252a087..925b64c8a8 100644
--- a/server/src/controllers/asset.controller.ts
+++ b/server/src/controllers/asset.controller.ts
@@ -1,7 +1,7 @@
 import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
 import { ApiOperation, ApiTags } from '@nestjs/swagger';
 import { EndpointLifecycle } from 'src/decorators';
-import { AssetResponseDto, MemoryLaneResponseDto } from 'src/dtos/asset-response.dto';
+import { AssetResponseDto } from 'src/dtos/asset-response.dto';
 import {
   AssetBulkDeleteDto,
   AssetBulkUpdateDto,
@@ -13,7 +13,6 @@ import {
   UpdateAssetDto,
 } from 'src/dtos/asset.dto';
 import { AuthDto } from 'src/dtos/auth.dto';
-import { MemoryLaneDto } from 'src/dtos/search.dto';
 import { RouteKey } from 'src/enum';
 import { Auth, Authenticated } from 'src/middleware/auth.guard';
 import { AssetService } from 'src/services/asset.service';
@@ -24,12 +23,6 @@ import { UUIDParamDto } from 'src/validation';
 export class AssetController {
   constructor(private service: AssetService) {}
 
-  @Get('memory-lane')
-  @Authenticated()
-  getMemoryLane(@Auth() auth: AuthDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
-    return this.service.getMemoryLane(auth, dto);
-  }
-
   @Get('random')
   @Authenticated()
   @EndpointLifecycle({ deprecatedAt: 'v1.116.0' })
diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts
index c0e589f380..3732e665cd 100644
--- a/server/src/dtos/asset-response.dto.ts
+++ b/server/src/dtos/asset-response.dto.ts
@@ -199,10 +199,3 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
     resized: true,
   };
 }
-
-export class MemoryLaneResponseDto {
-  @ApiProperty({ type: 'integer' })
-  yearsAgo!: number;
-
-  assets!: AssetResponseDto[];
-}
diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts
index af4fef5bd5..ecfb7936d2 100755
--- a/server/src/services/asset.service.spec.ts
+++ b/server/src/services/asset.service.spec.ts
@@ -1,6 +1,6 @@
 import { BadRequestException } from '@nestjs/common';
 import { DateTime } from 'luxon';
-import { MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
+import { MapAsset } from 'src/dtos/asset-response.dto';
 import { AssetJobName, AssetStatsResponseDto } from 'src/dtos/asset.dto';
 import { AssetStatus, AssetType, JobName, JobStatus } from 'src/enum';
 import { AssetStats } from 'src/repositories/asset.repository';
@@ -11,7 +11,6 @@ import { faceStub } from 'test/fixtures/face.stub';
 import { userStub } from 'test/fixtures/user.stub';
 import { factory } from 'test/small.factory';
 import { makeStream, newTestService, ServiceMocks } from 'test/utils';
-import { vitest } from 'vitest';
 
 const stats: AssetStats = {
   [AssetType.IMAGE]: 10,
@@ -44,62 +43,6 @@ describe(AssetService.name, () => {
     mockGetById([assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]);
   });
 
-  describe('getMemoryLane', () => {
-    beforeAll(() => {
-      vitest.useFakeTimers();
-      vitest.setSystemTime(new Date('2024-01-15'));
-    });
-
-    afterAll(() => {
-      vitest.useRealTimers();
-    });
-
-    it('should group the assets correctly', async () => {
-      const image1 = { ...assetStub.image, localDateTime: new Date(2023, 1, 15, 0, 0, 0) };
-      const image2 = { ...assetStub.image, localDateTime: new Date(2023, 1, 15, 1, 0, 0) };
-      const image3 = { ...assetStub.image, localDateTime: new Date(2015, 1, 15) };
-      const image4 = { ...assetStub.image, localDateTime: new Date(2009, 1, 15) };
-
-      mocks.partner.getAll.mockResolvedValue([]);
-      mocks.asset.getByDayOfYear.mockResolvedValue([
-        {
-          year: 2023,
-          assets: [image1, image2],
-        },
-        {
-          year: 2015,
-          assets: [image3],
-        },
-        {
-          year: 2009,
-          assets: [image4],
-        },
-      ] as any);
-
-      await expect(sut.getMemoryLane(authStub.admin, { day: 15, month: 1 })).resolves.toEqual([
-        { yearsAgo: 1, title: '1 year ago', assets: [mapAsset(image1), mapAsset(image2)] },
-        { yearsAgo: 9, title: '9 years ago', assets: [mapAsset(image3)] },
-        { yearsAgo: 15, title: '15 years ago', assets: [mapAsset(image4)] },
-      ]);
-
-      expect(mocks.asset.getByDayOfYear.mock.calls).toEqual([[[authStub.admin.user.id], { day: 15, month: 1 }]]);
-    });
-
-    it('should get memories with partners with inTimeline enabled', async () => {
-      const partner = factory.partner();
-      const auth = factory.auth({ user: { id: partner.sharedWithId } });
-
-      mocks.partner.getAll.mockResolvedValue([partner]);
-      mocks.asset.getByDayOfYear.mockResolvedValue([]);
-
-      await sut.getMemoryLane(auth, { day: 15, month: 1 });
-
-      expect(mocks.asset.getByDayOfYear.mock.calls).toEqual([
-        [[auth.user.id, partner.sharedById], { day: 15, month: 1 }],
-      ]);
-    });
-  });
-
   describe('getStatistics', () => {
     it('should get the statistics for a user, excluding archived assets', async () => {
       mocks.asset.getStatistics.mockResolvedValue(stats);
diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts
index 3e13ed0b8e..bcbe86875b 100644
--- a/server/src/services/asset.service.ts
+++ b/server/src/services/asset.service.ts
@@ -3,13 +3,7 @@ import _ from 'lodash';
 import { DateTime, Duration } from 'luxon';
 import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
 import { OnJob } from 'src/decorators';
-import {
-  AssetResponseDto,
-  MapAsset,
-  MemoryLaneResponseDto,
-  SanitizedAssetResponseDto,
-  mapAsset,
-} from 'src/dtos/asset-response.dto';
+import { AssetResponseDto, MapAsset, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
 import {
   AssetBulkDeleteDto,
   AssetBulkUpdateDto,
@@ -20,7 +14,6 @@ import {
   mapStats,
 } from 'src/dtos/asset.dto';
 import { AuthDto } from 'src/dtos/auth.dto';
-import { MemoryLaneDto } from 'src/dtos/search.dto';
 import { AssetStatus, JobName, JobStatus, Permission, QueueName } from 'src/enum';
 import { BaseService } from 'src/services/base.service';
 import { ISidecarWriteJob, JobItem, JobOf } from 'src/types';
@@ -28,26 +21,6 @@ import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUn
 
 @Injectable()
 export class AssetService extends BaseService {
-  async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
-    const partnerIds = await getMyPartnerIds({
-      userId: auth.user.id,
-      repository: this.partnerRepository,
-      timelineEnabled: true,
-    });
-    const userIds = [auth.user.id, ...partnerIds];
-
-    const groups = await this.assetRepository.getByDayOfYear(userIds, dto);
-    return groups.map(({ year, assets }) => {
-      const yearsAgo = DateTime.utc().year - year;
-      return {
-        yearsAgo,
-        // TODO move this to clients
-        title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} ago`,
-        assets: assets.map((asset) => mapAsset(asset, { auth })),
-      };
-    });
-  }
-
   async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
     const stats = await this.assetRepository.getStatistics(auth.user.id, dto);
     return mapStats(stats);