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>
This commit is contained in:
co-authored by
Cursor
parent
1c7678ce67
commit
5e5bfc4aa3
@@ -0,0 +1,99 @@
|
||||
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)];
|
||||
}
|
||||
Reference in New Issue
Block a user