fix(server): accept showAt and hideAt for creating memories (#26429)

* fix(server): accept showAt and hideAt for creating memories

* fix history
This commit is contained in:
Mees Frensel
2026-02-23 22:26:34 +01:00
committed by GitHub
parent 9ea0a69a72
commit bf47147fbb
7 changed files with 106 additions and 3 deletions

View File

@@ -15,9 +15,11 @@ class MemoryCreateDto {
MemoryCreateDto({
this.assetIds = const [],
required this.data,
this.hideAt,
this.isSaved,
required this.memoryAt,
this.seenAt,
this.showAt,
required this.type,
});
@@ -26,6 +28,15 @@ class MemoryCreateDto {
OnThisDayDto data;
/// Date when memory should be hidden
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? hideAt;
/// Is memory saved
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -47,6 +58,15 @@ class MemoryCreateDto {
///
DateTime? seenAt;
/// Date when memory should be shown
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? showAt;
/// Memory type
MemoryType type;
@@ -54,9 +74,11 @@ class MemoryCreateDto {
bool operator ==(Object other) => identical(this, other) || other is MemoryCreateDto &&
_deepEquality.equals(other.assetIds, assetIds) &&
other.data == data &&
other.hideAt == hideAt &&
other.isSaved == isSaved &&
other.memoryAt == memoryAt &&
other.seenAt == seenAt &&
other.showAt == showAt &&
other.type == type;
@override
@@ -64,18 +86,25 @@ class MemoryCreateDto {
// ignore: unnecessary_parenthesis
(assetIds.hashCode) +
(data.hashCode) +
(hideAt == null ? 0 : hideAt!.hashCode) +
(isSaved == null ? 0 : isSaved!.hashCode) +
(memoryAt.hashCode) +
(seenAt == null ? 0 : seenAt!.hashCode) +
(showAt == null ? 0 : showAt!.hashCode) +
(type.hashCode);
@override
String toString() => 'MemoryCreateDto[assetIds=$assetIds, data=$data, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, type=$type]';
String toString() => 'MemoryCreateDto[assetIds=$assetIds, data=$data, hideAt=$hideAt, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, showAt=$showAt, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetIds'] = this.assetIds;
json[r'data'] = this.data;
if (this.hideAt != null) {
json[r'hideAt'] = this.hideAt!.toUtc().toIso8601String();
} else {
// json[r'hideAt'] = null;
}
if (this.isSaved != null) {
json[r'isSaved'] = this.isSaved;
} else {
@@ -86,6 +115,11 @@ class MemoryCreateDto {
json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String();
} else {
// json[r'seenAt'] = null;
}
if (this.showAt != null) {
json[r'showAt'] = this.showAt!.toUtc().toIso8601String();
} else {
// json[r'showAt'] = null;
}
json[r'type'] = this.type;
return json;
@@ -104,9 +138,11 @@ class MemoryCreateDto {
? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
data: OnThisDayDto.fromJson(json[r'data'])!,
hideAt: mapDateTime(json, r'hideAt', r''),
isSaved: mapValueOfType<bool>(json, r'isSaved'),
memoryAt: mapDateTime(json, r'memoryAt', r'')!,
seenAt: mapDateTime(json, r'seenAt', r''),
showAt: mapDateTime(json, r'showAt', r''),
type: MemoryType.fromJson(json[r'type'])!,
);
}

View File

@@ -18651,6 +18651,22 @@
"data": {
"$ref": "#/components/schemas/OnThisDayDto"
},
"hideAt": {
"description": "Date when memory should be hidden",
"format": "date-time",
"type": "string",
"x-immich-history": [
{
"version": "v2.6.0",
"state": "Added"
},
{
"version": "v2.6.0",
"state": "Stable"
}
],
"x-immich-state": "Stable"
},
"isSaved": {
"description": "Is memory saved",
"type": "boolean"
@@ -18665,6 +18681,22 @@
"format": "date-time",
"type": "string"
},
"showAt": {
"description": "Date when memory should be shown",
"format": "date-time",
"type": "string",
"x-immich-history": [
{
"version": "v2.6.0",
"state": "Added"
},
{
"version": "v2.6.0",
"state": "Stable"
}
],
"x-immich-state": "Stable"
},
"type": {
"allOf": [
{

View File

@@ -1404,12 +1404,16 @@ export type MemoryCreateDto = {
/** Asset IDs to associate with memory */
assetIds?: string[];
data: OnThisDayDto;
/** Date when memory should be hidden */
hideAt?: string;
/** Is memory saved */
isSaved?: boolean;
/** Memory date */
memoryAt: string;
/** Date when memory was seen */
seenAt?: string;
/** Date when memory should be shown */
showAt?: string;
/** Memory type */
"type": MemoryType;
};

View File

@@ -51,6 +51,20 @@ describe(MemoryController.name, () => {
errorDto.badRequest(['data.year must be a positive number', 'data.year must be an integer number']),
);
});
it('should accept showAt and hideAt', async () => {
const { status } = await request(ctx.getHttpServer())
.post('/memories')
.send({
type: 'on_this_day',
data: { year: 2020 },
memoryAt: new Date(2021).toISOString(),
showAt: new Date(2022).toISOString(),
hideAt: new Date(2023).toISOString(),
});
expect(status).toBe(201);
});
});
describe('GET /memories/statistics', () => {

View File

@@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator';
import { Memory } from 'src/database';
import { HistoryBuilder } from 'src/decorators';
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { AssetOrderWithRandom, MemoryType } from 'src/enum';
@@ -77,6 +78,20 @@ export class MemoryCreateDto extends MemoryBaseDto {
@ValidateDate({ description: 'Memory date' })
memoryAt!: Date;
@ValidateDate({
optional: true,
description: 'Date when memory should be shown',
history: new HistoryBuilder().added('v2.6.0').stable('v2.6.0'),
})
showAt?: Date;
@ValidateDate({
optional: true,
description: 'Date when memory should be hidden',
history: new HistoryBuilder().added('v2.6.0').stable('v2.6.0'),
})
hideAt?: Date;
@ValidateUUID({ optional: true, each: true, description: 'Asset IDs to associate with memory' })
assetIds?: string[];
}

View File

@@ -100,6 +100,8 @@ export class MemoryService extends BaseService {
data: dto.data,
isSaved: dto.isSaved,
memoryAt: dto.memoryAt,
showAt: dto.showAt,
hideAt: dto.hideAt,
seenAt: dto.seenAt,
},
allowedAssetIds,

View File

@@ -233,7 +233,7 @@ export const ValidateHexColor = () => {
};
type DateOptions = OptionalOptions & { optional?: boolean; format?: 'date' | 'date-time' };
export const ValidateDate = (options?: DateOptions & ApiPropertyOptions) => {
export const ValidateDate = (options?: DateOptions & PropertyOptions) => {
const {
optional,
nullable = false,
@@ -243,7 +243,7 @@ export const ValidateDate = (options?: DateOptions & ApiPropertyOptions) => {
} = options || {};
return applyDecorators(
ApiProperty({ format, ...apiPropertyOptions }),
Property({ format, ...apiPropertyOptions }),
IsDate(),
optional ? Optional({ nullable, emptyToNull }) : IsNotEmpty(),
Transform(({ key, value }) => {