whale-code 6.5.7 → 6.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/dist/cli/services/agent-loop.js +26 -2
- package/dist/cli/services/agent-loop.js.map +1 -1
- package/dist/cli/services/hooks.js +2 -1
- package/dist/cli/services/hooks.js.map +1 -1
- package/dist/cli/services/telemetry-spans.js +1 -0
- package/dist/cli/services/telemetry-spans.js.map +1 -1
- package/dist/cli/services/telemetry.d.ts +23 -0
- package/dist/cli/services/telemetry.js +45 -1
- package/dist/cli/services/telemetry.js.map +1 -1
- package/dist/server/handlers/__test-utils__/test-db.d.ts +17 -3
- package/dist/server/handlers/__test-utils__/test-db.js +113 -14
- package/dist/server/handlers/__test-utils__/test-db.js.map +1 -1
- package/dist/server/handlers/affiliates.d.ts +9 -0
- package/dist/server/handlers/affiliates.js +197 -0
- package/dist/server/handlers/affiliates.js.map +1 -0
- package/dist/server/handlers/api-docs.d.ts +4 -2
- package/dist/server/handlers/api-docs.js +204 -1681
- package/dist/server/handlers/api-docs.js.map +1 -1
- package/dist/server/handlers/campaigns.d.ts +9 -0
- package/dist/server/handlers/campaigns.js +237 -0
- package/dist/server/handlers/campaigns.js.map +1 -0
- package/dist/server/handlers/catalog-schemas.js +9 -9
- package/dist/server/handlers/catalog-schemas.js.map +1 -1
- package/dist/server/handlers/catalog.js +1 -1
- package/dist/server/handlers/catalog.js.map +1 -1
- package/dist/server/handlers/comms-documents.js +28 -2
- package/dist/server/handlers/comms-documents.js.map +1 -1
- package/dist/server/handlers/comms-pdf-generation.js +25 -3
- package/dist/server/handlers/comms-pdf-generation.js.map +1 -1
- package/dist/server/handlers/comms-pdf-helpers.js +4 -4
- package/dist/server/handlers/comms-pdf-helpers.js.map +1 -1
- package/dist/server/handlers/comms.d.ts +100 -0
- package/dist/server/handlers/comms.js +146 -12
- package/dist/server/handlers/comms.js.map +1 -1
- package/dist/server/handlers/coupons.d.ts +9 -0
- package/dist/server/handlers/coupons.js +220 -0
- package/dist/server/handlers/coupons.js.map +1 -0
- package/dist/server/handlers/embeddings.js +1 -1
- package/dist/server/handlers/embeddings.js.map +1 -1
- package/dist/server/handlers/enrichment.js +2 -622
- package/dist/server/handlers/enrichment.js.map +1 -1
- package/dist/server/handlers/fulfillment.d.ts +9 -0
- package/dist/server/handlers/fulfillment.js +209 -0
- package/dist/server/handlers/fulfillment.js.map +1 -0
- package/dist/server/handlers/google-ads.d.ts +24 -0
- package/dist/server/handlers/google-ads.js +2199 -0
- package/dist/server/handlers/google-ads.js.map +1 -0
- package/dist/server/handlers/invoices.d.ts +9 -0
- package/dist/server/handlers/invoices.js +252 -0
- package/dist/server/handlers/invoices.js.map +1 -0
- package/dist/server/handlers/loyalty.d.ts +9 -0
- package/dist/server/handlers/loyalty.js +197 -0
- package/dist/server/handlers/loyalty.js.map +1 -0
- package/dist/server/handlers/meta-ads-graph-api.js +18 -3
- package/dist/server/handlers/meta-ads-graph-api.js.map +1 -1
- package/dist/server/handlers/phone.d.ts +9 -0
- package/dist/server/handlers/phone.js +197 -0
- package/dist/server/handlers/phone.js.map +1 -0
- package/dist/server/handlers/pipeline.d.ts +9 -0
- package/dist/server/handlers/pipeline.js +277 -0
- package/dist/server/handlers/pipeline.js.map +1 -0
- package/dist/server/handlers/qr-codes.d.ts +9 -0
- package/dist/server/handlers/qr-codes.js +198 -0
- package/dist/server/handlers/qr-codes.js.map +1 -0
- package/dist/server/handlers/reviews.d.ts +9 -0
- package/dist/server/handlers/reviews.js +171 -0
- package/dist/server/handlers/reviews.js.map +1 -0
- package/dist/server/handlers/segments.d.ts +9 -0
- package/dist/server/handlers/segments.js +229 -0
- package/dist/server/handlers/segments.js.map +1 -0
- package/dist/server/handlers/social.d.ts +9 -0
- package/dist/server/handlers/social.js +81 -0
- package/dist/server/handlers/social.js.map +1 -0
- package/dist/server/handlers/tax.d.ts +9 -0
- package/dist/server/handlers/tax.js +182 -0
- package/dist/server/handlers/tax.js.map +1 -0
- package/dist/server/handlers/wallet.d.ts +9 -0
- package/dist/server/handlers/wallet.js +203 -0
- package/dist/server/handlers/wallet.js.map +1 -0
- package/dist/server/handlers/webhooks-mgmt.d.ts +9 -0
- package/dist/server/handlers/webhooks-mgmt.js +181 -0
- package/dist/server/handlers/webhooks-mgmt.js.map +1 -0
- package/dist/server/handlers/wholesale.d.ts +9 -0
- package/dist/server/handlers/wholesale.js +219 -0
- package/dist/server/handlers/wholesale.js.map +1 -0
- package/dist/server/index.js +20 -9
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/clickhouse-buffer.js +1 -0
- package/dist/server/lib/clickhouse-buffer.js.map +1 -1
- package/dist/server/lib/coa-renderer.d.ts +1 -1
- package/dist/server/lib/coa-renderer.js +32 -10
- package/dist/server/lib/coa-renderer.js.map +1 -1
- package/dist/server/server-worker.d.ts +1 -0
- package/dist/server/server-worker.js +464 -3
- package/dist/server/server-worker.js.map +1 -1
- package/dist/server/tool-router.js +118 -4
- package/dist/server/tool-router.js.map +1 -1
- package/package.json +28 -4
- package/vendor/ink/package.json +0 -2
- package/whale-logo.png +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoices.js","names":["sanitizeFilterValue","handleInvoices","sb","args","storeId","sid","action","q","from","select","eq","order","ascending","status","payment_status","customer_id","query","sq","String","or","limit","data","error","success","message","count","length","invoiceId","invoice_id","single","customerEmail","customer_email","maxInv","nextNum","invoice_number","parsed","parseInt","replace","isNaN","invoiceNumber","padStart","lineItems","line_items","subtotal","reduce","s","li","quantity","unit_price","taxAmount","tax_amount","discountAmount","discount_amount","totalAmount","total_amount","Math","round","record","store_id","amount_paid","undefined","order_id","customer_name","customer_phone","description","due_date","notes","internal_notes","insert","updates","updated_at","Date","toISOString","update","sent_at","amount","inv","invErr","newAmountPaid","Number","newPaymentStatus","newStatus","amount_due","paid_at","payment_method","transaction_id"],"sources":["../../../src/server/handlers/invoices.ts"],"sourcesContent":["// server/handlers/invoices.ts — Invoice creation and management\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport { sanitizeFilterValue } from \"../lib/utils.js\";\n\ntype Result = { success: boolean; data?: unknown; error?: string; [key: string]: unknown };\n\nexport async function handleInvoices(\n sb: SupabaseClient,\n args: Record<string, unknown>,\n storeId?: string,\n): Promise<Result> {\n const sid = storeId as string;\n\n switch (args.action) {\n // ---- LIST: list invoices ----\n case \"list\": {\n let q = sb.from(\"invoices\")\n .select(\"id, invoice_number, customer_name, customer_email, total_amount, status, payment_status, amount_paid, amount_due, due_date, sent_at, paid_at, created_at\")\n .eq(\"store_id\", sid)\n .order(\"created_at\", { ascending: false });\n if (args.status) q = q.eq(\"status\", args.status as string);\n if (args.payment_status) q = q.eq(\"payment_status\", args.payment_status as string);\n if (args.customer_id) q = q.eq(\"customer_id\", args.customer_id as string);\n if (args.query) {\n const sq = sanitizeFilterValue(String(args.query));\n q = q.or(`invoice_number.ilike.%${sq}%,customer_name.ilike.%${sq}%,customer_email.ilike.%${sq}%`);\n }\n q = q.limit(args.limit as number || 50);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- GET: get invoice detail ----\n case \"get\": {\n const invoiceId = args.invoice_id as string;\n if (!invoiceId) return { success: false, error: \"invoice_id is required\" };\n const { data, error } = await sb.from(\"invoices\")\n .select(\"*\")\n .eq(\"id\", invoiceId)\n .eq(\"store_id\", sid)\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- CREATE: create a new invoice ----\n case \"create\": {\n const customerEmail = args.customer_email as string;\n if (!customerEmail) return { success: false, error: \"customer_email is required\" };\n\n // Generate sequential invoice number\n const { data: maxInv } = await sb.from(\"invoices\")\n .select(\"invoice_number\")\n .eq(\"store_id\", sid)\n .order(\"created_at\", { ascending: false })\n .limit(1)\n .single();\n let nextNum = 1001;\n if (maxInv?.invoice_number) {\n const parsed = parseInt((maxInv.invoice_number as string).replace(/\\D/g, \"\"), 10);\n if (!isNaN(parsed)) nextNum = parsed + 1;\n }\n const invoiceNumber = `INV-${String(nextNum).padStart(5, \"0\")}`;\n\n const lineItems = args.line_items as Array<{ description: string; quantity: number; unit_price: number }> || [];\n const subtotal = lineItems.reduce((s, li) => s + (li.quantity || 0) * (li.unit_price || 0), 0);\n const taxAmount = (args.tax_amount as number) || 0;\n const discountAmount = (args.discount_amount as number) || 0;\n const totalAmount = args.total_amount as number || Math.round((subtotal + taxAmount - discountAmount) * 100) / 100;\n\n const record: Record<string, unknown> = {\n store_id: sid,\n invoice_number: invoiceNumber,\n customer_email: customerEmail,\n line_items: lineItems,\n subtotal: Math.round(subtotal * 100) / 100,\n tax_amount: taxAmount,\n discount_amount: discountAmount,\n total_amount: totalAmount,\n status: \"draft\",\n payment_status: \"pending\",\n amount_paid: 0,\n };\n if (args.customer_id !== undefined) record.customer_id = args.customer_id;\n if (args.order_id !== undefined) record.order_id = args.order_id;\n if (args.customer_name !== undefined) record.customer_name = args.customer_name;\n if (args.customer_phone !== undefined) record.customer_phone = args.customer_phone;\n if (args.description !== undefined) record.description = args.description;\n if (args.due_date !== undefined) record.due_date = args.due_date;\n if (args.notes !== undefined) record.notes = args.notes;\n if (args.internal_notes !== undefined) record.internal_notes = args.internal_notes;\n\n const { data, error } = await sb.from(\"invoices\")\n .insert(record)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- UPDATE: modify an invoice ----\n case \"update\": {\n const invoiceId = args.invoice_id as string;\n if (!invoiceId) return { success: false, error: \"invoice_id is required\" };\n const updates: Record<string, unknown> = { updated_at: new Date().toISOString() };\n if (args.status !== undefined) updates.status = args.status;\n if (args.payment_status !== undefined) updates.payment_status = args.payment_status;\n if (args.line_items !== undefined) updates.line_items = args.line_items;\n if (args.subtotal !== undefined) updates.subtotal = args.subtotal;\n if (args.tax_amount !== undefined) updates.tax_amount = args.tax_amount;\n if (args.discount_amount !== undefined) updates.discount_amount = args.discount_amount;\n if (args.total_amount !== undefined) updates.total_amount = args.total_amount;\n if (args.amount_paid !== undefined) updates.amount_paid = args.amount_paid;\n if (args.due_date !== undefined) updates.due_date = args.due_date;\n if (args.notes !== undefined) updates.notes = args.notes;\n if (args.internal_notes !== undefined) updates.internal_notes = args.internal_notes;\n if (args.customer_name !== undefined) updates.customer_name = args.customer_name;\n if (args.customer_email !== undefined) updates.customer_email = args.customer_email;\n\n const { data, error } = await sb.from(\"invoices\")\n .update(updates)\n .eq(\"id\", invoiceId)\n .eq(\"store_id\", sid)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- MARK_SENT: mark an invoice as sent ----\n case \"mark_sent\": {\n const invoiceId = args.invoice_id as string;\n if (!invoiceId) return { success: false, error: \"invoice_id is required\" };\n const { data, error } = await sb.from(\"invoices\")\n .update({ status: \"sent\", sent_at: new Date().toISOString(), updated_at: new Date().toISOString() })\n .eq(\"id\", invoiceId)\n .eq(\"store_id\", sid)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- RECORD_PAYMENT: record a payment against an invoice ----\n case \"record_payment\": {\n const invoiceId = args.invoice_id as string;\n const amount = args.amount as number;\n if (!invoiceId || amount === undefined) return { success: false, error: \"invoice_id and amount are required\" };\n\n const { data: inv, error: invErr } = await sb.from(\"invoices\")\n .select(\"total_amount, amount_paid\")\n .eq(\"id\", invoiceId)\n .eq(\"store_id\", sid)\n .single();\n if (invErr || !inv) return { success: false, error: invErr?.message || \"Invoice not found\" };\n\n const newAmountPaid = Math.round(((Number(inv.amount_paid) || 0) + amount) * 100) / 100;\n const totalAmount = Number(inv.total_amount) || 0;\n const newPaymentStatus = newAmountPaid >= totalAmount ? \"paid\" : \"partial\";\n const newStatus = newAmountPaid >= totalAmount ? \"paid\" : \"partially_paid\";\n\n const updates: Record<string, unknown> = {\n amount_paid: newAmountPaid,\n amount_due: Math.round((totalAmount - newAmountPaid) * 100) / 100,\n payment_status: newPaymentStatus,\n status: newStatus,\n updated_at: new Date().toISOString(),\n };\n if (newAmountPaid >= totalAmount) updates.paid_at = new Date().toISOString();\n if (args.payment_method) updates.payment_method = args.payment_method;\n if (args.transaction_id) updates.transaction_id = args.transaction_id;\n\n const { data, error } = await sb.from(\"invoices\")\n .update(updates)\n .eq(\"id\", invoiceId)\n .eq(\"store_id\", sid)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- CANCEL: cancel an invoice ----\n case \"cancel\": {\n const invoiceId = args.invoice_id as string;\n if (!invoiceId) return { success: false, error: \"invoice_id is required\" };\n const { data, error } = await sb.from(\"invoices\")\n .update({ status: \"cancelled\", updated_at: new Date().toISOString() })\n .eq(\"id\", invoiceId)\n .eq(\"store_id\", sid)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n default:\n return { success: false, error: `Unknown invoices action: ${args.action}. Valid: list, get, create, update, mark_sent, record_payment, cancel` };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
type Result = {
|
|
3
|
+
success: boolean;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
error?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
export declare function handleLoyalty(sb: SupabaseClient, args: Record<string, unknown>, storeId?: string): Promise<Result>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// server/handlers/loyalty.ts — Loyalty programs, rewards, and customer points management
|
|
2
|
+
|
|
3
|
+
export async function handleLoyalty(sb, args, storeId) {
|
|
4
|
+
const sid = storeId;
|
|
5
|
+
switch (args.action) {
|
|
6
|
+
// ---- GET_PROGRAM: fetch the store's loyalty program config ----
|
|
7
|
+
case "get_program":
|
|
8
|
+
{
|
|
9
|
+
const {
|
|
10
|
+
data,
|
|
11
|
+
error
|
|
12
|
+
} = await sb.from("loyalty_programs").select("*").eq("store_id", sid).maybeSingle();
|
|
13
|
+
if (error) return {
|
|
14
|
+
success: false,
|
|
15
|
+
error: error.message
|
|
16
|
+
};
|
|
17
|
+
if (!data) return {
|
|
18
|
+
success: true,
|
|
19
|
+
data: null,
|
|
20
|
+
error: "No loyalty program configured for this store"
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
success: true,
|
|
24
|
+
data
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---- UPDATE_PROGRAM: update loyalty program settings ----
|
|
29
|
+
case "update_program":
|
|
30
|
+
{
|
|
31
|
+
const updates = {
|
|
32
|
+
updated_at: new Date().toISOString()
|
|
33
|
+
};
|
|
34
|
+
if (args.name !== undefined) updates.name = args.name;
|
|
35
|
+
if (args.description !== undefined) updates.description = args.description;
|
|
36
|
+
if (args.points_per_dollar !== undefined) updates.points_per_dollar = args.points_per_dollar;
|
|
37
|
+
if (args.point_value !== undefined) updates.point_value = args.point_value;
|
|
38
|
+
if (args.min_redemption_points !== undefined) updates.min_redemption_points = args.min_redemption_points;
|
|
39
|
+
if (args.points_expiry_days !== undefined) updates.points_expiry_days = args.points_expiry_days;
|
|
40
|
+
if (args.tiers !== undefined) updates.tiers = args.tiers;
|
|
41
|
+
if (args.is_active !== undefined) updates.is_active = args.is_active;
|
|
42
|
+
if (args.allow_points_on_discounted_items !== undefined) updates.allow_points_on_discounted_items = args.allow_points_on_discounted_items;
|
|
43
|
+
if (args.points_on_tax !== undefined) updates.points_on_tax = args.points_on_tax;
|
|
44
|
+
const {
|
|
45
|
+
data,
|
|
46
|
+
error
|
|
47
|
+
} = await sb.from("loyalty_programs").update(updates).eq("store_id", sid).select().single();
|
|
48
|
+
if (error) return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: error.message
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
data
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---- LIST_REWARDS: list available loyalty rewards ----
|
|
59
|
+
case "list_rewards":
|
|
60
|
+
{
|
|
61
|
+
let q = sb.from("loyalty_rewards").select("*").eq("store_id", sid).order("points_required", {
|
|
62
|
+
ascending: true
|
|
63
|
+
});
|
|
64
|
+
if (args.is_active !== undefined) q = q.eq("is_active", args.is_active);
|
|
65
|
+
if (args.limit) q = q.limit(args.limit);
|
|
66
|
+
const {
|
|
67
|
+
data,
|
|
68
|
+
error
|
|
69
|
+
} = await q;
|
|
70
|
+
return error ? {
|
|
71
|
+
success: false,
|
|
72
|
+
error: error.message
|
|
73
|
+
} : {
|
|
74
|
+
success: true,
|
|
75
|
+
count: data?.length,
|
|
76
|
+
data
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---- CREATE_REWARD: add a new redeemable reward ----
|
|
81
|
+
case "create_reward":
|
|
82
|
+
{
|
|
83
|
+
const name = args.name;
|
|
84
|
+
const pointsRequired = args.points_required;
|
|
85
|
+
const rewardType = args.reward_type;
|
|
86
|
+
if (!name || !pointsRequired || !rewardType) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: "name, points_required, and reward_type are required"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const record = {
|
|
93
|
+
store_id: sid,
|
|
94
|
+
name,
|
|
95
|
+
points_required: pointsRequired,
|
|
96
|
+
reward_type: rewardType
|
|
97
|
+
};
|
|
98
|
+
if (args.description !== undefined) record.description = args.description;
|
|
99
|
+
if (args.reward_value !== undefined) record.reward_value = args.reward_value;
|
|
100
|
+
if (args.product_id !== undefined) record.product_id = args.product_id;
|
|
101
|
+
if (args.max_redemptions !== undefined) record.max_redemptions = args.max_redemptions;
|
|
102
|
+
const {
|
|
103
|
+
data,
|
|
104
|
+
error
|
|
105
|
+
} = await sb.from("loyalty_rewards").insert(record).select().single();
|
|
106
|
+
return error ? {
|
|
107
|
+
success: false,
|
|
108
|
+
error: error.message
|
|
109
|
+
} : {
|
|
110
|
+
success: true,
|
|
111
|
+
data
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- UPDATE_REWARD: modify an existing reward ----
|
|
116
|
+
case "update_reward":
|
|
117
|
+
{
|
|
118
|
+
const rewardId = args.reward_id;
|
|
119
|
+
if (!rewardId) return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: "reward_id is required"
|
|
122
|
+
};
|
|
123
|
+
const updates = {};
|
|
124
|
+
if (args.name !== undefined) updates.name = args.name;
|
|
125
|
+
if (args.description !== undefined) updates.description = args.description;
|
|
126
|
+
if (args.points_required !== undefined) updates.points_required = args.points_required;
|
|
127
|
+
if (args.reward_type !== undefined) updates.reward_type = args.reward_type;
|
|
128
|
+
if (args.reward_value !== undefined) updates.reward_value = args.reward_value;
|
|
129
|
+
if (args.is_active !== undefined) updates.is_active = args.is_active;
|
|
130
|
+
if (args.max_redemptions !== undefined) updates.max_redemptions = args.max_redemptions;
|
|
131
|
+
const {
|
|
132
|
+
data,
|
|
133
|
+
error
|
|
134
|
+
} = await sb.from("loyalty_rewards").update(updates).eq("id", rewardId).eq("store_id", sid).select().single();
|
|
135
|
+
return error ? {
|
|
136
|
+
success: false,
|
|
137
|
+
error: error.message
|
|
138
|
+
} : {
|
|
139
|
+
success: true,
|
|
140
|
+
data
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---- TRANSACTIONS: list loyalty point transactions ----
|
|
145
|
+
case "transactions":
|
|
146
|
+
{
|
|
147
|
+
let q = sb.from("loyalty_transactions").select("*").eq("store_id", sid).order("created_at", {
|
|
148
|
+
ascending: false
|
|
149
|
+
});
|
|
150
|
+
if (args.customer_id) q = q.eq("customer_id", args.customer_id);
|
|
151
|
+
if (args.transaction_type) q = q.eq("transaction_type", args.transaction_type);
|
|
152
|
+
q = q.limit(args.limit || 50);
|
|
153
|
+
const {
|
|
154
|
+
data,
|
|
155
|
+
error
|
|
156
|
+
} = await q;
|
|
157
|
+
return error ? {
|
|
158
|
+
success: false,
|
|
159
|
+
error: error.message
|
|
160
|
+
} : {
|
|
161
|
+
success: true,
|
|
162
|
+
count: data?.length,
|
|
163
|
+
data
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---- ANOMALIES: list loyalty fraud/anomaly alerts ----
|
|
168
|
+
case "anomalies":
|
|
169
|
+
{
|
|
170
|
+
let q = sb.from("loyalty_anomalies").select("*").order("detected_at", {
|
|
171
|
+
ascending: false
|
|
172
|
+
});
|
|
173
|
+
if (args.severity) q = q.eq("severity", args.severity);
|
|
174
|
+
if (args.resolved === false) q = q.is("resolved_at", null);
|
|
175
|
+
if (args.resolved === true) q = q.not("resolved_at", "is", null);
|
|
176
|
+
q = q.limit(args.limit || 25);
|
|
177
|
+
const {
|
|
178
|
+
data,
|
|
179
|
+
error
|
|
180
|
+
} = await q;
|
|
181
|
+
return error ? {
|
|
182
|
+
success: false,
|
|
183
|
+
error: error.message
|
|
184
|
+
} : {
|
|
185
|
+
success: true,
|
|
186
|
+
count: data?.length,
|
|
187
|
+
data
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
default:
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: `Unknown loyalty action: ${args.action}. Valid: get_program, update_program, list_rewards, create_reward, update_reward, transactions, anomalies`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=loyalty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loyalty.js","names":["handleLoyalty","sb","args","storeId","sid","action","data","error","from","select","eq","maybeSingle","success","message","updates","updated_at","Date","toISOString","name","undefined","description","points_per_dollar","point_value","min_redemption_points","points_expiry_days","tiers","is_active","allow_points_on_discounted_items","points_on_tax","update","single","q","order","ascending","limit","count","length","pointsRequired","points_required","rewardType","reward_type","record","store_id","reward_value","product_id","max_redemptions","insert","rewardId","reward_id","customer_id","transaction_type","severity","resolved","is","not"],"sources":["../../../src/server/handlers/loyalty.ts"],"sourcesContent":["// server/handlers/loyalty.ts — Loyalty programs, rewards, and customer points management\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\ntype Result = { success: boolean; data?: unknown; error?: string; [key: string]: unknown };\n\nexport async function handleLoyalty(\n sb: SupabaseClient,\n args: Record<string, unknown>,\n storeId?: string,\n): Promise<Result> {\n const sid = storeId as string;\n\n switch (args.action) {\n // ---- GET_PROGRAM: fetch the store's loyalty program config ----\n case \"get_program\": {\n const { data, error } = await sb.from(\"loyalty_programs\")\n .select(\"*\")\n .eq(\"store_id\", sid)\n .maybeSingle();\n if (error) return { success: false, error: error.message };\n if (!data) return { success: true, data: null, error: \"No loyalty program configured for this store\" };\n return { success: true, data };\n }\n\n // ---- UPDATE_PROGRAM: update loyalty program settings ----\n case \"update_program\": {\n const updates: Record<string, unknown> = { updated_at: new Date().toISOString() };\n if (args.name !== undefined) updates.name = args.name;\n if (args.description !== undefined) updates.description = args.description;\n if (args.points_per_dollar !== undefined) updates.points_per_dollar = args.points_per_dollar;\n if (args.point_value !== undefined) updates.point_value = args.point_value;\n if (args.min_redemption_points !== undefined) updates.min_redemption_points = args.min_redemption_points;\n if (args.points_expiry_days !== undefined) updates.points_expiry_days = args.points_expiry_days;\n if (args.tiers !== undefined) updates.tiers = args.tiers;\n if (args.is_active !== undefined) updates.is_active = args.is_active;\n if (args.allow_points_on_discounted_items !== undefined) updates.allow_points_on_discounted_items = args.allow_points_on_discounted_items;\n if (args.points_on_tax !== undefined) updates.points_on_tax = args.points_on_tax;\n\n const { data, error } = await sb.from(\"loyalty_programs\")\n .update(updates)\n .eq(\"store_id\", sid)\n .select()\n .single();\n if (error) return { success: false, error: error.message };\n return { success: true, data };\n }\n\n // ---- LIST_REWARDS: list available loyalty rewards ----\n case \"list_rewards\": {\n let q = sb.from(\"loyalty_rewards\")\n .select(\"*\")\n .eq(\"store_id\", sid)\n .order(\"points_required\", { ascending: true });\n if (args.is_active !== undefined) q = q.eq(\"is_active\", args.is_active as boolean);\n if (args.limit) q = q.limit(args.limit as number);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- CREATE_REWARD: add a new redeemable reward ----\n case \"create_reward\": {\n const name = args.name as string;\n const pointsRequired = args.points_required as number;\n const rewardType = args.reward_type as string;\n if (!name || !pointsRequired || !rewardType) {\n return { success: false, error: \"name, points_required, and reward_type are required\" };\n }\n const record: Record<string, unknown> = {\n store_id: sid,\n name,\n points_required: pointsRequired,\n reward_type: rewardType,\n };\n if (args.description !== undefined) record.description = args.description;\n if (args.reward_value !== undefined) record.reward_value = args.reward_value;\n if (args.product_id !== undefined) record.product_id = args.product_id;\n if (args.max_redemptions !== undefined) record.max_redemptions = args.max_redemptions;\n\n const { data, error } = await sb.from(\"loyalty_rewards\")\n .insert(record)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- UPDATE_REWARD: modify an existing reward ----\n case \"update_reward\": {\n const rewardId = args.reward_id as string;\n if (!rewardId) return { success: false, error: \"reward_id is required\" };\n const updates: Record<string, unknown> = {};\n if (args.name !== undefined) updates.name = args.name;\n if (args.description !== undefined) updates.description = args.description;\n if (args.points_required !== undefined) updates.points_required = args.points_required;\n if (args.reward_type !== undefined) updates.reward_type = args.reward_type;\n if (args.reward_value !== undefined) updates.reward_value = args.reward_value;\n if (args.is_active !== undefined) updates.is_active = args.is_active;\n if (args.max_redemptions !== undefined) updates.max_redemptions = args.max_redemptions;\n\n const { data, error } = await sb.from(\"loyalty_rewards\")\n .update(updates)\n .eq(\"id\", rewardId)\n .eq(\"store_id\", sid)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n // ---- TRANSACTIONS: list loyalty point transactions ----\n case \"transactions\": {\n let q = sb.from(\"loyalty_transactions\")\n .select(\"*\")\n .eq(\"store_id\", sid)\n .order(\"created_at\", { ascending: false });\n if (args.customer_id) q = q.eq(\"customer_id\", args.customer_id as string);\n if (args.transaction_type) q = q.eq(\"transaction_type\", args.transaction_type as string);\n q = q.limit(args.limit as number || 50);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- ANOMALIES: list loyalty fraud/anomaly alerts ----\n case \"anomalies\": {\n let q = sb.from(\"loyalty_anomalies\")\n .select(\"*\")\n .order(\"detected_at\", { ascending: false });\n if (args.severity) q = q.eq(\"severity\", args.severity as string);\n if (args.resolved === false) q = q.is(\"resolved_at\", null);\n if (args.resolved === true) q = q.not(\"resolved_at\", \"is\", null);\n q = q.limit(args.limit as number || 25);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n default:\n return { success: false, error: `Unknown loyalty action: ${args.action}. Valid: get_program, update_program, list_rewards, create_reward, update_reward, transactions, anomalies` };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -102,6 +102,7 @@ export async function graphDelete(path, token) {
|
|
|
102
102
|
// CREDENTIALS
|
|
103
103
|
// ============================================================================
|
|
104
104
|
|
|
105
|
+
const TOKEN_EXPIRY_WARNING_DAYS = 7;
|
|
105
106
|
export async function getIntegration(sb, storeId) {
|
|
106
107
|
const {
|
|
107
108
|
data,
|
|
@@ -110,11 +111,25 @@ export async function getIntegration(sb, storeId) {
|
|
|
110
111
|
if (error || !data?.access_token_encrypted || !data?.ad_account_id) {
|
|
111
112
|
throw new Error("Meta not connected. Go to Settings → Meta Connection to link your account.");
|
|
112
113
|
}
|
|
113
|
-
// Warn if token is expired (but still return it — some operations may work with refresh)
|
|
114
114
|
if (data.token_expires_at) {
|
|
115
115
|
const expiresAt = new Date(data.token_expires_at);
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
const daysLeft = (expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24);
|
|
117
|
+
if (daysLeft <= 0) {
|
|
118
|
+
// Token expired — update status so CRM shows reconnect prompt
|
|
119
|
+
if (data.status !== "token_expired") {
|
|
120
|
+
await sb.from("meta_integrations").update({
|
|
121
|
+
status: "token_expired",
|
|
122
|
+
updated_at: new Date().toISOString()
|
|
123
|
+
}).eq("store_id", storeId);
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`Meta access token expired. Reconnect in CRM Settings → Meta Connection.`);
|
|
126
|
+
}
|
|
127
|
+
if (daysLeft <= TOKEN_EXPIRY_WARNING_DAYS && data.status !== "expiring_soon") {
|
|
128
|
+
// Proactively mark as expiring so CRM can show warning banner
|
|
129
|
+
await sb.from("meta_integrations").update({
|
|
130
|
+
status: "expiring_soon",
|
|
131
|
+
updated_at: new Date().toISOString()
|
|
132
|
+
}).eq("store_id", storeId);
|
|
118
133
|
}
|
|
119
134
|
}
|
|
120
135
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-ads-graph-api.js","names":["META_BASE","enc","v","encodeURIComponent","formBody","params","token","parts","k","Object","entries","undefined","push","JSON","stringify","String","join","parseMetaError","body","path","status","json","parse","err","error","userMsg","error_user_msg","message","code","slice","graphGet","fields","extra","url","res","fetch","ok","text","Error","page1","Array","isArray","data","all","next","paging","pages","r","j","graphPost","method","headers","graphDelete","getIntegration","sb","storeId","from","select","eq","limit","single","access_token_encrypted","ad_account_id","token_expires_at","expiresAt","Date","toISOString","accessToken","adAccountId","businessId","business_id","pageId","page_id","pixelId","pixel_id","instagramAccountId","instagram_business_id","tokenExpiresAt","pageTokenCache","Map","getPageToken","int","cacheKey","cached","get","now","fetchedAt","result","pageToken","access_token","set","size","delete","keys"],"sources":["../../../src/server/handlers/meta-ads-graph-api.ts"],"sourcesContent":["/**\n * Meta Ads — Graph API low-level helpers, credentials, and page token management.\n */\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport { META_BASE, type MetaIntegration } from \"./meta-ads-types.js\";\n\n// ============================================================================\n// GRAPH API — low-level Meta helpers\n// ============================================================================\n\n/** RFC 3986 percent-encode. */\nexport function enc(v: string): string {\n return encodeURIComponent(v);\n}\n\n/** Build form-encoded body from params object + access_token. */\nfunction formBody(params: Record<string, unknown>, token: string): string {\n const parts: string[] = [`access_token=${enc(token)}`];\n for (const [k, v] of Object.entries(params)) {\n if (v === null || v === undefined) continue;\n parts.push(`${k}=${enc(typeof v === \"object\" ? JSON.stringify(v) : String(v))}`);\n }\n return parts.join(\"&\");\n}\n\n/**\n * Parse Meta API error responses into human-readable messages.\n * Extracts error_user_msg when available (much better than raw JSON).\n */\nexport function parseMetaError(body: string, path: string, status: number): string {\n try {\n const json = JSON.parse(body);\n const err = json.error || {};\n const userMsg = err.error_user_msg || err.message || \"Unknown error\";\n const code = err.code ? ` (code ${err.code})` : \"\";\n return `Meta API ${path}: ${userMsg}${code}`;\n } catch {\n return `Meta API ${path}: ${status} — ${body.slice(0, 300)}`;\n }\n}\n\n/** GET from Meta Graph API with auto-pagination. */\nexport async function graphGet(\n path: string,\n fields: string,\n token: string,\n extra?: string,\n): Promise<Record<string, unknown>> {\n let url = `${META_BASE}/${path}?fields=${fields}&access_token=${enc(token)}&limit=200`;\n if (extra) url += `&${extra}`;\n\n const res = await fetch(url);\n if (!res.ok) {\n const body = await res.text();\n throw new Error(parseMetaError(body, path, res.status));\n }\n const page1 = (await res.json()) as Record<string, unknown>;\n\n // Single-object response — return as-is\n if (!Array.isArray(page1.data)) return page1;\n\n // List response — paginate up to 20 pages\n const all = [...(page1.data as Record<string, unknown>[])];\n let next = (page1.paging as Record<string, unknown>)?.next as string | undefined;\n let pages = 1;\n while (next && pages < 20) {\n const r = await fetch(next);\n if (!r.ok) break;\n const j = (await r.json()) as Record<string, unknown>;\n if (Array.isArray(j.data)) all.push(...(j.data as Record<string, unknown>[]));\n next = (j.paging as Record<string, unknown>)?.next as string | undefined;\n pages++;\n }\n return { data: all };\n}\n\n/** POST to Meta Graph API with form encoding. */\nexport async function graphPost(\n path: string,\n params: Record<string, unknown>,\n token: string,\n): Promise<Record<string, unknown>> {\n const res = await fetch(`${META_BASE}/${path}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: formBody(params, token),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(parseMetaError(body, path, res.status));\n }\n return (await res.json()) as Record<string, unknown>;\n}\n\n/** DELETE from Meta Graph API. */\nexport async function graphDelete(\n path: string,\n token: string,\n): Promise<Record<string, unknown>> {\n const res = await fetch(`${META_BASE}/${path}?access_token=${enc(token)}`, {\n method: \"DELETE\",\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(parseMetaError(body, path, res.status));\n }\n return (await res.json()) as Record<string, unknown>;\n}\n\n// ============================================================================\n// CREDENTIALS\n// ============================================================================\n\nexport async function getIntegration(sb: SupabaseClient, storeId: string): Promise<MetaIntegration> {\n const { data, error } = await sb\n .from(\"meta_integrations\")\n .select(\"access_token_encrypted, ad_account_id, business_id, page_id, pixel_id, instagram_business_id, token_expires_at, status\")\n .eq(\"store_id\", storeId)\n .limit(1)\n .single();\n if (error || !data?.access_token_encrypted || !data?.ad_account_id) {\n throw new Error(\"Meta not connected. Go to Settings → Meta Connection to link your account.\");\n }\n // Warn if token is expired (but still return it — some operations may work with refresh)\n if (data.token_expires_at) {\n const expiresAt = new Date(data.token_expires_at);\n if (expiresAt < new Date()) {\n throw new Error(`Meta access token expired at ${expiresAt.toISOString()}. Reconnect in the CRM app (Settings → Meta Connection).`);\n }\n }\n return {\n accessToken: data.access_token_encrypted,\n adAccountId: data.ad_account_id,\n businessId: data.business_id || null,\n pageId: data.page_id || null,\n pixelId: data.pixel_id || null,\n instagramAccountId: data.instagram_business_id || null,\n tokenExpiresAt: data.token_expires_at || null,\n };\n}\n\n/** Page Access Token cache (user token → page token). Avoids re-fetching every call. */\nconst pageTokenCache = new Map<string, { token: string; fetchedAt: number }>();\n\n/**\n * Exchange user access token for a Page Access Token.\n * Required for lead forms, page-level APIs, and some creative operations.\n * Cached for 5 minutes.\n */\nexport async function getPageToken(int: MetaIntegration): Promise<string> {\n if (!int.pageId) throw new Error(\"No Facebook Page linked. Add page_id in Meta Connection settings.\");\n\n const cacheKey = `${int.pageId}:${int.accessToken.slice(-10)}`;\n const cached = pageTokenCache.get(cacheKey);\n if (cached && Date.now() - cached.fetchedAt < 300_000) return cached.token;\n\n const result = await graphGet(int.pageId, \"access_token\", int.accessToken);\n const pageToken = result.access_token as string;\n if (!pageToken) throw new Error(\"Could not get Page Access Token. Ensure your token has pages_manage_ads permission.\");\n\n pageTokenCache.set(cacheKey, { token: pageToken, fetchedAt: Date.now() });\n // Evict old entries\n if (pageTokenCache.size > 20) pageTokenCache.delete([...pageTokenCache.keys()][0]);\n\n return pageToken;\n}\n"],"mappings":"AAAA;AACA;AACA;;AAGA,SAASA,SAAS,QAA8B,qBAAqB;;AAErE;AACA;AACA;;AAEA;AACA,OAAO,SAASC,GAAGA,CAACC,CAAS,EAAU;EACrC,OAAOC,kBAAkB,CAACD,CAAC,CAAC;AAC9B;;AAEA;AACA,SAASE,QAAQA,CAACC,MAA+B,EAAEC,KAAa,EAAU;EACxE,MAAMC,KAAe,GAAG,CAAC,gBAAgBN,GAAG,CAACK,KAAK,CAAC,EAAE,CAAC;EACtD,KAAK,MAAM,CAACE,CAAC,EAAEN,CAAC,CAAC,IAAIO,MAAM,CAACC,OAAO,CAACL,MAAM,CAAC,EAAE;IAC3C,IAAIH,CAAC,KAAK,IAAI,IAAIA,CAAC,KAAKS,SAAS,EAAE;IACnCJ,KAAK,CAACK,IAAI,CAAC,GAAGJ,CAAC,IAAIP,GAAG,CAAC,OAAOC,CAAC,KAAK,QAAQ,GAAGW,IAAI,CAACC,SAAS,CAACZ,CAAC,CAAC,GAAGa,MAAM,CAACb,CAAC,CAAC,CAAC,EAAE,CAAC;EAClF;EACA,OAAOK,KAAK,CAACS,IAAI,CAAC,GAAG,CAAC;AACxB;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,IAAY,EAAEC,IAAY,EAAEC,MAAc,EAAU;EACjF,IAAI;IACF,MAAMC,IAAI,GAAGR,IAAI,CAACS,KAAK,CAACJ,IAAI,CAAC;IAC7B,MAAMK,GAAG,GAAGF,IAAI,CAACG,KAAK,IAAI,CAAC,CAAC;IAC5B,MAAMC,OAAO,GAAGF,GAAG,CAACG,cAAc,IAAIH,GAAG,CAACI,OAAO,IAAI,eAAe;IACpE,MAAMC,IAAI,GAAGL,GAAG,CAACK,IAAI,GAAG,UAAUL,GAAG,CAACK,IAAI,GAAG,GAAG,EAAE;IAClD,OAAO,YAAYT,IAAI,KAAKM,OAAO,GAAGG,IAAI,EAAE;EAC9C,CAAC,CAAC,MAAM;IACN,OAAO,YAAYT,IAAI,KAAKC,MAAM,MAAMF,IAAI,CAACW,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;EAC9D;AACF;;AAEA;AACA,OAAO,eAAeC,QAAQA,CAC5BX,IAAY,EACZY,MAAc,EACdzB,KAAa,EACb0B,KAAc,EACoB;EAClC,IAAIC,GAAG,GAAG,GAAGjC,SAAS,IAAImB,IAAI,WAAWY,MAAM,iBAAiB9B,GAAG,CAACK,KAAK,CAAC,YAAY;EACtF,IAAI0B,KAAK,EAAEC,GAAG,IAAI,IAAID,KAAK,EAAE;EAE7B,MAAME,GAAG,GAAG,MAAMC,KAAK,CAACF,GAAG,CAAC;EAC5B,IAAI,CAACC,GAAG,CAACE,EAAE,EAAE;IACX,MAAMlB,IAAI,GAAG,MAAMgB,GAAG,CAACG,IAAI,CAAC,CAAC;IAC7B,MAAM,IAAIC,KAAK,CAACrB,cAAc,CAACC,IAAI,EAAEC,IAAI,EAAEe,GAAG,CAACd,MAAM,CAAC,CAAC;EACzD;EACA,MAAMmB,KAAK,GAAI,MAAML,GAAG,CAACb,IAAI,CAAC,CAA6B;;EAE3D;EACA,IAAI,CAACmB,KAAK,CAACC,OAAO,CAACF,KAAK,CAACG,IAAI,CAAC,EAAE,OAAOH,KAAK;;EAE5C;EACA,MAAMI,GAAG,GAAG,CAAC,GAAIJ,KAAK,CAACG,IAAkC,CAAC;EAC1D,IAAIE,IAAI,GAAIL,KAAK,CAACM,MAAM,EAA8BD,IAA0B;EAChF,IAAIE,KAAK,GAAG,CAAC;EACb,OAAOF,IAAI,IAAIE,KAAK,GAAG,EAAE,EAAE;IACzB,MAAMC,CAAC,GAAG,MAAMZ,KAAK,CAACS,IAAI,CAAC;IAC3B,IAAI,CAACG,CAAC,CAACX,EAAE,EAAE;IACX,MAAMY,CAAC,GAAI,MAAMD,CAAC,CAAC1B,IAAI,CAAC,CAA6B;IACrD,IAAImB,KAAK,CAACC,OAAO,CAACO,CAAC,CAACN,IAAI,CAAC,EAAEC,GAAG,CAAC/B,IAAI,CAAC,GAAIoC,CAAC,CAACN,IAAkC,CAAC;IAC7EE,IAAI,GAAII,CAAC,CAACH,MAAM,EAA8BD,IAA0B;IACxEE,KAAK,EAAE;EACT;EACA,OAAO;IAAEJ,IAAI,EAAEC;EAAI,CAAC;AACtB;;AAEA;AACA,OAAO,eAAeM,SAASA,CAC7B9B,IAAY,EACZd,MAA+B,EAC/BC,KAAa,EACqB;EAClC,MAAM4B,GAAG,GAAG,MAAMC,KAAK,CAAC,GAAGnC,SAAS,IAAImB,IAAI,EAAE,EAAE;IAC9C+B,MAAM,EAAE,MAAM;IACdC,OAAO,EAAE;MAAE,cAAc,EAAE;IAAoC,CAAC;IAChEjC,IAAI,EAAEd,QAAQ,CAACC,MAAM,EAAEC,KAAK;EAC9B,CAAC,CAAC;EACF,IAAI,CAAC4B,GAAG,CAACE,EAAE,EAAE;IACX,MAAMlB,IAAI,GAAG,MAAMgB,GAAG,CAACG,IAAI,CAAC,CAAC;IAC7B,MAAM,IAAIC,KAAK,CAACrB,cAAc,CAACC,IAAI,EAAEC,IAAI,EAAEe,GAAG,CAACd,MAAM,CAAC,CAAC;EACzD;EACA,OAAQ,MAAMc,GAAG,CAACb,IAAI,CAAC,CAAC;AAC1B;;AAEA;AACA,OAAO,eAAe+B,WAAWA,CAC/BjC,IAAY,EACZb,KAAa,EACqB;EAClC,MAAM4B,GAAG,GAAG,MAAMC,KAAK,CAAC,GAAGnC,SAAS,IAAImB,IAAI,iBAAiBlB,GAAG,CAACK,KAAK,CAAC,EAAE,EAAE;IACzE4C,MAAM,EAAE;EACV,CAAC,CAAC;EACF,IAAI,CAAChB,GAAG,CAACE,EAAE,EAAE;IACX,MAAMlB,IAAI,GAAG,MAAMgB,GAAG,CAACG,IAAI,CAAC,CAAC;IAC7B,MAAM,IAAIC,KAAK,CAACrB,cAAc,CAACC,IAAI,EAAEC,IAAI,EAAEe,GAAG,CAACd,MAAM,CAAC,CAAC;EACzD;EACA,OAAQ,MAAMc,GAAG,CAACb,IAAI,CAAC,CAAC;AAC1B;;AAEA;AACA;AACA;;AAEA,OAAO,eAAegC,cAAcA,CAACC,EAAkB,EAAEC,OAAe,EAA4B;EAClG,MAAM;IAAEb,IAAI;IAAElB;EAAM,CAAC,GAAG,MAAM8B,EAAE,CAC7BE,IAAI,CAAC,mBAAmB,CAAC,CACzBC,MAAM,CAAC,wHAAwH,CAAC,CAChIC,EAAE,CAAC,UAAU,EAAEH,OAAO,CAAC,CACvBI,KAAK,CAAC,CAAC,CAAC,CACRC,MAAM,CAAC,CAAC;EACX,IAAIpC,KAAK,IAAI,CAACkB,IAAI,EAAEmB,sBAAsB,IAAI,CAACnB,IAAI,EAAEoB,aAAa,EAAE;IAClE,MAAM,IAAIxB,KAAK,CAAC,4EAA4E,CAAC;EAC/F;EACA;EACA,IAAII,IAAI,CAACqB,gBAAgB,EAAE;IACzB,MAAMC,SAAS,GAAG,IAAIC,IAAI,CAACvB,IAAI,CAACqB,gBAAgB,CAAC;IACjD,IAAIC,SAAS,GAAG,IAAIC,IAAI,CAAC,CAAC,EAAE;MAC1B,MAAM,IAAI3B,KAAK,CAAC,gCAAgC0B,SAAS,CAACE,WAAW,CAAC,CAAC,0DAA0D,CAAC;IACpI;EACF;EACA,OAAO;IACLC,WAAW,EAAEzB,IAAI,CAACmB,sBAAsB;IACxCO,WAAW,EAAE1B,IAAI,CAACoB,aAAa;IAC/BO,UAAU,EAAE3B,IAAI,CAAC4B,WAAW,IAAI,IAAI;IACpCC,MAAM,EAAE7B,IAAI,CAAC8B,OAAO,IAAI,IAAI;IAC5BC,OAAO,EAAE/B,IAAI,CAACgC,QAAQ,IAAI,IAAI;IAC9BC,kBAAkB,EAAEjC,IAAI,CAACkC,qBAAqB,IAAI,IAAI;IACtDC,cAAc,EAAEnC,IAAI,CAACqB,gBAAgB,IAAI;EAC3C,CAAC;AACH;;AAEA;AACA,MAAMe,cAAc,GAAG,IAAIC,GAAG,CAA+C,CAAC;;AAE9E;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,YAAYA,CAACC,GAAoB,EAAmB;EACxE,IAAI,CAACA,GAAG,CAACV,MAAM,EAAE,MAAM,IAAIjC,KAAK,CAAC,mEAAmE,CAAC;EAErG,MAAM4C,QAAQ,GAAG,GAAGD,GAAG,CAACV,MAAM,IAAIU,GAAG,CAACd,WAAW,CAACtC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE;EAC9D,MAAMsD,MAAM,GAAGL,cAAc,CAACM,GAAG,CAACF,QAAQ,CAAC;EAC3C,IAAIC,MAAM,IAAIlB,IAAI,CAACoB,GAAG,CAAC,CAAC,GAAGF,MAAM,CAACG,SAAS,GAAG,OAAO,EAAE,OAAOH,MAAM,CAAC7E,KAAK;EAE1E,MAAMiF,MAAM,GAAG,MAAMzD,QAAQ,CAACmD,GAAG,CAACV,MAAM,EAAE,cAAc,EAAEU,GAAG,CAACd,WAAW,CAAC;EAC1E,MAAMqB,SAAS,GAAGD,MAAM,CAACE,YAAsB;EAC/C,IAAI,CAACD,SAAS,EAAE,MAAM,IAAIlD,KAAK,CAAC,qFAAqF,CAAC;EAEtHwC,cAAc,CAACY,GAAG,CAACR,QAAQ,EAAE;IAAE5E,KAAK,EAAEkF,SAAS;IAAEF,SAAS,EAAErB,IAAI,CAACoB,GAAG,CAAC;EAAE,CAAC,CAAC;EACzE;EACA,IAAIP,cAAc,CAACa,IAAI,GAAG,EAAE,EAAEb,cAAc,CAACc,MAAM,CAAC,CAAC,GAAGd,cAAc,CAACe,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;EAElF,OAAOL,SAAS;AAClB","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"meta-ads-graph-api.js","names":["META_BASE","enc","v","encodeURIComponent","formBody","params","token","parts","k","Object","entries","undefined","push","JSON","stringify","String","join","parseMetaError","body","path","status","json","parse","err","error","userMsg","error_user_msg","message","code","slice","graphGet","fields","extra","url","res","fetch","ok","text","Error","page1","Array","isArray","data","all","next","paging","pages","r","j","graphPost","method","headers","graphDelete","TOKEN_EXPIRY_WARNING_DAYS","getIntegration","sb","storeId","from","select","eq","limit","single","access_token_encrypted","ad_account_id","token_expires_at","expiresAt","Date","daysLeft","getTime","now","update","updated_at","toISOString","accessToken","adAccountId","businessId","business_id","pageId","page_id","pixelId","pixel_id","instagramAccountId","instagram_business_id","tokenExpiresAt","pageTokenCache","Map","getPageToken","int","cacheKey","cached","get","fetchedAt","result","pageToken","access_token","set","size","delete","keys"],"sources":["../../../src/server/handlers/meta-ads-graph-api.ts"],"sourcesContent":["/**\n * Meta Ads — Graph API low-level helpers, credentials, and page token management.\n */\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport { META_BASE, type MetaIntegration } from \"./meta-ads-types.js\";\n\n// ============================================================================\n// GRAPH API — low-level Meta helpers\n// ============================================================================\n\n/** RFC 3986 percent-encode. */\nexport function enc(v: string): string {\n return encodeURIComponent(v);\n}\n\n/** Build form-encoded body from params object + access_token. */\nfunction formBody(params: Record<string, unknown>, token: string): string {\n const parts: string[] = [`access_token=${enc(token)}`];\n for (const [k, v] of Object.entries(params)) {\n if (v === null || v === undefined) continue;\n parts.push(`${k}=${enc(typeof v === \"object\" ? JSON.stringify(v) : String(v))}`);\n }\n return parts.join(\"&\");\n}\n\n/**\n * Parse Meta API error responses into human-readable messages.\n * Extracts error_user_msg when available (much better than raw JSON).\n */\nexport function parseMetaError(body: string, path: string, status: number): string {\n try {\n const json = JSON.parse(body);\n const err = json.error || {};\n const userMsg = err.error_user_msg || err.message || \"Unknown error\";\n const code = err.code ? ` (code ${err.code})` : \"\";\n return `Meta API ${path}: ${userMsg}${code}`;\n } catch {\n return `Meta API ${path}: ${status} — ${body.slice(0, 300)}`;\n }\n}\n\n/** GET from Meta Graph API with auto-pagination. */\nexport async function graphGet(\n path: string,\n fields: string,\n token: string,\n extra?: string,\n): Promise<Record<string, unknown>> {\n let url = `${META_BASE}/${path}?fields=${fields}&access_token=${enc(token)}&limit=200`;\n if (extra) url += `&${extra}`;\n\n const res = await fetch(url);\n if (!res.ok) {\n const body = await res.text();\n throw new Error(parseMetaError(body, path, res.status));\n }\n const page1 = (await res.json()) as Record<string, unknown>;\n\n // Single-object response — return as-is\n if (!Array.isArray(page1.data)) return page1;\n\n // List response — paginate up to 20 pages\n const all = [...(page1.data as Record<string, unknown>[])];\n let next = (page1.paging as Record<string, unknown>)?.next as string | undefined;\n let pages = 1;\n while (next && pages < 20) {\n const r = await fetch(next);\n if (!r.ok) break;\n const j = (await r.json()) as Record<string, unknown>;\n if (Array.isArray(j.data)) all.push(...(j.data as Record<string, unknown>[]));\n next = (j.paging as Record<string, unknown>)?.next as string | undefined;\n pages++;\n }\n return { data: all };\n}\n\n/** POST to Meta Graph API with form encoding. */\nexport async function graphPost(\n path: string,\n params: Record<string, unknown>,\n token: string,\n): Promise<Record<string, unknown>> {\n const res = await fetch(`${META_BASE}/${path}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: formBody(params, token),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(parseMetaError(body, path, res.status));\n }\n return (await res.json()) as Record<string, unknown>;\n}\n\n/** DELETE from Meta Graph API. */\nexport async function graphDelete(\n path: string,\n token: string,\n): Promise<Record<string, unknown>> {\n const res = await fetch(`${META_BASE}/${path}?access_token=${enc(token)}`, {\n method: \"DELETE\",\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(parseMetaError(body, path, res.status));\n }\n return (await res.json()) as Record<string, unknown>;\n}\n\n// ============================================================================\n// CREDENTIALS\n// ============================================================================\n\nconst TOKEN_EXPIRY_WARNING_DAYS = 7;\n\nexport async function getIntegration(sb: SupabaseClient, storeId: string): Promise<MetaIntegration> {\n const { data, error } = await sb\n .from(\"meta_integrations\")\n .select(\"access_token_encrypted, ad_account_id, business_id, page_id, pixel_id, instagram_business_id, token_expires_at, status\")\n .eq(\"store_id\", storeId)\n .limit(1)\n .single();\n if (error || !data?.access_token_encrypted || !data?.ad_account_id) {\n throw new Error(\"Meta not connected. Go to Settings → Meta Connection to link your account.\");\n }\n\n if (data.token_expires_at) {\n const expiresAt = new Date(data.token_expires_at);\n const daysLeft = (expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24);\n\n if (daysLeft <= 0) {\n // Token expired — update status so CRM shows reconnect prompt\n if (data.status !== \"token_expired\") {\n await sb.from(\"meta_integrations\")\n .update({ status: \"token_expired\", updated_at: new Date().toISOString() })\n .eq(\"store_id\", storeId);\n }\n throw new Error(`Meta access token expired. Reconnect in CRM Settings → Meta Connection.`);\n }\n\n if (daysLeft <= TOKEN_EXPIRY_WARNING_DAYS && data.status !== \"expiring_soon\") {\n // Proactively mark as expiring so CRM can show warning banner\n await sb.from(\"meta_integrations\")\n .update({ status: \"expiring_soon\", updated_at: new Date().toISOString() })\n .eq(\"store_id\", storeId);\n }\n }\n\n return {\n accessToken: data.access_token_encrypted,\n adAccountId: data.ad_account_id,\n businessId: data.business_id || null,\n pageId: data.page_id || null,\n pixelId: data.pixel_id || null,\n instagramAccountId: data.instagram_business_id || null,\n tokenExpiresAt: data.token_expires_at || null,\n };\n}\n\n/** Page Access Token cache (user token → page token). Avoids re-fetching every call. */\nconst pageTokenCache = new Map<string, { token: string; fetchedAt: number }>();\n\n/**\n * Exchange user access token for a Page Access Token.\n * Required for lead forms, page-level APIs, and some creative operations.\n * Cached for 5 minutes.\n */\nexport async function getPageToken(int: MetaIntegration): Promise<string> {\n if (!int.pageId) throw new Error(\"No Facebook Page linked. Add page_id in Meta Connection settings.\");\n\n const cacheKey = `${int.pageId}:${int.accessToken.slice(-10)}`;\n const cached = pageTokenCache.get(cacheKey);\n if (cached && Date.now() - cached.fetchedAt < 300_000) return cached.token;\n\n const result = await graphGet(int.pageId, \"access_token\", int.accessToken);\n const pageToken = result.access_token as string;\n if (!pageToken) throw new Error(\"Could not get Page Access Token. Ensure your token has pages_manage_ads permission.\");\n\n pageTokenCache.set(cacheKey, { token: pageToken, fetchedAt: Date.now() });\n // Evict old entries\n if (pageTokenCache.size > 20) pageTokenCache.delete([...pageTokenCache.keys()][0]);\n\n return pageToken;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
type Result = {
|
|
3
|
+
success: boolean;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
error?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
export declare function handlePhone(sb: SupabaseClient, args: Record<string, unknown>, storeId?: string): Promise<Result>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// server/handlers/phone.ts — RingCentral integration: calls, SMS, voicemail
|
|
2
|
+
|
|
3
|
+
export async function handlePhone(sb, args, storeId) {
|
|
4
|
+
const sid = storeId;
|
|
5
|
+
switch (args.action) {
|
|
6
|
+
// ---- STATUS: get RingCentral integration status ----
|
|
7
|
+
case "status":
|
|
8
|
+
{
|
|
9
|
+
const {
|
|
10
|
+
data,
|
|
11
|
+
error
|
|
12
|
+
} = await sb.from("rc_integrations").select("id, status, main_number, webhook_status, sync_enabled, last_synced_at, sync_error, created_at").eq("store_id", sid).maybeSingle();
|
|
13
|
+
if (error) return {
|
|
14
|
+
success: false,
|
|
15
|
+
error: error.message
|
|
16
|
+
};
|
|
17
|
+
if (!data) return {
|
|
18
|
+
success: true,
|
|
19
|
+
data: {
|
|
20
|
+
connected: false,
|
|
21
|
+
message: "RingCentral not configured"
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
data: {
|
|
27
|
+
connected: data.status === "connected",
|
|
28
|
+
...data
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---- EXTENSIONS: list phone extensions ----
|
|
34
|
+
case "extensions":
|
|
35
|
+
{
|
|
36
|
+
const {
|
|
37
|
+
data,
|
|
38
|
+
error
|
|
39
|
+
} = await sb.from("rc_extensions").select("id, rc_extension_id, extension_number, name, type, status, email, presence_status, telephony_status, dnd_status").eq("store_id", sid).order("extension_number", {
|
|
40
|
+
ascending: true
|
|
41
|
+
});
|
|
42
|
+
return error ? {
|
|
43
|
+
success: false,
|
|
44
|
+
error: error.message
|
|
45
|
+
} : {
|
|
46
|
+
success: true,
|
|
47
|
+
count: data?.length,
|
|
48
|
+
data
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---- CALL_LOGS: list call logs ----
|
|
53
|
+
case "call_logs":
|
|
54
|
+
{
|
|
55
|
+
let q = sb.from("rc_call_logs").select("id, rc_call_id, direction, type, result, from_number, from_name, to_number, to_name, start_time, duration, recording_url, customer_id, created_at").eq("store_id", sid).order("start_time", {
|
|
56
|
+
ascending: false
|
|
57
|
+
});
|
|
58
|
+
if (args.direction) q = q.eq("direction", args.direction);
|
|
59
|
+
if (args.result) q = q.eq("result", args.result);
|
|
60
|
+
if (args.customer_id) q = q.eq("customer_id", args.customer_id);
|
|
61
|
+
if (args.from_number) q = q.eq("from_number", args.from_number);
|
|
62
|
+
if (args.to_number) q = q.eq("to_number", args.to_number);
|
|
63
|
+
q = q.limit(args.limit || 50);
|
|
64
|
+
const {
|
|
65
|
+
data,
|
|
66
|
+
error
|
|
67
|
+
} = await q;
|
|
68
|
+
return error ? {
|
|
69
|
+
success: false,
|
|
70
|
+
error: error.message
|
|
71
|
+
} : {
|
|
72
|
+
success: true,
|
|
73
|
+
count: data?.length,
|
|
74
|
+
data
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---- SMS_MESSAGES: list SMS messages ----
|
|
79
|
+
case "sms_messages":
|
|
80
|
+
{
|
|
81
|
+
let q = sb.from("rc_sms_messages").select("id, rc_message_id, direction, body, from_number, to_numbers, delivery_status, read_status, customer_id, created_at").eq("store_id", sid).order("created_at", {
|
|
82
|
+
ascending: false
|
|
83
|
+
});
|
|
84
|
+
if (args.direction) q = q.eq("direction", args.direction);
|
|
85
|
+
if (args.customer_id) q = q.eq("customer_id", args.customer_id);
|
|
86
|
+
q = q.limit(args.limit || 50);
|
|
87
|
+
const {
|
|
88
|
+
data,
|
|
89
|
+
error
|
|
90
|
+
} = await q;
|
|
91
|
+
return error ? {
|
|
92
|
+
success: false,
|
|
93
|
+
error: error.message
|
|
94
|
+
} : {
|
|
95
|
+
success: true,
|
|
96
|
+
count: data?.length,
|
|
97
|
+
data
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---- SMS_TEMPLATES: list SMS templates ----
|
|
102
|
+
case "sms_templates":
|
|
103
|
+
{
|
|
104
|
+
const {
|
|
105
|
+
data,
|
|
106
|
+
error
|
|
107
|
+
} = await sb.from("rc_sms_templates").select("*").eq("store_id", sid).eq("is_active", true).order("usage_count", {
|
|
108
|
+
ascending: false
|
|
109
|
+
});
|
|
110
|
+
return error ? {
|
|
111
|
+
success: false,
|
|
112
|
+
error: error.message
|
|
113
|
+
} : {
|
|
114
|
+
success: true,
|
|
115
|
+
count: data?.length,
|
|
116
|
+
data
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---- VOICEMAILS: list voicemails ----
|
|
121
|
+
case "voicemails":
|
|
122
|
+
{
|
|
123
|
+
let q = sb.from("rc_voicemails").select("id, rc_message_id, from_number, from_name, to_extension_id, duration, transcription, read_status, customer_id, created_at").eq("store_id", sid).order("created_at", {
|
|
124
|
+
ascending: false
|
|
125
|
+
});
|
|
126
|
+
if (args.read_status) q = q.eq("read_status", args.read_status);
|
|
127
|
+
if (args.customer_id) q = q.eq("customer_id", args.customer_id);
|
|
128
|
+
q = q.limit(args.limit || 50);
|
|
129
|
+
const {
|
|
130
|
+
data,
|
|
131
|
+
error
|
|
132
|
+
} = await q;
|
|
133
|
+
return error ? {
|
|
134
|
+
success: false,
|
|
135
|
+
error: error.message
|
|
136
|
+
} : {
|
|
137
|
+
success: true,
|
|
138
|
+
count: data?.length,
|
|
139
|
+
data
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- CALL_ANALYTICS: get call analytics for a date range ----
|
|
144
|
+
case "call_analytics":
|
|
145
|
+
{
|
|
146
|
+
let q = sb.from("rc_call_analytics").select("*").eq("store_id", sid).order("date", {
|
|
147
|
+
ascending: false
|
|
148
|
+
});
|
|
149
|
+
if (args.start_date) q = q.gte("date", args.start_date);
|
|
150
|
+
if (args.end_date) q = q.lte("date", args.end_date);
|
|
151
|
+
if (args.extension_id) q = q.eq("extension_id", args.extension_id);
|
|
152
|
+
q = q.limit(args.limit || 30);
|
|
153
|
+
const {
|
|
154
|
+
data,
|
|
155
|
+
error
|
|
156
|
+
} = await q;
|
|
157
|
+
return error ? {
|
|
158
|
+
success: false,
|
|
159
|
+
error: error.message
|
|
160
|
+
} : {
|
|
161
|
+
success: true,
|
|
162
|
+
count: data?.length,
|
|
163
|
+
data
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---- MARK_READ: mark a voicemail as read ----
|
|
168
|
+
case "mark_read":
|
|
169
|
+
{
|
|
170
|
+
const vmId = args.voicemail_id;
|
|
171
|
+
if (!vmId) return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: "voicemail_id is required"
|
|
174
|
+
};
|
|
175
|
+
const {
|
|
176
|
+
data,
|
|
177
|
+
error
|
|
178
|
+
} = await sb.from("rc_voicemails").update({
|
|
179
|
+
read_status: "Read",
|
|
180
|
+
updated_at: new Date().toISOString()
|
|
181
|
+
}).eq("id", vmId).eq("store_id", sid).select().single();
|
|
182
|
+
return error ? {
|
|
183
|
+
success: false,
|
|
184
|
+
error: error.message
|
|
185
|
+
} : {
|
|
186
|
+
success: true,
|
|
187
|
+
data
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
default:
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: `Unknown phone action: ${args.action}. Valid: status, extensions, call_logs, sms_messages, sms_templates, voicemails, call_analytics, mark_read`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=phone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone.js","names":["handlePhone","sb","args","storeId","sid","action","data","error","from","select","eq","maybeSingle","success","message","connected","status","order","ascending","count","length","q","direction","result","customer_id","from_number","to_number","limit","read_status","start_date","gte","end_date","lte","extension_id","vmId","voicemail_id","update","updated_at","Date","toISOString","single"],"sources":["../../../src/server/handlers/phone.ts"],"sourcesContent":["// server/handlers/phone.ts — RingCentral integration: calls, SMS, voicemail\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\ntype Result = { success: boolean; data?: unknown; error?: string; [key: string]: unknown };\n\nexport async function handlePhone(\n sb: SupabaseClient,\n args: Record<string, unknown>,\n storeId?: string,\n): Promise<Result> {\n const sid = storeId as string;\n\n switch (args.action) {\n // ---- STATUS: get RingCentral integration status ----\n case \"status\": {\n const { data, error } = await sb.from(\"rc_integrations\")\n .select(\"id, status, main_number, webhook_status, sync_enabled, last_synced_at, sync_error, created_at\")\n .eq(\"store_id\", sid)\n .maybeSingle();\n if (error) return { success: false, error: error.message };\n if (!data) return { success: true, data: { connected: false, message: \"RingCentral not configured\" } };\n return { success: true, data: { connected: data.status === \"connected\", ...data } };\n }\n\n // ---- EXTENSIONS: list phone extensions ----\n case \"extensions\": {\n const { data, error } = await sb.from(\"rc_extensions\")\n .select(\"id, rc_extension_id, extension_number, name, type, status, email, presence_status, telephony_status, dnd_status\")\n .eq(\"store_id\", sid)\n .order(\"extension_number\", { ascending: true });\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- CALL_LOGS: list call logs ----\n case \"call_logs\": {\n let q = sb.from(\"rc_call_logs\")\n .select(\"id, rc_call_id, direction, type, result, from_number, from_name, to_number, to_name, start_time, duration, recording_url, customer_id, created_at\")\n .eq(\"store_id\", sid)\n .order(\"start_time\", { ascending: false });\n if (args.direction) q = q.eq(\"direction\", args.direction as string);\n if (args.result) q = q.eq(\"result\", args.result as string);\n if (args.customer_id) q = q.eq(\"customer_id\", args.customer_id as string);\n if (args.from_number) q = q.eq(\"from_number\", args.from_number as string);\n if (args.to_number) q = q.eq(\"to_number\", args.to_number as string);\n q = q.limit(args.limit as number || 50);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- SMS_MESSAGES: list SMS messages ----\n case \"sms_messages\": {\n let q = sb.from(\"rc_sms_messages\")\n .select(\"id, rc_message_id, direction, body, from_number, to_numbers, delivery_status, read_status, customer_id, created_at\")\n .eq(\"store_id\", sid)\n .order(\"created_at\", { ascending: false });\n if (args.direction) q = q.eq(\"direction\", args.direction as string);\n if (args.customer_id) q = q.eq(\"customer_id\", args.customer_id as string);\n q = q.limit(args.limit as number || 50);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- SMS_TEMPLATES: list SMS templates ----\n case \"sms_templates\": {\n const { data, error } = await sb.from(\"rc_sms_templates\")\n .select(\"*\")\n .eq(\"store_id\", sid)\n .eq(\"is_active\", true)\n .order(\"usage_count\", { ascending: false });\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- VOICEMAILS: list voicemails ----\n case \"voicemails\": {\n let q = sb.from(\"rc_voicemails\")\n .select(\"id, rc_message_id, from_number, from_name, to_extension_id, duration, transcription, read_status, customer_id, created_at\")\n .eq(\"store_id\", sid)\n .order(\"created_at\", { ascending: false });\n if (args.read_status) q = q.eq(\"read_status\", args.read_status as string);\n if (args.customer_id) q = q.eq(\"customer_id\", args.customer_id as string);\n q = q.limit(args.limit as number || 50);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- CALL_ANALYTICS: get call analytics for a date range ----\n case \"call_analytics\": {\n let q = sb.from(\"rc_call_analytics\")\n .select(\"*\")\n .eq(\"store_id\", sid)\n .order(\"date\", { ascending: false });\n if (args.start_date) q = q.gte(\"date\", args.start_date as string);\n if (args.end_date) q = q.lte(\"date\", args.end_date as string);\n if (args.extension_id) q = q.eq(\"extension_id\", args.extension_id as string);\n q = q.limit(args.limit as number || 30);\n const { data, error } = await q;\n return error ? { success: false, error: error.message } : { success: true, count: data?.length, data };\n }\n\n // ---- MARK_READ: mark a voicemail as read ----\n case \"mark_read\": {\n const vmId = args.voicemail_id as string;\n if (!vmId) return { success: false, error: \"voicemail_id is required\" };\n const { data, error } = await sb.from(\"rc_voicemails\")\n .update({ read_status: \"Read\", updated_at: new Date().toISOString() })\n .eq(\"id\", vmId)\n .eq(\"store_id\", sid)\n .select()\n .single();\n return error ? { success: false, error: error.message } : { success: true, data };\n }\n\n default:\n return { success: false, error: `Unknown phone action: ${args.action}. Valid: status, extensions, call_logs, sms_messages, sms_templates, voicemails, call_analytics, mark_read` };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
type Result = {
|
|
3
|
+
success: boolean;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
error?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
export declare function handlePipeline(sb: SupabaseClient, args: Record<string, unknown>, storeId?: string): Promise<Result>;
|
|
9
|
+
export {};
|