Rocky_Mountain_Vending/lib/server/ghl-sync.ts

130 lines
3.6 KiB
TypeScript

type GhlSyncEnv = {
token: string
locationId: string
baseUrl: string
}
function normalizeBaseUrl(value?: string) {
return (value || "https://services.leadconnectorhq.com").replace(/\/+$/, "")
}
export function getGhlSyncEnv(): GhlSyncEnv {
const token = String(
process.env.GHL_PRIVATE_INTEGRATION_TOKEN || process.env.GHL_API_TOKEN || ""
).trim()
const locationId = String(process.env.GHL_LOCATION_ID || "").trim()
const baseUrl = normalizeBaseUrl(process.env.GHL_API_BASE_URL)
if (!token || !locationId) {
throw new Error("GHL token or location ID is not configured.")
}
return { token, locationId, baseUrl }
}
async function fetchGhlJson(pathname: string, init?: RequestInit) {
const env = getGhlSyncEnv()
const response = await fetch(`${env.baseUrl}${pathname}`, {
...init,
headers: {
Authorization: `Bearer ${env.token}`,
Version: process.env.GHL_API_VERSION || "2021-07-28",
Accept: "application/json",
"Content-Type": "application/json",
...(init?.headers || {}),
},
cache: "no-store",
})
const text = await response.text()
let body: any = null
if (text) {
try {
body = JSON.parse(text)
} catch {
body = null
}
}
if (!response.ok) {
throw new Error(
`GHL request failed (${response.status}) for ${pathname}: ${body?.message || text || "Unknown error"}`
)
}
return body
}
export async function fetchGhlContacts(args?: {
limit?: number
cursor?: string
}) {
const env = getGhlSyncEnv()
const searchParams = new URLSearchParams({
locationId: env.locationId,
limit: String(Math.min(100, Math.max(1, args?.limit || 100))),
})
if (args?.cursor) {
searchParams.set("startAfterId", args.cursor)
}
const payload = await fetchGhlJson(`/contacts/?${searchParams.toString()}`)
const contacts = Array.isArray(payload?.contacts)
? payload.contacts
: Array.isArray(payload?.data?.contacts)
? payload.data.contacts
: []
const nextCursor =
contacts.length > 0 ? String(contacts[contacts.length - 1]?.id || "") : ""
return {
items: contacts,
nextCursor: nextCursor || undefined,
}
}
export async function fetchGhlMessages(args?: {
limit?: number
cursor?: string
channel?: "Call" | "SMS"
}) {
const env = getGhlSyncEnv()
const url = new URL(`${env.baseUrl}/conversations/messages/export`)
url.searchParams.set("locationId", env.locationId)
url.searchParams.set("limit", String(Math.min(100, Math.max(1, args?.limit || 100))))
url.searchParams.set("channel", args?.channel || "SMS")
if (args?.cursor) {
url.searchParams.set("cursor", args.cursor)
}
const payload = await fetchGhlJson(url.pathname + url.search)
return {
items: Array.isArray(payload?.messages) ? payload.messages : [],
nextCursor:
typeof payload?.nextCursor === "string" && payload.nextCursor
? payload.nextCursor
: undefined,
}
}
export async function fetchGhlCallLogs(args?: {
page?: number
pageSize?: number
}) {
const env = getGhlSyncEnv()
const url = new URL(`${env.baseUrl}/voice-ai/dashboard/call-logs`)
url.searchParams.set("locationId", env.locationId)
url.searchParams.set("page", String(Math.max(1, args?.page || 1)))
url.searchParams.set(
"pageSize",
String(Math.min(50, Math.max(1, args?.pageSize || 50)))
)
const payload = await fetchGhlJson(url.pathname + url.search)
return {
items: Array.isArray(payload?.callLogs) ? payload.callLogs : [],
page: Number(payload?.page || args?.page || 1),
total: Number(payload?.total || 0),
pageSize: Number(payload?.pageSize || args?.pageSize || 50),
}
}