bundle-offers-app/app/lib/shop.server.ts
metatroncubeswdev 365e45406a Bundle Offers Shopify app: discount function, admin, storefront, deploy stack
Rust discount function (one automatic discount evaluating all offers in priority
order, parity with the Odoo engine), React Router admin with Polaris, theme app
extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and
a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:35:58 -04:00

47 lines
2.0 KiB
TypeScript

import db from "../db.server";
import { planCodeFromSubscription } from "./plans";
type GraphQL = (query: string, options?: { variables?: Record<string, unknown> }) => Promise<Response>;
export interface AdminLike { graphql: GraphQL }
export async function gql<T = any>(admin: AdminLike, query: string, variables?: Record<string, unknown>): Promise<T> {
const res = await admin.graphql(query, variables ? { variables } : undefined);
const json = (await res.json()) as { data?: T; errors?: unknown };
if (json.errors) throw new Error(`GraphQL error: ${JSON.stringify(json.errors)}`);
return json.data as T;
}
const SHOP_QUERY = `#graphql
query BundleOffersShop {
shop { currencyCode ianaTimezone currencyFormats { moneyFormat } }
currentAppInstallation { id activeSubscriptions { name status test } }
}`;
/** Creates/refreshes the Shop row from the Admin API (currency, timezone, plan). */
export async function syncShop(admin: AdminLike, domain: string) {
const data = await gql(admin, SHOP_QUERY);
const subs = (data.currentAppInstallation?.activeSubscriptions || []) as { name: string; status: string }[];
const active = subs.find((s) => s.status === "ACTIVE");
const planCode = planCodeFromSubscription(active?.name);
const values = {
currency: data.shop.currencyCode,
timezone: data.shop.ianaTimezone || "UTC",
moneyFormat: data.shop.currencyFormats?.moneyFormat || "${{amount}}",
appInstallationId: data.currentAppInstallation?.id,
planCode,
uninstalledAt: null,
};
return db.shop.upsert({ where: { domain }, create: { domain, ...values }, update: values });
}
export async function getShop(domain: string) {
return db.shop.findUnique({ where: { domain } });
}
/** Shop row for an admin request; syncs from Shopify the first time (or when forced). */
export async function ensureShop(admin: AdminLike, domain: string, force = false) {
const shop = await getShop(domain);
if (shop && !force && shop.appInstallationId && !shop.uninstalledAt) return shop;
return syncShop(admin, domain);
}