Files
backend/src/website/website-static-image-catalog.ts
T
Alireza HassaniandCursor 5e5bfc4aa3 Sync static image slots from the live website catalog.
Refresh now imports keys from the storefront and deletes unused slots plus their files.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 09:55:03 +03:30

100 lines
3.1 KiB
TypeScript

import { SPECIAL_GROUP_KEY_PATTERN } from '../common/special-group-key';
export const WEBSITE_STATIC_IMAGE_CATALOG_PATH = '/meshkee/static-image-slots';
export type WebsiteStaticImageCatalogSlot = {
key: string;
label: string;
kind: 'single' | 'list';
pageKey: string;
aspectRatio: string | null;
recommendedWidth: number | null;
itemCount: number | null;
};
const ASPECT_RATIO_PATTERN = /^[0-9]{1,3}:[0-9]{1,3}$/;
function asRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: null;
}
function readString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function readOptionalInt(value: unknown, min: number, max: number): number | null {
if (value == null || value === '') return null;
const parsed = typeof value === 'number' ? value : Number(value);
if (!Number.isInteger(parsed) || parsed < min || parsed > max) return null;
return parsed;
}
export function parseWebsiteStaticImageCatalog(
payload: unknown,
): WebsiteStaticImageCatalogSlot[] {
const root = asRecord(payload);
const rawSlots = Array.isArray(root?.slots) ? root.slots : Array.isArray(payload) ? payload : null;
if (!rawSlots) {
throw new Error('Slot catalog must be { slots: [...] }');
}
const slots: WebsiteStaticImageCatalogSlot[] = [];
const seen = new Set<string>();
for (const entry of rawSlots) {
const row = asRecord(entry);
if (!row) continue;
const key = readString(row.key);
if (!key || !SPECIAL_GROUP_KEY_PATTERN.test(key) || seen.has(key)) continue;
const kind = readString(row.kind) === 'single' ? 'single' : 'list';
const label = readString(row.label) || key;
const pageKey = readString(row.pageKey) || 'home';
if (!SPECIAL_GROUP_KEY_PATTERN.test(pageKey)) continue;
const aspectRatioRaw = readString(row.aspectRatio);
const aspectRatio =
aspectRatioRaw && ASPECT_RATIO_PATTERN.test(aspectRatioRaw) ? aspectRatioRaw : null;
const itemCount =
kind === 'single' ? 1 : readOptionalInt(row.itemCount, 1, 24);
seen.add(key);
slots.push({
key,
label: label.slice(0, 255),
kind,
pageKey,
aspectRatio,
recommendedWidth: readOptionalInt(row.recommendedWidth, 1, 8000),
itemCount,
});
}
if (slots.length === 0) {
throw new Error('Slot catalog did not contain any valid slots');
}
return slots;
}
export function websiteCatalogUrls(hosts: string[]): string[] {
const uniqueHosts = [...new Set(hosts.map((host) => host.toLowerCase().trim()).filter(Boolean))];
const urls: string[] = [];
for (const host of uniqueHosts) {
const bare = host.replace(/^www\./, '');
const variants = [bare, `www.${bare}`];
for (const candidate of variants) {
urls.push(`https://${candidate}${WEBSITE_STATIC_IMAGE_CATALOG_PATH}`);
urls.push(`http://${candidate}${WEBSITE_STATIC_IMAGE_CATALOG_PATH}`);
urls.push(`https://${candidate}${WEBSITE_STATIC_IMAGE_CATALOG_PATH}.json`);
}
}
return [...new Set(urls)];
}