Add website static image slots with page grouping and bilingual captions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-16 23:56:16 +03:30
co-authored by Cursor
parent b991a60cbc
commit 1c7678ce67
18 changed files with 1098 additions and 6 deletions
@@ -0,0 +1,222 @@
import { Transform, Type } from 'class-transformer';
import {
IsArray,
IsIn,
IsInt,
IsOptional,
IsString,
Matches,
Max,
MaxLength,
Min,
MinLength,
ValidateNested,
} from 'class-validator';
import {
SPECIAL_GROUP_KEY_MESSAGE,
SPECIAL_GROUP_KEY_PATTERN,
} from '../../common/special-group-key';
const emptyToUndefined = ({ value }: { value: unknown }) => {
if (value == null) return undefined;
if (typeof value !== 'string') return value;
const trimmed = value.trim();
return trimmed === '' ? undefined : trimmed;
};
export class WebsiteStaticImageItemInputDto {
@IsString()
imageMediaId!: string;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MaxLength(255)
title?: string;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MaxLength(255)
titleFa?: string;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MaxLength(255)
titleEn?: string;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MaxLength(500)
subtext?: string;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MaxLength(2048)
@Matches(/^(https?:\/\/[^\s]+|\/[^\s]*)$/i, {
message: 'linkUrl must be an absolute or site-relative URL',
})
linkUrl?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
sortOrder?: number;
}
export class CreateWebsiteStaticImageSlotDto {
@IsString()
@MinLength(1)
@MaxLength(100)
@Matches(SPECIAL_GROUP_KEY_PATTERN, { message: SPECIAL_GROUP_KEY_MESSAGE })
key!: string;
@IsString()
@MinLength(1)
@MaxLength(255)
label!: string;
@IsIn(['single', 'list'])
kind!: 'single' | 'list';
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@Matches(/^[0-9]{1,3}:[0-9]{1,3}$/, {
message: 'aspectRatio must look like 16:9',
})
aspectRatio?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(8000)
recommendedWidth?: number;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(24)
itemCount?: number;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MinLength(1)
@MaxLength(100)
@Matches(SPECIAL_GROUP_KEY_PATTERN, { message: SPECIAL_GROUP_KEY_MESSAGE })
pageKey?: string;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => WebsiteStaticImageItemInputDto)
items?: WebsiteStaticImageItemInputDto[];
@IsOptional()
@Type(() => Number)
@IsInt()
sortOrder?: number;
}
export class UpdateWebsiteStaticImageSlotDto {
@IsOptional()
@IsString()
@MinLength(1)
@MaxLength(100)
@Matches(SPECIAL_GROUP_KEY_PATTERN, { message: SPECIAL_GROUP_KEY_MESSAGE })
key?: string;
@IsOptional()
@IsString()
@MinLength(1)
@MaxLength(255)
label?: string;
@IsOptional()
@IsIn(['single', 'list'])
kind?: 'single' | 'list';
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@Matches(/^[0-9]{1,3}:[0-9]{1,3}$/, {
message: 'aspectRatio must look like 16:9',
})
aspectRatio?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(8000)
recommendedWidth?: number | null;
@IsOptional()
@Transform(({ value }) => {
if (value === undefined) return undefined
if (value === null || value === '') return null
return Number(value)
})
@IsInt()
@Min(1)
@Max(24)
itemCount?: number | null;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MinLength(1)
@MaxLength(100)
@Matches(SPECIAL_GROUP_KEY_PATTERN, { message: SPECIAL_GROUP_KEY_MESSAGE })
pageKey?: string;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => WebsiteStaticImageItemInputDto)
items?: WebsiteStaticImageItemInputDto[];
@IsOptional()
@Type(() => Number)
@IsInt()
sortOrder?: number;
}
export class ListWebsiteStaticImageSlotsDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
pageSize?: number;
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MinLength(1)
@MaxLength(100)
@Matches(SPECIAL_GROUP_KEY_PATTERN, { message: SPECIAL_GROUP_KEY_MESSAGE })
pageKey?: string;
}
export class PublicWebsiteStaticImagesQueryDto {
@IsOptional()
@Transform(emptyToUndefined)
@IsString()
@MinLength(1)
@MaxLength(100)
@Matches(SPECIAL_GROUP_KEY_PATTERN, { message: SPECIAL_GROUP_KEY_MESSAGE })
pageKey?: string;
}