import type { ActivityConfig } from "./activity.server"; type AdminGraphqlClient = { graphql: (query: string, options?: { variables?: Record }) => Promise; }; type MetaobjectField = { key: string; value: string | null; }; const METAOBJECT_QUERY = `#graphql query DreameActivityConfig($type: String!, $handle: String!) { metaobjectByHandle(handle: { type: $type, handle: $handle }) { fields { key value } } } `; export async function getActivityConfig(admin: AdminGraphqlClient): Promise { const type = process.env.METAOBJECT_TYPE || "$app:dreame_activity"; const handle = process.env.METAOBJECT_HANDLE || "dreame-activity-config"; const response = await admin.graphql(METAOBJECT_QUERY, { variables: { type, handle } }); const payload = (await response.json()) as { data?: { metaobjectByHandle?: { fields?: MetaobjectField[] } | null }; errors?: unknown; }; const fields = payload.data?.metaobjectByHandle?.fields; if (!fields?.length) { throw new Error(`Dreame activity metaobject not found: ${type}/${handle}`); } const values = Object.fromEntries(fields.map((field) => [field.key, field.value])); const buttonText = parseButtonText(values.button_text); const activityId = Number(values.activity_id); if (!Number.isFinite(activityId)) { throw new Error("Metaobject field activity_id is required"); } return { activityId, apiAppId: requireString(values.api_app_id, "api_app_id"), apiSecret: requireString(values.api_secret, "api_secret"), statusApiUrl: requireString(values.status_api_url, "status_api_url"), pushApiUrl: requireString(values.push_api_url, "push_api_url"), statusSuccessCode: parseOptionalNumber(values.status_success_code, "status_success_code"), statusActiveRequired: parseOptionalBoolean(values.status_active_required, "status_active_required"), pushSuccessCode: parseOptionalNumber(values.push_success_code, "push_success_code"), redirectUrl: requireString(values.redirect_url, "redirect_url"), buttonText, shop: values.shop || undefined, }; } function requireString(value: string | null | undefined, key: string): string { if (!value) { throw new Error(`Metaobject field ${key} is required`); } return value; } function parseButtonText(value: string | null | undefined): Record { if (!value) { throw new Error("Metaobject field button_text is required"); } const parsed = JSON.parse(value) as Record; const entries = Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === "string"); if (!entries.length) { throw new Error("Metaobject field button_text must contain at least one locale"); } return Object.fromEntries(entries); } function parseOptionalNumber(value: string | null | undefined, key: string): number | undefined { if (!value) { return undefined; } const parsed = Number(value); if (!Number.isFinite(parsed)) { throw new Error(`Metaobject field ${key} must be a number`); } return parsed; } function parseOptionalBoolean(value: string | null | undefined, key: string): boolean | undefined { if (!value) { return undefined; } const normalized = value.trim().toLowerCase(); if (["true", "1", "yes"].includes(normalized)) { return true; } if (["false", "0", "no"].includes(normalized)) { return false; } throw new Error(`Metaobject field ${key} must be a boolean`); }