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 || "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"), 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); }