mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	fix(server): memory lane title (#2772)
* fix(server): memory lane title * feat: parallel requests * pr feedback * fix test --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
		
							
								
								
									
										8
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							| @@ -1163,7 +1163,7 @@ Name | Type | Description  | Notes | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
| 
 | ||||
| # **getMemoryLane** | ||||
| > List<MemoryLaneResponseDto> getMemoryLane(timezone) | ||||
| > List<MemoryLaneResponseDto> getMemoryLane(timestamp) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @@ -1186,10 +1186,10 @@ import 'package:openapi/api.dart'; | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction); | ||||
| 
 | ||||
| final api_instance = AssetApi(); | ||||
| final timezone = timezone_example; // String |  | ||||
| final timestamp = 2013-10-20T19:20:30+01:00; // DateTime | Get pictures for +24 hours from this time going back x years | ||||
| 
 | ||||
| try { | ||||
|     final result = api_instance.getMemoryLane(timezone); | ||||
|     final result = api_instance.getMemoryLane(timestamp); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling AssetApi->getMemoryLane: $e\n'); | ||||
| @@ -1200,7 +1200,7 @@ try { | ||||
| 
 | ||||
| Name | Type | Description  | Notes | ||||
| ------------- | ------------- | ------------- | ------------- | ||||
|  **timezone** | **String**|  |  | ||||
|  **timestamp** | **DateTime**| Get pictures for +24 hours from this time going back x years |  | ||||
| 
 | ||||
| ### Return type | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										14
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							| @@ -1118,8 +1118,9 @@ class AssetApi { | ||||
|   /// Performs an HTTP 'GET /asset/memory-lane' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [String] timezone (required): | ||||
|   Future<Response> getMemoryLaneWithHttpInfo(String timezone,) async { | ||||
|   /// * [DateTime] timestamp (required): | ||||
|   ///   Get pictures for +24 hours from this time going back x years | ||||
|   Future<Response> getMemoryLaneWithHttpInfo(DateTime timestamp,) async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/asset/memory-lane'; | ||||
| 
 | ||||
| @@ -1130,7 +1131,7 @@ class AssetApi { | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
| 
 | ||||
|       queryParams.addAll(_queryParams('', 'timezone', timezone)); | ||||
|       queryParams.addAll(_queryParams('', 'timestamp', timestamp)); | ||||
| 
 | ||||
|     const contentTypes = <String>[]; | ||||
| 
 | ||||
| @@ -1148,9 +1149,10 @@ class AssetApi { | ||||
| 
 | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [String] timezone (required): | ||||
|   Future<List<MemoryLaneResponseDto>?> getMemoryLane(String timezone,) async { | ||||
|     final response = await getMemoryLaneWithHttpInfo(timezone,); | ||||
|   /// * [DateTime] timestamp (required): | ||||
|   ///   Get pictures for +24 hours from this time going back x years | ||||
|   Future<List<MemoryLaneResponseDto>?> getMemoryLane(DateTime timestamp,) async { | ||||
|     final response = await getMemoryLaneWithHttpInfo(timestamp,); | ||||
|     if (response.statusCode >= HttpStatus.badRequest) { | ||||
|       throw ApiException(response.statusCode, await _decodeBodyBytes(response)); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										2
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -129,7 +129,7 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     //Future<List<MemoryLaneResponseDto>> getMemoryLane(String timezone) async | ||||
|     //Future<List<MemoryLaneResponseDto>> getMemoryLane(DateTime timestamp) async | ||||
|     test('test getMemoryLane', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|   | ||||
| @@ -1539,10 +1539,12 @@ | ||||
|         "operationId": "getMemoryLane", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "name": "timezone", | ||||
|             "name": "timestamp", | ||||
|             "required": true, | ||||
|             "in": "query", | ||||
|             "description": "Get pictures for +24 hours from this time going back x years", | ||||
|             "schema": { | ||||
|               "format": "date-time", | ||||
|               "type": "string" | ||||
|             } | ||||
|           } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { assetEntityStub, authStub, newAssetRepositoryMock } from '@test'; | ||||
| import { AssetService, IAssetRepository } from '.'; | ||||
| import { when } from 'jest-when'; | ||||
| import { AssetService, IAssetRepository, mapAsset } from '.'; | ||||
|  | ||||
| describe(AssetService.name, () => { | ||||
|   let sut: AssetService; | ||||
| @@ -38,4 +39,62 @@ describe(AssetService.name, () => { | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('getMemoryLane', () => { | ||||
|     it('should get pictures for each year', async () => { | ||||
|       assetMock.getByDate.mockResolvedValue([]); | ||||
|  | ||||
|       await expect(sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15), years: 10 })).resolves.toEqual( | ||||
|         [], | ||||
|       ); | ||||
|  | ||||
|       expect(assetMock.getByDate).toHaveBeenCalledTimes(10); | ||||
|       expect(assetMock.getByDate.mock.calls).toEqual([ | ||||
|         [authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2020-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2019-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2018-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2017-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2016-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2015-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2014-06-15T00:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2013-06-15T00:00:00.000Z')], | ||||
|       ]); | ||||
|     }); | ||||
|  | ||||
|     it('should keep hours from the date', async () => { | ||||
|       assetMock.getByDate.mockResolvedValue([]); | ||||
|  | ||||
|       await expect( | ||||
|         sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15, 5), years: 2 }), | ||||
|       ).resolves.toEqual([]); | ||||
|  | ||||
|       expect(assetMock.getByDate).toHaveBeenCalledTimes(2); | ||||
|       expect(assetMock.getByDate.mock.calls).toEqual([ | ||||
|         [authStub.admin.id, new Date('2022-06-15T05:00:00.000Z')], | ||||
|         [authStub.admin.id, new Date('2021-06-15T05:00:00.000Z')], | ||||
|       ]); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   it('should set the title correctly', async () => { | ||||
|     when(assetMock.getByDate) | ||||
|       .calledWith(authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')) | ||||
|       .mockResolvedValue([assetEntityStub.image]); | ||||
|     when(assetMock.getByDate) | ||||
|       .calledWith(authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')) | ||||
|       .mockResolvedValue([assetEntityStub.video]); | ||||
|  | ||||
|     await expect(sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15), years: 2 })).resolves.toEqual([ | ||||
|       { title: '1 year since...', assets: [mapAsset(assetEntityStub.image)] }, | ||||
|       { title: '2 years since...', assets: [mapAsset(assetEntityStub.video)] }, | ||||
|     ]); | ||||
|  | ||||
|     expect(assetMock.getByDate).toHaveBeenCalledTimes(2); | ||||
|     expect(assetMock.getByDate.mock.calls).toEqual([ | ||||
|       [authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')], | ||||
|       [authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')], | ||||
|     ]); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| import { Inject } from '@nestjs/common'; | ||||
| import { DateTime } from 'luxon'; | ||||
| import { AuthUserDto } from '../auth'; | ||||
| import { IAssetRepository } from './asset.repository'; | ||||
| import { MemoryLaneDto } from './dto'; | ||||
| import { MapMarkerDto } from './dto/map-marker.dto'; | ||||
| import { MapMarkerResponseDto, mapAsset } from './response-dto'; | ||||
| import { mapAsset, MapMarkerResponseDto } from './response-dto'; | ||||
| import { MemoryLaneResponseDto } from './response-dto/memory-lane-response.dto'; | ||||
| import { DateTime } from 'luxon'; | ||||
|  | ||||
| export class AssetService { | ||||
|   constructor(@Inject(IAssetRepository) private assetRepository: IAssetRepository) {} | ||||
| @@ -13,30 +14,22 @@ export class AssetService { | ||||
|     return this.assetRepository.getMapMarkers(authUser.id, options); | ||||
|   } | ||||
|  | ||||
|   async getMemoryLane(authUser: AuthUserDto, timezone: string): Promise<MemoryLaneResponseDto[]> { | ||||
|     const result: MemoryLaneResponseDto[] = []; | ||||
|   async getMemoryLane(authUser: AuthUserDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> { | ||||
|     const target = DateTime.fromJSDate(dto.timestamp); | ||||
|  | ||||
|     const luxonDate = DateTime.fromISO(new Date().toISOString(), { zone: timezone }); | ||||
|     const today = new Date(luxonDate.year, luxonDate.month - 1, luxonDate.day); | ||||
|     const onRequest = async (yearsAgo: number): Promise<MemoryLaneResponseDto> => { | ||||
|       const assets = await this.assetRepository.getByDate(authUser.id, target.minus({ years: yearsAgo }).toJSDate()); | ||||
|       return { | ||||
|         title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} since...`, | ||||
|         assets: assets.map((a) => mapAsset(a)), | ||||
|       }; | ||||
|     }; | ||||
|  | ||||
|     const years = Array.from({ length: 30 }, (_, i) => { | ||||
|       const year = today.getFullYear() - i - 1; | ||||
|       return new Date(year, today.getMonth(), today.getDate()); | ||||
|     }); | ||||
|  | ||||
|     for (const year of years) { | ||||
|       const assets = await this.assetRepository.getByDate(authUser.id, year); | ||||
|  | ||||
|       if (assets.length > 0) { | ||||
|         const yearGap = today.getFullYear() - year.getFullYear(); | ||||
|         const memory = new MemoryLaneResponseDto(); | ||||
|         memory.title = `${yearGap} year${yearGap > 1 && 's'} since...`; | ||||
|         memory.assets = assets.map((a) => mapAsset(a)); | ||||
|  | ||||
|         result.push(memory); | ||||
|       } | ||||
|     const requests: Promise<MemoryLaneResponseDto>[] = []; | ||||
|     for (let i = 1; i <= dto.years; i++) { | ||||
|       requests.push(onRequest(i)); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
|     return Promise.all(requests).then((results) => results.filter((result) => result.assets.length > 0)); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,2 +1,3 @@ | ||||
| export * from './asset-ids.dto'; | ||||
| export * from './map-marker.dto'; | ||||
| export * from './memory-lane.dto'; | ||||
|   | ||||
							
								
								
									
										14
									
								
								server/src/domain/asset/dto/memory-lane.dto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								server/src/domain/asset/dto/memory-lane.dto.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import { Type } from 'class-transformer'; | ||||
| import { IsDate, IsNumber, IsPositive } from 'class-validator'; | ||||
|  | ||||
| export class MemoryLaneDto { | ||||
|   /** Get pictures for +24 hours from this time going back x years */ | ||||
|   @IsDate() | ||||
|   @Type(() => Date) | ||||
|   timestamp!: Date; | ||||
|  | ||||
|   @IsNumber() | ||||
|   @IsPositive() | ||||
|   @Type(() => Number) | ||||
|   years = 30; | ||||
| } | ||||
| @@ -2,6 +2,5 @@ import { AssetResponseDto } from './asset-response.dto'; | ||||
|  | ||||
| export class MemoryLaneResponseDto { | ||||
|   title!: string; | ||||
|  | ||||
|   assets!: AssetResponseDto[]; | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { AssetService, AuthUserDto, MapMarkerResponseDto } from '@app/domain'; | ||||
| import { AssetService, AuthUserDto, MapMarkerResponseDto, MemoryLaneDto } from '@app/domain'; | ||||
| import { MapMarkerDto } from '@app/domain/asset/dto/map-marker.dto'; | ||||
| import { Controller, Get, Query } from '@nestjs/common'; | ||||
| import { ApiTags } from '@nestjs/swagger'; | ||||
| @@ -20,10 +20,7 @@ export class AssetController { | ||||
|   } | ||||
|  | ||||
|   @Get('memory-lane') | ||||
|   getMemoryLane( | ||||
|     @GetAuthUser() authUser: AuthUserDto, | ||||
|     @Query('timezone') timezone: string, | ||||
|   ): Promise<MemoryLaneResponseDto[]> { | ||||
|     return this.service.getMemoryLane(authUser, timezone); | ||||
|   getMemoryLane(@GetAuthUser() authUser: AuthUserDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> { | ||||
|     return this.service.getMemoryLane(authUser, dto); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import { | ||||
| } from '@app/domain'; | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import { DateTime } from 'luxon'; | ||||
| import { FindOptionsRelations, FindOptionsWhere, In, IsNull, Not, Repository } from 'typeorm'; | ||||
| import { AssetEntity, AssetType } from '../entities'; | ||||
| import OptionalBetween from '../utils/optional-between.util'; | ||||
| @@ -21,7 +22,7 @@ export class AssetRepository implements IAssetRepository { | ||||
|   constructor(@InjectRepository(AssetEntity) private repository: Repository<AssetEntity>) {} | ||||
|  | ||||
|   getByDate(ownerId: string, date: Date): Promise<AssetEntity[]> { | ||||
|     // For reference of a correct approach althought slower | ||||
|     // For reference of a correct approach although slower | ||||
|  | ||||
|     // let builder = this.repository | ||||
|     //   .createQueryBuilder('asset') | ||||
| @@ -36,14 +37,13 @@ export class AssetRepository implements IAssetRepository { | ||||
|     //   .orderBy('asset.fileCreatedAt', 'DESC'); | ||||
|  | ||||
|     // return builder.getMany(); | ||||
|     const tomorrow = new Date(date.getTime() + 24 * 60 * 60 * 1000); | ||||
|  | ||||
|     return this.repository.find({ | ||||
|       where: { | ||||
|         ownerId, | ||||
|         isVisible: true, | ||||
|         isArchived: false, | ||||
|         fileCreatedAt: OptionalBetween(date, tomorrow), | ||||
|         fileCreatedAt: OptionalBetween(date, DateTime.fromJSDate(date).plus({ day: 1 }).toJSDate()), | ||||
|       }, | ||||
|       relations: { | ||||
|         exifInfo: true, | ||||
|   | ||||
							
								
								
									
										32
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										32
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -5523,13 +5523,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} timezone  | ||||
|          * @param {string} timestamp Get pictures for +24 hours from this time going back x years | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getMemoryLane: async (timezone: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             // verify required parameter 'timezone' is not null or undefined
 | ||||
|             assertParamExists('getMemoryLane', 'timezone', timezone) | ||||
|         getMemoryLane: async (timestamp: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             // verify required parameter 'timestamp' is not null or undefined
 | ||||
|             assertParamExists('getMemoryLane', 'timestamp', timestamp) | ||||
|             const localVarPath = `/asset/memory-lane`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
| @@ -5551,8 +5551,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration | ||||
|             // http bearer authentication required
 | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
| 
 | ||||
|             if (timezone !== undefined) { | ||||
|                 localVarQueryParameter['timezone'] = timezone; | ||||
|             if (timestamp !== undefined) { | ||||
|                 localVarQueryParameter['timestamp'] = (timestamp as any instanceof Date) ? | ||||
|                     (timestamp as any).toISOString() : | ||||
|                     timestamp; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
| @@ -6157,12 +6159,12 @@ export const AssetApiFp = function(configuration?: Configuration) { | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} timezone  | ||||
|          * @param {string} timestamp Get pictures for +24 hours from this time going back x years | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async getMemoryLane(timezone: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MemoryLaneResponseDto>>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timezone, options); | ||||
|         async getMemoryLane(timestamp: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MemoryLaneResponseDto>>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
| @@ -6446,12 +6448,12 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} timezone  | ||||
|          * @param {string} timestamp Get pictures for +24 hours from this time going back x years | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getMemoryLane(timezone: string, options?: any): AxiosPromise<Array<MemoryLaneResponseDto>> { | ||||
|             return localVarFp.getMemoryLane(timezone, options).then((request) => request(axios, basePath)); | ||||
|         getMemoryLane(timestamp: string, options?: any): AxiosPromise<Array<MemoryLaneResponseDto>> { | ||||
|             return localVarFp.getMemoryLane(timestamp, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          * Get all asset of a device that are in the database, ID only. | ||||
| @@ -6857,11 +6859,11 @@ export interface AssetApiGetMapMarkersRequest { | ||||
|  */ | ||||
| export interface AssetApiGetMemoryLaneRequest { | ||||
|     /** | ||||
|      *  | ||||
|      * Get pictures for +24 hours from this time going back x years | ||||
|      * @type {string} | ||||
|      * @memberof AssetApiGetMemoryLane | ||||
|      */ | ||||
|     readonly timezone: string | ||||
|     readonly timestamp: string | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @@ -7304,7 +7306,7 @@ export class AssetApi extends BaseAPI { | ||||
|      * @memberof AssetApi | ||||
|      */ | ||||
|     public getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig) { | ||||
|         return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timezone, options).then((request) => request(this.axios, this.basePath)); | ||||
|         return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|   | ||||
| @@ -35,8 +35,9 @@ | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		if (!$memoryStore) { | ||||
| 			const timezone = DateTime.local().zoneName; | ||||
| 			const { data } = await api.assetApi.getMemoryLane({ timezone }); | ||||
| 			const { data } = await api.assetApi.getMemoryLane({ | ||||
| 				timestamp: DateTime.local().startOf('day').toISO() | ||||
| 			}); | ||||
| 			$memoryStore = data; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -11,8 +11,9 @@ | ||||
| 	$: shouldRender = memoryLane.length > 0; | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		const timezone = DateTime.local().zoneName; | ||||
| 		const { data } = await api.assetApi.getMemoryLane({ timezone }); | ||||
| 		const { data } = await api.assetApi.getMemoryLane({ | ||||
| 			timestamp: DateTime.local().startOf('day').toISO() | ||||
| 		}); | ||||
|  | ||||
| 		memoryLane = data; | ||||
| 		$memoryStore = data; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user