dreame-shopify-app/app/services/config.server.ts

78 lines
2.5 KiB
TypeScript

import type { ActivityConfig } from "./activity.server";
type AdminGraphqlClient = {
graphql: (query: string, options?: { variables?: Record<string, unknown> }) => Promise<Response>;
};
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<ActivityConfig> {
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<string, string> {
if (!value) {
throw new Error("Metaobject field button_text is required");
}
const parsed = JSON.parse(value) as Record<string, unknown>;
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);
}