#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); const RESOURCES = [ ["venues", true, true], ["branches", true, true], ["dining-areas", false, false], ["tables", false, false], ["menus", true, true], ["menu-categories", false, false], ["menu-items", false, false], ["allergens", false, false], ["modifier-groups", false, false], ["modifier-options", false, false], ["availability-windows", false, false], ["menu-media-refs", false, false], ["qr-codes", false, false], ["qr-menu-sessions", false, false], ["carts", false, false], ["cart-lines", false, false], ["pos-registers", false, false], ["pos-ticket-lines", false, false], ["pos-payments", false, false], ["pos-discounts", false, false], ["pos-tax-lines", false, false], ["pos-floor-plans", false, false], ["pos-stations", false, false], ["kitchen-stations", false, false], ["connector-registrations", false, false], ["connector-dispatches", false, false], ["analytics-reports", false, false], ["bundle-definitions", false, false], ["roles", false, false], ["permissions", false, false], ["configurations", true, false], ["events", false, false], ]; function kebabToProp(s) { return s.split("-").map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1))).join(""); } let apiBlocks = ""; for (const [p, hasUpdate, hasDelete] of RESOURCES) { const prop = kebabToProp(p); let block = ` ${prop}: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/${p}\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/${p}/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => request>(\`/api/v1/${p}\`, { tenantId, method: "POST", body: JSON.stringify(body), }),`; if (hasUpdate) { block += ` update: (tenantId: string, id: string, body: Record) => request>(\`/api/v1/${p}/\${id}\`, { tenantId, method: "PATCH", body: JSON.stringify(body), }),`; } if (hasDelete) { block += ` remove: (tenantId: string, id: string) => request>(\`/api/v1/${p}/\${id}/delete\`, { tenantId, method: "POST" }),`; } block += ` },`; apiBlocks += block + "\n"; } const header = `/** * Hospitality Service API client — Torbat Food (real backend only). * Browser → /api/hospitality BFF; SSR → direct upstream :8009 */ import { getValidAccessToken, refreshSession } from "@/lib/auth"; import type { CapabilitiesResponse, HealthResponse, ListParams } from "@/modules/hospitality/types"; const HOSPITALITY_BASE = typeof window !== "undefined" ? "/api/hospitality" : process.env.HOSPITALITY_SERVICE_URL || process.env.NEXT_PUBLIC_HOSPITALITY_API_URL || "http://localhost:8009"; export class HospitalityApiError extends Error { constructor( public status: number, public code: string, message: string ) { super(message); this.name = "HospitalityApiError"; } } type RequestOptions = RequestInit & { tenantId: string; auth?: boolean }; async function request(path: string, options: RequestOptions): Promise { const { tenantId, auth = true, headers: customHeaders, ...init } = options; const headers = new Headers(customHeaders); if (!headers.has("Content-Type") && init.body) { headers.set("Content-Type", "application/json"); } headers.set("X-Tenant-ID", tenantId); if (auth) { const token = await getValidAccessToken(); if (token) headers.set("Authorization", \`Bearer \${token}\`); } const url = path.startsWith("http") ? path : \`\${HOSPITALITY_BASE.replace(/\\/$/, "")}\${path.startsWith("/") ? path : \`/\${path}\`}\`; let res: Response; try { res = await fetch(url, { ...init, headers }); } catch (err) { const detail = err instanceof Error ? err.message : "network_error"; throw new Error(\`اتصال به سرویس تربت فود برقرار نشد (\${url}) — \${detail}\`); } if (res.status === 401 && auth) { const refreshed = await refreshSession({ force: true }); if (refreshed) { headers.set("Authorization", \`Bearer \${refreshed}\`); res = await fetch(url, { ...init, headers }); } } if (!res.ok) { const body = await res.json().catch(() => ({})); throw new HospitalityApiError( res.status, body?.error?.code || body?.detail || "unknown_error", body?.error?.message || body?.detail || res.statusText ); } if (res.status === 204) return undefined as T; return res.json() as Promise; } function qs(params?: Record) { if (!params) return ""; const sp = new URLSearchParams(); Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== "") sp.set(k, String(v)); }); const s = sp.toString(); return s ? \`?\${s}\` : ""; } function action(tenantId: string, apiPath: string, body?: Record) { return request>(apiPath, { tenantId, method: "POST", body: body ? JSON.stringify(body) : undefined, }); } export const hospitalityApi = { health: (): Promise => fetch(\`\${HOSPITALITY_BASE}/health\`).then((r) => r.json()), capabilities: (): Promise => fetch(\`\${HOSPITALITY_BASE}/capabilities\`).then((r) => r.json()), ${apiBlocks} tenantBundles: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/tenant-bundles\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/tenant-bundles/\${id}\`, { tenantId }), activate: (tenantId: string, body: Record) => action(tenantId, "/api/v1/tenant-bundles/activate", body), deactivate: (tenantId: string, id: string) => action(tenantId, \`/api/v1/tenant-bundles/\${id}/deactivate\`), }, featureToggles: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/feature-toggles\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/feature-toggles/\${id}\`, { tenantId }), upsert: (tenantId: string, body: Record) => action(tenantId, "/api/v1/feature-toggles/upsert", body), }, menuItemModifiers: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/menu-item-modifiers\${qs(params)}\`, { tenantId }), link: (tenantId: string, body: Record) => action(tenantId, "/api/v1/menu-item-modifiers/link", body), }, menuItemAllergens: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/menu-item-allergens\${qs(params)}\`, { tenantId }), link: (tenantId: string, body: Record) => action(tenantId, "/api/v1/menu-item-allergens/link", body), }, menuLocalizations: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/menu-localizations\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/menu-localizations/\${id}\`, { tenantId }), upsert: (tenantId: string, body: Record) => action(tenantId, "/api/v1/menu-localizations/upsert", body), }, settings: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/settings\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/settings/\${id}\`, { tenantId }), upsert: (tenantId: string, body: Record) => action(tenantId, "/api/v1/settings/upsert", body), }, reservations: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/reservations\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/reservations/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/reservations", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, \`/api/v1/reservations/\${id}/status\`, body), }, tableAssignments: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/table-assignments\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/table-assignments/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/table-assignments", body), release: (tenantId: string, id: string) => action(tenantId, \`/api/v1/table-assignments/\${id}/release\`), }, serviceRequests: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/service-requests\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/service-requests/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/service-requests", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, \`/api/v1/service-requests/\${id}/status\`, body), }, waitlist: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/waitlist\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/waitlist/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/waitlist", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, \`/api/v1/waitlist/\${id}/status\`, body), }, posShifts: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/pos-shifts\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/pos-shifts/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/pos-shifts", body), close: (tenantId: string, id: string) => action(tenantId, \`/api/v1/pos-shifts/\${id}/close\`), }, posTickets: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/pos-tickets\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/pos-tickets/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/pos-tickets", body), void: (tenantId: string, id: string) => action(tenantId, \`/api/v1/pos-tickets/\${id}/void\`), }, qrOrderingSessions: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/qr-ordering-sessions\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/qr-ordering-sessions/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/qr-ordering-sessions", body), submit: (tenantId: string, id: string) => action(tenantId, \`/api/v1/qr-ordering-sessions/\${id}/submit\`), }, kitchenTickets: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/kitchen-tickets\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/kitchen-tickets/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/kitchen-tickets", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, \`/api/v1/kitchen-tickets/\${id}/status\`, body), }, kitchenTicketItems: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/kitchen-ticket-items\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/kitchen-ticket-items/\${id}\`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/kitchen-ticket-items", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, \`/api/v1/kitchen-ticket-items/\${id}/status\`, body), }, analyticsSnapshots: { list: (tenantId: string, params?: ListParams) => request[]>(\`/api/v1/analytics-snapshots\${qs(params)}\`, { tenantId }), get: (tenantId: string, id: string) => request>(\`/api/v1/analytics-snapshots/\${id}\`, { tenantId }), refresh: (tenantId: string, body: Record) => action(tenantId, "/api/v1/analytics-snapshots/refresh", body), }, }; `; fs.writeFileSync(path.join(ROOT, "modules/hospitality/services/hospitality-api.ts"), header); console.log("Generated hospitality-api.ts");