@oneclick.dev/cms-core-modules 0.0.124 → 0.0.126
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/dist/AppointmentListTable-CWzbwqR-.js +1 -0
- package/dist/AppointmentListTable-DoZuBstk.mjs +270 -0
- package/dist/ContentEditor-BlNbqkAz.js +324 -0
- package/dist/{ContentEditor-DX1bTQKs.mjs → ContentEditor-D754iLJS.mjs} +8074 -27026
- package/dist/{Detail-B7pe808q.mjs → Detail-BOxWQ1QQ.mjs} +2 -2
- package/dist/{Detail-B_Q7Qkuz.mjs → Detail-BfKW0uZE.mjs} +2 -2
- package/dist/Detail-DFVu2F38.mjs +11 -0
- package/dist/{Detail-t3tOcy3X.js → Detail-DJea_fCd.js} +1 -1
- package/dist/{Detail-CNiLIEh9.js → Detail-DPJacXfm.js} +1 -1
- package/dist/Detail-DSLZ7_bM.js +1 -0
- package/dist/{Detail-BESGmY7z.js → Detail-DUGThL9Y.js} +1 -1
- package/dist/{Detail-DNxAzeCs.mjs → Detail-D_LlEeHu.mjs} +2 -2
- package/dist/Overview-0hFXiuYo.js +1 -0
- package/dist/Overview-BTx5LF4J.js +1 -0
- package/dist/Overview-CCu2PHai.mjs +289 -0
- package/dist/Overview-D2u-leeO.js +1 -0
- package/dist/Overview-D_2wozCG.mjs +1733 -0
- package/dist/Overview-Dffo-zH4.mjs +11 -0
- package/dist/{TableCalculationWidget-CrqcLGfU.mjs → TableCalculationWidget-B_LFoOYm.mjs} +3 -3
- package/dist/{TableCalculationWidget-CkQPrVUd.js → TableCalculationWidget-CahOIcbk.js} +1 -1
- package/dist/TableDataWidget-BwqWFH4A.js +1 -0
- package/dist/TableDataWidget-RsVxcDsL.mjs +112 -0
- package/dist/TableOverviewCard-BPWCqo56.js +1 -0
- package/dist/{TableOverviewCard-Ci0d4o4w.mjs → TableOverviewCard-D667DDag.mjs} +113 -127
- package/dist/TableResultView.vue_vue_type_script_setup_true_lang-C3XiOXgb.js +1 -0
- package/dist/TableResultView.vue_vue_type_script_setup_true_lang-sp0TLio0.mjs +547 -0
- package/dist/{TableRowActionCard-BhdnnPaq.mjs → TableRowActionCard-BPYJ-Kjy.mjs} +95 -101
- package/dist/TableRowActionCard-Bnqj1YIe.js +1 -0
- package/dist/TableView-CwhZ9YN2.js +4 -0
- package/dist/TableView-DX9PKS8N.mjs +9673 -0
- package/dist/TemplateBuilder.vue_vue_type_script_setup_true_lang-BgDdzbLe.js +1 -0
- package/dist/TemplateBuilder.vue_vue_type_script_setup_true_lang-D85V4nrQ.mjs +2886 -0
- package/dist/cms-core-modules.css +1 -1
- package/dist/dateRangePresets-CaPVagHY.mjs +453 -0
- package/dist/dateRangePresets-gxQCiZ6c.js +1 -0
- package/dist/index-6hZ6MYY6.js +1 -0
- package/dist/index-BVmmicu3.mjs +14 -0
- package/dist/index-ChHD_1Cf.js +144 -0
- package/dist/index-CvV4nVWI.js +1 -0
- package/dist/index-DH2z0W_w.mjs +19012 -0
- package/dist/index-DZKzj48V.mjs +17 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.mjs +237 -103
- package/dist/orders-C_sz8J9b.js +1 -0
- package/dist/{orders-BfTLIm7U.mjs → orders-Cgdggzvx.mjs} +84 -84
- package/dist/server-handlers.cjs.js +1 -1
- package/dist/server-handlers.mjs +2055 -1123
- package/dist/src/appointments/chat-components/AppointmentListTable.vue.d.ts +3 -0
- package/dist/src/appointments/index.d.ts +93 -3
- package/dist/src/appointments/reservation-summary.d.ts +48 -0
- package/dist/src/appointments/server.d.ts +1 -0
- package/dist/src/appointments/tools.d.ts +93 -3
- package/dist/src/filezone/config.d.ts +31 -0
- package/dist/src/filezone/index.d.ts +43 -0
- package/dist/src/filezone/index.vue.d.ts +2 -0
- package/dist/src/filezone/metadata.d.ts +2 -0
- package/dist/src/filezone/pages/Overview.vue.d.ts +4 -0
- package/dist/src/filezone/permissions.d.ts +5 -0
- package/dist/src/filezone/routes.d.ts +5 -0
- package/dist/src/filezone/server.d.ts +36 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/mailTemplates/builder-types.d.ts +40 -0
- package/dist/src/mailTemplates/components/BlockRenderer.vue.d.ts +24 -0
- package/dist/src/mailTemplates/components/DesignPanel.vue.d.ts +13 -0
- package/dist/src/mailTemplates/components/EmailCanvas.vue.d.ts +44 -0
- package/dist/src/mailTemplates/components/RichTextEditor.vue.d.ts +10 -0
- package/dist/src/mailTemplates/components/RightSidebar.vue.d.ts +18 -0
- package/dist/src/mailTemplates/components/SettingsPanel.vue.d.ts +15 -0
- package/dist/src/mailTemplates/components/TemplateBuilder.vue.d.ts +2 -0
- package/dist/src/mailTemplates/components/TemplateList.vue.d.ts +19 -1
- package/dist/src/mailTemplates/components/rich-text.d.ts +6 -0
- package/dist/src/mailTemplates/config.d.ts +10 -4
- package/dist/src/mailTemplates/defaults.d.ts +10 -0
- package/dist/src/mailTemplates/editor-state.d.ts +21 -0
- package/dist/src/mailTemplates/index.d.ts +11 -5
- package/dist/src/mailTemplates/index.vue.d.ts +2 -4
- package/dist/src/mailTemplates/metadata.d.ts +1 -1
- package/dist/src/mailTemplates/pages/Detail.vue.d.ts +1 -13
- package/dist/src/mailTemplates/pages/Overview.vue.d.ts +1 -13
- package/dist/src/mailTemplates/server.d.ts +23 -0
- package/dist/src/server-handlers.d.ts +4 -0
- package/dist/src/table/badge-filter-utils.d.ts +8 -0
- package/dist/src/table/chat-components/TableOverviewCard.vue.d.ts +2 -7
- package/dist/src/table/chat-components/TableResultView.vue.d.ts +3 -7
- package/dist/src/table/components/TableDisplayCell.vue.d.ts +8 -0
- package/dist/src/table/components/columns/BadgeColumnSettings.vue.d.ts +9 -0
- package/dist/src/table/components/columns/ColumnColorSelect.vue.d.ts +11 -0
- package/dist/src/table/components/columns/ColumnDisplaySettings.vue.d.ts +9 -0
- package/dist/src/table/components/columns/ColumnTypeSelect.vue.d.ts +11 -0
- package/dist/src/table/components/columns/ColumnValueSourceInput.vue.d.ts +15 -0
- package/dist/src/table/components/columns/DialogTableColumnItem.vue.d.ts +8 -11
- package/dist/src/table/components/columns/ProgressColumnSettings.vue.d.ts +8 -0
- package/dist/src/table/components/stats/StatDialog.vue.d.ts +6 -10
- package/dist/src/table/components/stats/Stats.vue.d.ts +9 -15
- package/dist/src/table/composables/useTableClock.d.ts +4 -0
- package/dist/src/table/display-utils.d.ts +35 -0
- package/dist/src/table/row-value.d.ts +1 -0
- package/dist/src/table/stat-utils.d.ts +9 -0
- package/dist/src/table/tool-utils.d.ts +1 -1
- package/dist/src/table/types.d.ts +54 -1
- package/dist/src/whatsappBusiness/analytics.d.ts +4 -0
- package/dist/src/whatsappBusiness/components/AccountPanel.vue.d.ts +2 -0
- package/dist/src/whatsappBusiness/components/InsightsPanel.vue.d.ts +5 -0
- package/dist/src/whatsappBusiness/components/TemplateDialog.vue.d.ts +14 -0
- package/dist/src/whatsappBusiness/components/TemplatesPanel.vue.d.ts +5 -0
- package/dist/src/whatsappBusiness/components/presentation.d.ts +4 -0
- package/dist/src/whatsappBusiness/config.d.ts +51 -0
- package/dist/src/whatsappBusiness/connection.d.ts +77 -0
- package/dist/src/whatsappBusiness/index.d.ts +63 -0
- package/dist/src/whatsappBusiness/index.vue.d.ts +5 -0
- package/dist/src/whatsappBusiness/metadata.d.ts +2 -0
- package/dist/src/whatsappBusiness/pages/Overview.vue.d.ts +2 -0
- package/dist/src/whatsappBusiness/permissions.d.ts +5 -0
- package/dist/src/whatsappBusiness/provider.d.ts +21 -0
- package/dist/src/whatsappBusiness/routes.d.ts +5 -0
- package/dist/src/whatsappBusiness/server.d.ts +14 -0
- package/dist/src/whatsappBusiness/templates.d.ts +94 -0
- package/dist/src/whatsappBusiness/types.d.ts +162 -0
- package/dist/{tool-sandbox-DoF1PsyS.js → tool-sandbox-BSkKdH7V.js} +1 -1
- package/dist/{tool-sandbox-DdkMLkDK.mjs → tool-sandbox-DU9b1B7v.mjs} +1 -1
- package/dist/{types-ibC3-e1N.mjs → types-B5MifmeF.mjs} +339 -273
- package/dist/types-D5dgTskW.js +1 -0
- package/dist/{useTableAgentTools-D5mzGf3A.js → useTableAgentTools-BrUot49I.js} +1 -1
- package/dist/{useTableAgentTools-DcN3kYRL.mjs → useTableAgentTools-r5InEI4p.mjs} +8 -8
- package/dist/{vee-validate-zod-BQElPD0m.js → vee-validate-zod-CWZwNTWE.js} +1 -1
- package/dist/{vee-validate-zod-CN16K2x9.mjs → vee-validate-zod-D0WaDgDZ.mjs} +1 -1
- package/package.json +2 -2
- package/src/appointments/tools.ts +52 -2
- package/dist/AppointmentListTable-DZJNmTMb.mjs +0 -177
- package/dist/AppointmentListTable-Dieu9US_.js +0 -1
- package/dist/ContentEditor-BcIUumah.js +0 -467
- package/dist/Detail-Byk781b4.mjs +0 -26
- package/dist/Detail-mvSGbnFU.js +0 -1
- package/dist/Overview-BDlBCUI_.js +0 -1
- package/dist/Overview-Su8ZUbMs.mjs +0 -26
- package/dist/TableDataWidget-7qvC3Egd.js +0 -1
- package/dist/TableDataWidget-DMTSoGRO.mjs +0 -121
- package/dist/TableOverviewCard-AFq-puJs.js +0 -1
- package/dist/TableResultView.vue_vue_type_script_setup_true_lang-CBn-Oxl9.mjs +0 -534
- package/dist/TableResultView.vue_vue_type_script_setup_true_lang-C_Lv-SmJ.js +0 -1
- package/dist/TableRowActionCard-IzB6tNN-.js +0 -1
- package/dist/TableView-6mdrL3zx.js +0 -4
- package/dist/TableView-Du5nOE6R.mjs +0 -8228
- package/dist/TemplateList.vue_vue_type_script_setup_true_lang-B_AtWnHa.js +0 -1
- package/dist/TemplateList.vue_vue_type_script_setup_true_lang-Cmovy7kk.mjs +0 -40
- package/dist/dateRangePresets-B-l5b_wR.mjs +0 -48
- package/dist/dateRangePresets-CPWqst_x.js +0 -1
- package/dist/orders-DUa2PVoa.js +0 -1
- package/dist/types-BMraG1kN.js +0 -1
package/dist/server-handlers.mjs
CHANGED
|
@@ -1,123 +1,219 @@
|
|
|
1
|
-
import { createRouter as
|
|
1
|
+
import { createRouter as J, defineEventHandler as b, createError as g, getRouterParam as U, getQuery as v, readBody as j, setResponseStatus as os, setHeader as se, setResponseHeader as ns } from "h3";
|
|
2
2
|
import "vue";
|
|
3
|
-
import { s as
|
|
4
|
-
function
|
|
5
|
-
const { initFirebase: t, normalizeTimestamps: s } = e,
|
|
6
|
-
async function
|
|
7
|
-
const { supabase:
|
|
8
|
-
if (
|
|
9
|
-
throw
|
|
10
|
-
const
|
|
3
|
+
import { s as x, o as O, b as _e, a as it, e as Ke, d as rs, l as ke } from "./types-B5MifmeF.mjs";
|
|
4
|
+
function Ma(e) {
|
|
5
|
+
const { initFirebase: t, normalizeTimestamps: s } = e, r = J();
|
|
6
|
+
async function n(a) {
|
|
7
|
+
const { supabase: c, instanceId: i } = a.context.module, { data: o, error: d } = await c.from("project_modules").select("config").eq("id", i).single();
|
|
8
|
+
if (d || !o?.config)
|
|
9
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load module config." });
|
|
10
|
+
const u = o.config, l = u.project, f = u.productCollection || "products";
|
|
11
11
|
if (!l)
|
|
12
|
-
throw
|
|
12
|
+
throw g({ statusCode: 400, statusMessage: "Products module has no Firebase integration configured." });
|
|
13
13
|
return { firebase: await t(a, l), collection: f };
|
|
14
14
|
}
|
|
15
|
-
return
|
|
15
|
+
return r.get("/products", b(async (a) => {
|
|
16
16
|
try {
|
|
17
|
-
const { firebase:
|
|
18
|
-
return (await
|
|
19
|
-
id:
|
|
20
|
-
...
|
|
17
|
+
const { firebase: c, collection: i } = await n(a);
|
|
18
|
+
return (await c.firestore().collection(i).get()).docs.map((d) => s({
|
|
19
|
+
id: d.id,
|
|
20
|
+
...d.data()
|
|
21
21
|
}));
|
|
22
|
-
} catch (
|
|
23
|
-
throw console.error("Products handler — list error:",
|
|
24
|
-
statusCode:
|
|
25
|
-
statusMessage:
|
|
22
|
+
} catch (c) {
|
|
23
|
+
throw console.error("Products handler — list error:", c), g({
|
|
24
|
+
statusCode: c.statusCode || 500,
|
|
25
|
+
statusMessage: c.statusMessage || "Failed to list products"
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
-
})),
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
throw
|
|
28
|
+
})), r.get("/products/:productId", b(async (a) => {
|
|
29
|
+
const c = U(a, "productId");
|
|
30
|
+
if (!c)
|
|
31
|
+
throw g({ statusCode: 400, statusMessage: "Product ID is required." });
|
|
32
32
|
try {
|
|
33
|
-
const { firebase:
|
|
34
|
-
if (!
|
|
35
|
-
throw
|
|
33
|
+
const { firebase: i, collection: o } = await n(a), d = await i.firestore().collection(o).doc(c).get();
|
|
34
|
+
if (!d.exists)
|
|
35
|
+
throw g({ statusCode: 404, statusMessage: "Product not found." });
|
|
36
36
|
return s({
|
|
37
|
-
id:
|
|
38
|
-
...
|
|
37
|
+
id: d.id,
|
|
38
|
+
...d.data()
|
|
39
39
|
});
|
|
40
|
-
} catch (
|
|
41
|
-
throw console.error("Products handler — get error:",
|
|
42
|
-
statusCode:
|
|
43
|
-
statusMessage:
|
|
40
|
+
} catch (i) {
|
|
41
|
+
throw console.error("Products handler — get error:", i), g({
|
|
42
|
+
statusCode: i.statusCode || 500,
|
|
43
|
+
statusMessage: i.statusMessage || "Failed to get product"
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
})),
|
|
47
|
-
const
|
|
46
|
+
})), r.get("/empty-stock", b(async (a) => {
|
|
47
|
+
const i = v(a).category;
|
|
48
48
|
try {
|
|
49
|
-
const { firebase: o, collection:
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
const f = (await
|
|
53
|
-
id:
|
|
54
|
-
...
|
|
49
|
+
const { firebase: o, collection: d } = await n(a);
|
|
50
|
+
let u = o.firestore().collection(d).where("stock", "==", 0);
|
|
51
|
+
i && (u = u.where("collections", "array-contains", i));
|
|
52
|
+
const f = (await u.get()).docs.map((p) => s({
|
|
53
|
+
id: p.id,
|
|
54
|
+
...p.data()
|
|
55
55
|
}));
|
|
56
56
|
return {
|
|
57
57
|
count: f.length,
|
|
58
|
-
products: f.map((
|
|
59
|
-
id:
|
|
60
|
-
title:
|
|
61
|
-
slug:
|
|
62
|
-
stock:
|
|
63
|
-
price:
|
|
64
|
-
currency:
|
|
65
|
-
status:
|
|
58
|
+
products: f.map((p) => ({
|
|
59
|
+
id: p.id,
|
|
60
|
+
title: p.title,
|
|
61
|
+
slug: p.slug,
|
|
62
|
+
stock: p.stock,
|
|
63
|
+
price: p.price,
|
|
64
|
+
currency: p.currency,
|
|
65
|
+
status: p.status
|
|
66
66
|
}))
|
|
67
67
|
};
|
|
68
68
|
} catch (o) {
|
|
69
|
-
throw console.error("Products handler — empty-stock error:", o),
|
|
69
|
+
throw console.error("Products handler — empty-stock error:", o), g({
|
|
70
70
|
statusCode: o.statusCode || 500,
|
|
71
71
|
statusMessage: o.statusMessage || "Failed to query empty stock"
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
})),
|
|
74
|
+
})), r.handler;
|
|
75
|
+
}
|
|
76
|
+
const ct = 500, is = ["confirmed", "pending", "cancelled", "all"], cs = ["active", "approved", "needs_approval", "cancelled", "rejected", "all"];
|
|
77
|
+
function ne(e) {
|
|
78
|
+
throw g({ statusCode: 400, statusMessage: e });
|
|
79
|
+
}
|
|
80
|
+
function Ae(e, t, s) {
|
|
81
|
+
const r = e[t];
|
|
82
|
+
return r === void 0 && s !== void 0 ? s : ((typeof r != "string" || !r.trim()) && ne(`${t} must be a non-empty string.`), r.trim());
|
|
83
|
+
}
|
|
84
|
+
function dt(e, t, s, r, n = Number.MAX_SAFE_INTEGER) {
|
|
85
|
+
if (e[t] === void 0) return s;
|
|
86
|
+
const a = Ae(e, t), c = Number(a);
|
|
87
|
+
return (!/^\d+$/.test(a) || !Number.isSafeInteger(c) || c < r || c > n) && ne(`${t} must be an integer between ${r} and ${n}.`), c;
|
|
88
|
+
}
|
|
89
|
+
function q(e) {
|
|
90
|
+
return typeof e == "string" ? e : null;
|
|
91
|
+
}
|
|
92
|
+
function ds(e) {
|
|
93
|
+
if (typeof e != "number" && (typeof e != "string" || !e.trim())) return null;
|
|
94
|
+
const t = Number(e);
|
|
95
|
+
return Number.isFinite(t) ? t : null;
|
|
96
|
+
}
|
|
97
|
+
function ut(e, t, s) {
|
|
98
|
+
const r = { yesterday: -1, today: 0, tomorrow: 1 };
|
|
99
|
+
if (Object.prototype.hasOwnProperty.call(r, e)) {
|
|
100
|
+
const a = /* @__PURE__ */ new Date(`${t}T00:00:00.000Z`);
|
|
101
|
+
return a.setUTCDate(a.getUTCDate() + r[e]), a.toISOString().slice(0, 10);
|
|
102
|
+
}
|
|
103
|
+
/^\d{4}-\d{2}-\d{2}$/.test(e) || ne(`${s} must be YYYY-MM-DD, today, tomorrow, or yesterday.`);
|
|
104
|
+
const n = /* @__PURE__ */ new Date(`${e}T00:00:00.000Z`);
|
|
105
|
+
return (!Number.isFinite(n.getTime()) || n.toISOString().slice(0, 10) !== e) && ne(`${s} must be a valid calendar date.`), e;
|
|
106
|
+
}
|
|
107
|
+
async function us(e, t, s, r, n = /* @__PURE__ */ new Date()) {
|
|
108
|
+
let a;
|
|
109
|
+
try {
|
|
110
|
+
const I = new Intl.DateTimeFormat("en-US", { timeZone: r, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(n), M = (D) => I.find((_) => _.type === D).value;
|
|
111
|
+
a = `${M("year")}-${M("month")}-${M("day")}`;
|
|
112
|
+
} catch {
|
|
113
|
+
ne("The project timezone is invalid.");
|
|
114
|
+
}
|
|
115
|
+
const c = ut(Ae(s, "startDate"), a, "startDate"), i = ut(Ae(s, "endDate", c), a, "endDate");
|
|
116
|
+
i < c && ne("endDate must be on or after startDate.");
|
|
117
|
+
const o = s.agendaId === void 0 ? null : Ae(s, "agendaId"), d = Ae(s, "orderStatus", "confirmed"), u = Ae(s, "reservationStatus", "active");
|
|
118
|
+
is.includes(d) || ne("Invalid orderStatus."), cs.includes(u) || ne("Invalid reservationStatus.");
|
|
119
|
+
const l = dt(s, "limit", 100, 1, 200), f = dt(s, "offset", 0, 0);
|
|
120
|
+
let p = e.collection(t);
|
|
121
|
+
o && (p = p.where("agendaId", "==", o)), d !== "all" && (p = p.where("status", "==", d)), p = p.orderBy("__name__").select("status", "agendaId", "reservations", "customerInfo").limit(ct);
|
|
122
|
+
const m = { reservationCount: 0, orderCount: 0, spotCount: 0 }, h = /* @__PURE__ */ new Map(), y = [];
|
|
123
|
+
let w;
|
|
124
|
+
for (; ; ) {
|
|
125
|
+
const I = await (w ? p.startAfter(w) : p).get();
|
|
126
|
+
for (const M of I.docs) {
|
|
127
|
+
const D = M.data(), _ = /* @__PURE__ */ new Set();
|
|
128
|
+
for (const T of Array.isArray(D.reservations) ? D.reservations : []) {
|
|
129
|
+
if (!T || typeof T.date != "string") continue;
|
|
130
|
+
const E = T.date;
|
|
131
|
+
if (E < c || E > i || u === "active" && ["cancelled", "rejected"].includes(T.status) || u !== "active" && u !== "all" && T.status !== u) continue;
|
|
132
|
+
const V = T.spots == null ? 1 : Number(T.spots), P = Number.isFinite(V) && V >= 0 ? V : 1, K = h.get(E) || { reservationCount: 0, orderCount: 0, spotCount: 0 };
|
|
133
|
+
K.reservationCount++, K.spotCount += P, h.set(E, K), _.add(E), m.reservationCount >= f && y.length < l && y.push({
|
|
134
|
+
orderId: M.id,
|
|
135
|
+
reservationId: q(T.id),
|
|
136
|
+
agendaId: q(D.agendaId),
|
|
137
|
+
date: E,
|
|
138
|
+
startTime: q(T.timeslot?.startTime),
|
|
139
|
+
endTime: q(T.timeslot?.endTime),
|
|
140
|
+
spots: P,
|
|
141
|
+
status: q(D.status),
|
|
142
|
+
reservationStatus: q(T.status),
|
|
143
|
+
resourceId: q(T.resourceId) ?? q(T.resource?.id),
|
|
144
|
+
resourceName: q(T.resource?.name) ?? q(T.resourceName),
|
|
145
|
+
customerInfo: {
|
|
146
|
+
firstName: q(D.customerInfo?.firstName),
|
|
147
|
+
lastName: q(D.customerInfo?.lastName),
|
|
148
|
+
email: q(D.customerInfo?.email)
|
|
149
|
+
},
|
|
150
|
+
reservationPrice: ds(T.totalPrice)
|
|
151
|
+
}), m.reservationCount++, m.spotCount += P;
|
|
152
|
+
}
|
|
153
|
+
_.size && m.orderCount++;
|
|
154
|
+
for (const T of _) h.get(T).orderCount++;
|
|
155
|
+
}
|
|
156
|
+
if (I.docs.length < ct) break;
|
|
157
|
+
w = I.docs[I.docs.length - 1];
|
|
158
|
+
}
|
|
159
|
+
const C = f + y.length, A = C < m.reservationCount;
|
|
160
|
+
return {
|
|
161
|
+
startDate: c,
|
|
162
|
+
endDate: i,
|
|
163
|
+
timeZone: r,
|
|
164
|
+
filters: { agendaId: o, orderStatus: d, reservationStatus: u },
|
|
165
|
+
...m,
|
|
166
|
+
byDate: [...h].sort(([I], [M]) => I.localeCompare(M)).map(([I, M]) => ({ date: I, ...M })),
|
|
167
|
+
appointments: y,
|
|
168
|
+
pagination: { offset: f, limit: l, returnedCount: y.length, hasMore: A, nextOffset: A ? C : null }
|
|
169
|
+
};
|
|
75
170
|
}
|
|
76
|
-
function
|
|
77
|
-
const { initFirebase: t, normalizeTimestamps: s, deleteField:
|
|
171
|
+
function Sa(e) {
|
|
172
|
+
const { initFirebase: t, normalizeTimestamps: s, deleteField: r } = e, n = J(), a = "privateSettings", c = "refundFlow", i = "metadataSchema";
|
|
78
173
|
async function o(m) {
|
|
79
|
-
const { supabase: h, instanceId:
|
|
174
|
+
const { supabase: h, instanceId: y } = m.context.module, { data: w, error: C } = await h.from("project_modules").select("config, project_id").eq("id", y).single();
|
|
80
175
|
if (C || !w?.config)
|
|
81
|
-
throw
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
84
|
-
throw
|
|
85
|
-
return { firebase: await t(m,
|
|
176
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load module config." });
|
|
177
|
+
const A = w.config, I = A.project, M = A.reservationsCollection || "bookings_orders", D = A.agendaCollection || "agendas";
|
|
178
|
+
if (!I)
|
|
179
|
+
throw g({ statusCode: 400, statusMessage: "Appointments module has no Firebase integration configured." });
|
|
180
|
+
return { firebase: await t(m, I), reservationsCollection: M, agendaCollection: D, projectId: w.project_id };
|
|
86
181
|
}
|
|
87
|
-
function
|
|
88
|
-
return (m.reservations || []).map((
|
|
182
|
+
function d(m) {
|
|
183
|
+
return (m.reservations || []).map((y) => ({
|
|
89
184
|
orderId: m.id,
|
|
90
|
-
|
|
185
|
+
agendaId: m.agendaId,
|
|
186
|
+
reservationId: y.id,
|
|
91
187
|
customerInfo: m.customerInfo,
|
|
92
|
-
date:
|
|
93
|
-
startTime:
|
|
94
|
-
endTime:
|
|
95
|
-
spots:
|
|
188
|
+
date: y.date,
|
|
189
|
+
startTime: y.timeslot?.startTime,
|
|
190
|
+
endTime: y.timeslot?.endTime,
|
|
191
|
+
spots: y.spots,
|
|
96
192
|
status: m.status,
|
|
97
|
-
reservationStatus:
|
|
98
|
-
resourceId:
|
|
99
|
-
reservationPrice:
|
|
100
|
-
reservationBasePrice:
|
|
101
|
-
reservationAddOnsPrice:
|
|
102
|
-
pricingOption:
|
|
193
|
+
reservationStatus: y.status,
|
|
194
|
+
resourceId: y.resourceId,
|
|
195
|
+
reservationPrice: y.totalPrice,
|
|
196
|
+
reservationBasePrice: y.basePrice,
|
|
197
|
+
reservationAddOnsPrice: y.addOnsPrice,
|
|
198
|
+
pricingOption: y.pricingOption,
|
|
103
199
|
amountDue: m.amountDue,
|
|
104
200
|
amountPaid: m.amountPaid,
|
|
105
201
|
createdAt: m.createdAt,
|
|
106
202
|
metadata: m.metadata || {},
|
|
107
|
-
reservationMetadata:
|
|
203
|
+
reservationMetadata: y.metadata || {}
|
|
108
204
|
}));
|
|
109
205
|
}
|
|
110
|
-
function
|
|
206
|
+
function u(m) {
|
|
111
207
|
return {
|
|
112
208
|
showAmount: m?.showAmount !== !1,
|
|
113
209
|
fields: Array.isArray(m?.fields) ? m.fields : []
|
|
114
210
|
};
|
|
115
211
|
}
|
|
116
212
|
function l() {
|
|
117
|
-
return
|
|
118
|
-
refundFlow:
|
|
119
|
-
refundFlowEnabled:
|
|
120
|
-
refundDialog:
|
|
213
|
+
return r ? {
|
|
214
|
+
refundFlow: r(),
|
|
215
|
+
refundFlowEnabled: r(),
|
|
216
|
+
refundDialog: r()
|
|
121
217
|
} : {
|
|
122
218
|
refundFlow: null,
|
|
123
219
|
refundFlowEnabled: null,
|
|
@@ -127,21 +223,21 @@ function Qt(e) {
|
|
|
127
223
|
function f(m) {
|
|
128
224
|
return Array.isArray(m) ? m : [];
|
|
129
225
|
}
|
|
130
|
-
function
|
|
131
|
-
return
|
|
132
|
-
metadataSchema:
|
|
226
|
+
function p() {
|
|
227
|
+
return r ? {
|
|
228
|
+
metadataSchema: r()
|
|
133
229
|
} : {
|
|
134
230
|
metadataSchema: null
|
|
135
231
|
};
|
|
136
232
|
}
|
|
137
|
-
return
|
|
233
|
+
return n.get("/agendas", b(async (m) => {
|
|
138
234
|
try {
|
|
139
|
-
const { firebase: h, agendaCollection:
|
|
140
|
-
const
|
|
235
|
+
const { firebase: h, agendaCollection: y } = await o(m), C = (await h.firestore().collection(y).get()).docs.map((A) => {
|
|
236
|
+
const I = A.data();
|
|
141
237
|
return {
|
|
142
|
-
id:
|
|
143
|
-
serviceName:
|
|
144
|
-
type:
|
|
238
|
+
id: A.id,
|
|
239
|
+
serviceName: I.serviceName || "",
|
|
240
|
+
type: I.type || "regular"
|
|
145
241
|
};
|
|
146
242
|
});
|
|
147
243
|
return {
|
|
@@ -149,253 +245,258 @@ function Qt(e) {
|
|
|
149
245
|
agendas: C
|
|
150
246
|
};
|
|
151
247
|
} catch (h) {
|
|
152
|
-
throw console.error("Appointments handler — list agendas error:", h),
|
|
248
|
+
throw console.error("Appointments handler — list agendas error:", h), g({
|
|
153
249
|
statusCode: h.statusCode || 500,
|
|
154
250
|
statusMessage: h.statusMessage || "Failed to list agendas"
|
|
155
251
|
});
|
|
156
252
|
}
|
|
157
|
-
})),
|
|
158
|
-
const h =
|
|
253
|
+
})), n.get("/agendas/:agendaId/opening-hours", b(async (m) => {
|
|
254
|
+
const h = U(m, "agendaId"), w = v(m).date;
|
|
159
255
|
if (!h)
|
|
160
|
-
throw
|
|
256
|
+
throw g({ statusCode: 400, statusMessage: "Agenda ID is required." });
|
|
161
257
|
if (!w)
|
|
162
|
-
throw
|
|
258
|
+
throw g({ statusCode: 400, statusMessage: "Date is required (YYYY-MM-DD)." });
|
|
163
259
|
try {
|
|
164
|
-
const { firebase: C, agendaCollection:
|
|
165
|
-
if (!
|
|
166
|
-
throw
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
return
|
|
260
|
+
const { firebase: C, agendaCollection: A } = await o(m), I = await C.firestore().collection(A).doc(h).get();
|
|
261
|
+
if (!I.exists)
|
|
262
|
+
throw g({ statusCode: 404, statusMessage: "Agenda not found." });
|
|
263
|
+
const M = I.data(), D = (M.resources || []).filter((P) => P.isActive), _ = M.exceptions || [], E = (/* @__PURE__ */ new Date(w + "T00:00:00")).getDay(), V = D.map((P) => {
|
|
264
|
+
const K = P.openingHours?.[E] || [], Q = _.find((N) => {
|
|
265
|
+
const de = w >= N.startDate && w <= N.endDate, Z = !N.resourceIds || N.resourceIds.length === 0 || N.resourceIds.includes(P.id);
|
|
266
|
+
return de && Z;
|
|
171
267
|
});
|
|
172
|
-
return
|
|
173
|
-
resourceId:
|
|
174
|
-
resourceName:
|
|
268
|
+
return Q ? {
|
|
269
|
+
resourceId: P.id,
|
|
270
|
+
resourceName: P.name,
|
|
175
271
|
date: w,
|
|
176
|
-
dayOfWeek:
|
|
177
|
-
isClosed:
|
|
178
|
-
hours:
|
|
179
|
-
start:
|
|
180
|
-
end:
|
|
272
|
+
dayOfWeek: E,
|
|
273
|
+
isClosed: Q.isClosed,
|
|
274
|
+
hours: Q.isClosed ? [] : (Q.timeslots || []).map((N) => ({
|
|
275
|
+
start: N.startTime,
|
|
276
|
+
end: N.endTime
|
|
181
277
|
})),
|
|
182
278
|
isException: !0
|
|
183
279
|
} : {
|
|
184
|
-
resourceId:
|
|
185
|
-
resourceName:
|
|
280
|
+
resourceId: P.id,
|
|
281
|
+
resourceName: P.name,
|
|
186
282
|
date: w,
|
|
187
|
-
dayOfWeek:
|
|
188
|
-
isClosed:
|
|
189
|
-
hours:
|
|
283
|
+
dayOfWeek: E,
|
|
284
|
+
isClosed: K.length === 0,
|
|
285
|
+
hours: K,
|
|
190
286
|
isException: !1
|
|
191
287
|
};
|
|
192
288
|
});
|
|
193
289
|
return {
|
|
194
290
|
agendaId: h,
|
|
195
|
-
serviceName:
|
|
291
|
+
serviceName: M.serviceName || "",
|
|
196
292
|
date: w,
|
|
197
|
-
resources:
|
|
293
|
+
resources: V
|
|
198
294
|
};
|
|
199
295
|
} catch (C) {
|
|
200
|
-
throw console.error("Appointments handler — opening hours error:", C),
|
|
296
|
+
throw console.error("Appointments handler — opening hours error:", C), g({
|
|
201
297
|
statusCode: C.statusCode || 500,
|
|
202
298
|
statusMessage: C.statusMessage || "Failed to get opening hours"
|
|
203
299
|
});
|
|
204
300
|
}
|
|
205
|
-
})),
|
|
206
|
-
const h =
|
|
301
|
+
})), n.get("/agendas/:agendaId/refund-flow", b(async (m) => {
|
|
302
|
+
const h = U(m, "agendaId");
|
|
207
303
|
if (!h)
|
|
208
|
-
throw
|
|
304
|
+
throw g({ statusCode: 400, statusMessage: "Agenda ID is required." });
|
|
209
305
|
try {
|
|
210
|
-
const { firebase:
|
|
211
|
-
|
|
212
|
-
|
|
306
|
+
const { firebase: y, agendaCollection: w } = await o(m), A = y.firestore().collection(w).doc(h), I = A.collection(a).doc(c), [M, D] = await Promise.all([
|
|
307
|
+
A.get(),
|
|
308
|
+
I.get()
|
|
213
309
|
]);
|
|
214
|
-
if (!
|
|
215
|
-
throw
|
|
216
|
-
const
|
|
310
|
+
if (!M.exists)
|
|
311
|
+
throw g({ statusCode: 404, statusMessage: "Agenda not found." });
|
|
312
|
+
const _ = D.exists ? D.data() || {} : {};
|
|
217
313
|
return {
|
|
218
|
-
refundFlow:
|
|
219
|
-
refundFlowEnabled:
|
|
220
|
-
refundDialog:
|
|
314
|
+
refundFlow: _.flow || {},
|
|
315
|
+
refundFlowEnabled: _.enabled !== !1,
|
|
316
|
+
refundDialog: u(_.dialog)
|
|
221
317
|
};
|
|
222
|
-
} catch (
|
|
223
|
-
throw console.error("Appointments handler — refund flow load error:",
|
|
224
|
-
statusCode:
|
|
225
|
-
statusMessage:
|
|
318
|
+
} catch (y) {
|
|
319
|
+
throw console.error("Appointments handler — refund flow load error:", y), g({
|
|
320
|
+
statusCode: y.statusCode || 500,
|
|
321
|
+
statusMessage: y.statusMessage || "Failed to load refund flow"
|
|
226
322
|
});
|
|
227
323
|
}
|
|
228
|
-
})),
|
|
229
|
-
const h =
|
|
324
|
+
})), n.patch("/agendas/:agendaId/refund-flow", b(async (m) => {
|
|
325
|
+
const h = U(m, "agendaId");
|
|
230
326
|
if (!h)
|
|
231
|
-
throw
|
|
327
|
+
throw g({ statusCode: 400, statusMessage: "Agenda ID is required." });
|
|
232
328
|
try {
|
|
233
|
-
const
|
|
234
|
-
if (!(await
|
|
235
|
-
throw
|
|
236
|
-
const
|
|
237
|
-
return
|
|
238
|
-
flow:
|
|
239
|
-
enabled:
|
|
240
|
-
dialog:
|
|
329
|
+
const y = await j(m), { firebase: w, agendaCollection: C } = await o(m), A = w.firestore(), I = A.collection(C).doc(h), M = I.collection(a).doc(c), D = u(y?.refundDialog);
|
|
330
|
+
if (!(await I.get()).exists)
|
|
331
|
+
throw g({ statusCode: 404, statusMessage: "Agenda not found." });
|
|
332
|
+
const T = A.batch();
|
|
333
|
+
return T.set(M, {
|
|
334
|
+
flow: y?.refundFlow || {},
|
|
335
|
+
enabled: y?.refundFlowEnabled !== !1,
|
|
336
|
+
dialog: D,
|
|
241
337
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
242
|
-
}, { merge: !0 }),
|
|
243
|
-
} catch (
|
|
244
|
-
throw console.error("Appointments handler — refund flow save error:",
|
|
245
|
-
statusCode:
|
|
246
|
-
statusMessage:
|
|
338
|
+
}, { merge: !0 }), T.set(I, l(), { merge: !0 }), await T.commit(), { success: !0 };
|
|
339
|
+
} catch (y) {
|
|
340
|
+
throw console.error("Appointments handler — refund flow save error:", y), g({
|
|
341
|
+
statusCode: y.statusCode || 500,
|
|
342
|
+
statusMessage: y.statusMessage || "Failed to save refund flow"
|
|
247
343
|
});
|
|
248
344
|
}
|
|
249
|
-
})),
|
|
250
|
-
const h =
|
|
345
|
+
})), n.get("/agendas/:agendaId/metadata-schema", b(async (m) => {
|
|
346
|
+
const h = U(m, "agendaId");
|
|
251
347
|
if (!h)
|
|
252
|
-
throw
|
|
348
|
+
throw g({ statusCode: 400, statusMessage: "Agenda ID is required." });
|
|
253
349
|
try {
|
|
254
|
-
const { firebase:
|
|
255
|
-
|
|
256
|
-
|
|
350
|
+
const { firebase: y, agendaCollection: w } = await o(m), A = y.firestore().collection(w).doc(h), I = A.collection(a).doc(i), [M, D] = await Promise.all([
|
|
351
|
+
A.get(),
|
|
352
|
+
I.get()
|
|
257
353
|
]);
|
|
258
|
-
if (!
|
|
259
|
-
throw
|
|
260
|
-
const
|
|
354
|
+
if (!M.exists)
|
|
355
|
+
throw g({ statusCode: 404, statusMessage: "Agenda not found." });
|
|
356
|
+
const _ = D.exists ? D.data() || {} : {};
|
|
261
357
|
return {
|
|
262
|
-
metadataSchema: f(
|
|
358
|
+
metadataSchema: f(_.schema)
|
|
263
359
|
};
|
|
264
|
-
} catch (
|
|
265
|
-
throw console.error("Appointments handler — metadata schema load error:",
|
|
266
|
-
statusCode:
|
|
267
|
-
statusMessage:
|
|
360
|
+
} catch (y) {
|
|
361
|
+
throw console.error("Appointments handler — metadata schema load error:", y), g({
|
|
362
|
+
statusCode: y.statusCode || 500,
|
|
363
|
+
statusMessage: y.statusMessage || "Failed to load metadata schema"
|
|
268
364
|
});
|
|
269
365
|
}
|
|
270
|
-
})),
|
|
271
|
-
const h =
|
|
366
|
+
})), n.patch("/agendas/:agendaId/metadata-schema", b(async (m) => {
|
|
367
|
+
const h = U(m, "agendaId");
|
|
272
368
|
if (!h)
|
|
273
|
-
throw
|
|
369
|
+
throw g({ statusCode: 400, statusMessage: "Agenda ID is required." });
|
|
274
370
|
try {
|
|
275
|
-
const
|
|
276
|
-
if (!(await
|
|
277
|
-
throw
|
|
278
|
-
const
|
|
279
|
-
return
|
|
280
|
-
schema: f(
|
|
371
|
+
const y = await j(m), { firebase: w, agendaCollection: C } = await o(m), A = w.firestore(), I = A.collection(C).doc(h), M = I.collection(a).doc(i);
|
|
372
|
+
if (!(await I.get()).exists)
|
|
373
|
+
throw g({ statusCode: 404, statusMessage: "Agenda not found." });
|
|
374
|
+
const _ = A.batch();
|
|
375
|
+
return _.set(M, {
|
|
376
|
+
schema: f(y?.metadataSchema),
|
|
281
377
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
|
-
}, { merge: !0 }),
|
|
283
|
-
} catch (
|
|
284
|
-
throw console.error("Appointments handler — metadata schema save error:",
|
|
285
|
-
statusCode:
|
|
286
|
-
statusMessage:
|
|
378
|
+
}, { merge: !0 }), _.set(I, p(), { merge: !0 }), await _.commit(), { success: !0 };
|
|
379
|
+
} catch (y) {
|
|
380
|
+
throw console.error("Appointments handler — metadata schema save error:", y), g({
|
|
381
|
+
statusCode: y.statusCode || 500,
|
|
382
|
+
statusMessage: y.statusMessage || "Failed to save metadata schema"
|
|
287
383
|
});
|
|
288
384
|
}
|
|
289
|
-
})),
|
|
290
|
-
const h
|
|
385
|
+
})), n.get("/appointments/summary", b(async (m) => {
|
|
386
|
+
const { firebase: h, reservationsCollection: y, projectId: w } = await o(m), { data: C, error: A } = await m.context.module.supabase.from("projects").select("timezone").eq("id", w).single();
|
|
387
|
+
if (A || !C)
|
|
388
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load the booking project timezone." });
|
|
389
|
+
return us(h.firestore(), y, v(m), C.timezone || "UTC");
|
|
390
|
+
})), n.get("/appointments", b(async (m) => {
|
|
391
|
+
const h = v(m), y = Math.min(Number(h.quantity) || 20, 100), w = h.status || "confirmed";
|
|
291
392
|
try {
|
|
292
|
-
const { firebase: C, reservationsCollection:
|
|
293
|
-
(
|
|
393
|
+
const { firebase: C, reservationsCollection: A } = await o(m), M = (await C.firestore().collection(A).orderBy("createdAt", "desc").where("status", "==", w).limit(y).get()).docs.flatMap(
|
|
394
|
+
(D) => d(s({ id: D.id, ...D.data() }))
|
|
294
395
|
);
|
|
295
396
|
return {
|
|
296
|
-
count:
|
|
297
|
-
appointments:
|
|
397
|
+
count: M.length,
|
|
398
|
+
appointments: M
|
|
298
399
|
};
|
|
299
400
|
} catch (C) {
|
|
300
|
-
throw console.error("Appointments handler — list error:", C),
|
|
401
|
+
throw console.error("Appointments handler — list error:", C), g({
|
|
301
402
|
statusCode: C.statusCode || 500,
|
|
302
403
|
statusMessage: C.statusMessage || "Failed to list appointments"
|
|
303
404
|
});
|
|
304
405
|
}
|
|
305
|
-
})),
|
|
306
|
-
const h =
|
|
406
|
+
})), n.get("/appointments/find", b(async (m) => {
|
|
407
|
+
const h = v(m), y = (h.name || "").toLowerCase().trim(), w = (h.email || "").toLowerCase().trim(), C = h.date;
|
|
307
408
|
try {
|
|
308
|
-
const { firebase:
|
|
309
|
-
let
|
|
310
|
-
w && (
|
|
311
|
-
let
|
|
312
|
-
(
|
|
409
|
+
const { firebase: A, reservationsCollection: I } = await o(m);
|
|
410
|
+
let M = A.firestore().collection(I);
|
|
411
|
+
w && (M = M.where("customerInfo.email", "==", w));
|
|
412
|
+
let _ = (await M.orderBy("createdAt", "desc").limit(200).get()).docs.flatMap(
|
|
413
|
+
(T) => d(s({ id: T.id, ...T.data() }))
|
|
313
414
|
);
|
|
314
|
-
return C && (
|
|
315
|
-
const
|
|
316
|
-
return
|
|
415
|
+
return C && (_ = _.filter((T) => T.date === C)), y && (_ = _.filter((T) => {
|
|
416
|
+
const E = (T.customerInfo?.firstName || "").toLowerCase(), V = (T.customerInfo?.lastName || "").toLowerCase();
|
|
417
|
+
return E.includes(y) || V.includes(y) || `${E} ${V}`.includes(y);
|
|
317
418
|
})), {
|
|
318
|
-
count:
|
|
319
|
-
appointments:
|
|
419
|
+
count: _.length,
|
|
420
|
+
appointments: _
|
|
320
421
|
};
|
|
321
|
-
} catch (
|
|
322
|
-
throw console.error("Appointments handler — find error:",
|
|
323
|
-
statusCode:
|
|
324
|
-
statusMessage:
|
|
422
|
+
} catch (A) {
|
|
423
|
+
throw console.error("Appointments handler — find error:", A), g({
|
|
424
|
+
statusCode: A.statusCode || 500,
|
|
425
|
+
statusMessage: A.statusMessage || "Failed to find appointments"
|
|
325
426
|
});
|
|
326
427
|
}
|
|
327
|
-
})),
|
|
328
|
-
const h =
|
|
428
|
+
})), n.get("/appointments/:appointmentId", b(async (m) => {
|
|
429
|
+
const h = U(m, "appointmentId");
|
|
329
430
|
if (!h)
|
|
330
|
-
throw
|
|
431
|
+
throw g({ statusCode: 400, statusMessage: "Appointment ID is required." });
|
|
331
432
|
try {
|
|
332
|
-
const { firebase:
|
|
433
|
+
const { firebase: y, reservationsCollection: w } = await o(m), C = await y.firestore().collection(w).doc(h).get();
|
|
333
434
|
if (!C.exists)
|
|
334
|
-
throw
|
|
335
|
-
const
|
|
435
|
+
throw g({ statusCode: 404, statusMessage: "Appointment not found." });
|
|
436
|
+
const A = s({ id: C.id, ...C.data() });
|
|
336
437
|
return {
|
|
337
|
-
...
|
|
338
|
-
reservations:
|
|
438
|
+
...A,
|
|
439
|
+
reservations: d(A)
|
|
339
440
|
};
|
|
340
|
-
} catch (
|
|
341
|
-
throw console.error("Appointments handler — get error:",
|
|
342
|
-
statusCode:
|
|
343
|
-
statusMessage:
|
|
441
|
+
} catch (y) {
|
|
442
|
+
throw console.error("Appointments handler — get error:", y), g({
|
|
443
|
+
statusCode: y.statusCode || 500,
|
|
444
|
+
statusMessage: y.statusMessage || "Failed to get appointment"
|
|
344
445
|
});
|
|
345
446
|
}
|
|
346
|
-
})),
|
|
447
|
+
})), n.handler;
|
|
347
448
|
}
|
|
348
|
-
const
|
|
449
|
+
const lt = "https://analyticsdata.googleapis.com/v1beta", ls = [
|
|
349
450
|
"https://www.googleapis.com/auth/analytics.readonly",
|
|
350
451
|
"https://www.googleapis.com/auth/webmasters.readonly"
|
|
351
|
-
],
|
|
352
|
-
function
|
|
353
|
-
const { decrypt: t, getGoogleAccessToken: s } = e,
|
|
354
|
-
async function
|
|
355
|
-
const { supabase:
|
|
452
|
+
], mt = "https://searchconsole.googleapis.com/webmasters/v3";
|
|
453
|
+
function Da(e) {
|
|
454
|
+
const { decrypt: t, getGoogleAccessToken: s } = e, r = J();
|
|
455
|
+
async function n(o) {
|
|
456
|
+
const { supabase: d, instanceId: u } = o.context.module, { data: l, error: f } = await d.from("project_modules").select("config").eq("id", u).single();
|
|
356
457
|
if (f || !l?.config)
|
|
357
|
-
throw
|
|
358
|
-
const
|
|
458
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load module config." });
|
|
459
|
+
const p = l.config, m = p.propertyId, h = p.serviceAccount, y = p.siteUrl || "";
|
|
359
460
|
if (!h)
|
|
360
|
-
throw
|
|
461
|
+
throw g({ statusCode: 400, statusMessage: "No Google Service Account configured for this module." });
|
|
361
462
|
if (!m)
|
|
362
|
-
throw
|
|
363
|
-
const { data: w, error: C } = await
|
|
364
|
-
let
|
|
365
|
-
if (C || !
|
|
366
|
-
const { data:
|
|
367
|
-
if (
|
|
368
|
-
throw
|
|
369
|
-
|
|
463
|
+
throw g({ statusCode: 400, statusMessage: "No GA4 Property ID configured for this module." });
|
|
464
|
+
const { data: w, error: C } = await d.from("integrations").select("config").eq("id", h).single();
|
|
465
|
+
let A = w?.config;
|
|
466
|
+
if (C || !A) {
|
|
467
|
+
const { data: T, error: E } = await d.from("agency_integrations").select("config").eq("id", h).single();
|
|
468
|
+
if (E || !T?.config)
|
|
469
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load Google Service Account credentials." });
|
|
470
|
+
A = T.config;
|
|
370
471
|
}
|
|
371
|
-
const
|
|
472
|
+
const I = t(A.clientEmail), M = t(A.privateKey), D = t(A.projectId);
|
|
372
473
|
return { accessToken: await s(
|
|
373
|
-
{ clientEmail:
|
|
374
|
-
|
|
375
|
-
), propertyId: m, siteUrl:
|
|
474
|
+
{ clientEmail: I, privateKey: M, projectId: D },
|
|
475
|
+
ls
|
|
476
|
+
), propertyId: m, siteUrl: y };
|
|
376
477
|
}
|
|
377
|
-
async function a(o,
|
|
378
|
-
const l = await fetch(`${
|
|
478
|
+
async function a(o, d, u) {
|
|
479
|
+
const l = await fetch(`${lt}/properties/${d}:runReport`, {
|
|
379
480
|
method: "POST",
|
|
380
481
|
headers: {
|
|
381
482
|
Authorization: `Bearer ${o}`,
|
|
382
483
|
"Content-Type": "application/json"
|
|
383
484
|
},
|
|
384
|
-
body: JSON.stringify(
|
|
485
|
+
body: JSON.stringify(u)
|
|
385
486
|
});
|
|
386
487
|
if (!l.ok) {
|
|
387
488
|
const f = await l.json().catch(() => ({}));
|
|
388
|
-
throw console.error("GA4 runReport failed:", f),
|
|
489
|
+
throw console.error("GA4 runReport failed:", f), g({
|
|
389
490
|
statusCode: l.status,
|
|
390
491
|
statusMessage: f?.error?.message || "GA4 API request failed"
|
|
391
492
|
});
|
|
392
493
|
}
|
|
393
494
|
return l.json();
|
|
394
495
|
}
|
|
395
|
-
|
|
496
|
+
r.get(
|
|
396
497
|
"/report",
|
|
397
|
-
|
|
398
|
-
const { accessToken:
|
|
498
|
+
b(async (o) => {
|
|
499
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = f.match(/^(\d+)daysAgo$/), h = m ? parseInt(m[1], 10) : 30, y = `${h * 2}daysAgo`, w = `${h + 1}daysAgo`, C = [
|
|
399
500
|
{ name: "sessions" },
|
|
400
501
|
{ name: "totalUsers" },
|
|
401
502
|
{ name: "screenPageViews" },
|
|
@@ -406,12 +507,12 @@ function Yt(e) {
|
|
|
406
507
|
{ name: "sessionsPerUser" },
|
|
407
508
|
{ name: "screenPageViewsPerSession" }
|
|
408
509
|
];
|
|
409
|
-
let
|
|
510
|
+
let A, I = !0;
|
|
410
511
|
try {
|
|
411
|
-
|
|
512
|
+
A = await a(d, u, {
|
|
412
513
|
dateRanges: [
|
|
413
|
-
{ startDate: f, endDate:
|
|
414
|
-
{ startDate:
|
|
514
|
+
{ startDate: f, endDate: p, name: "current" },
|
|
515
|
+
{ startDate: y, endDate: w, name: "previous" }
|
|
415
516
|
],
|
|
416
517
|
dimensions: [{ name: "date" }],
|
|
417
518
|
metrics: C,
|
|
@@ -419,25 +520,25 @@ function Yt(e) {
|
|
|
419
520
|
orderBys: [{ dimension: { dimensionName: "date" } }]
|
|
420
521
|
});
|
|
421
522
|
} catch {
|
|
422
|
-
|
|
423
|
-
dateRanges: [{ startDate: f, endDate:
|
|
523
|
+
I = !1, A = await a(d, u, {
|
|
524
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
424
525
|
dimensions: [{ name: "date" }],
|
|
425
526
|
metrics: C,
|
|
426
527
|
metricAggregations: ["TOTAL"],
|
|
427
528
|
orderBys: [{ dimension: { dimensionName: "date" } }]
|
|
428
529
|
});
|
|
429
530
|
}
|
|
430
|
-
return
|
|
531
|
+
return ms(A, I);
|
|
431
532
|
})
|
|
432
|
-
),
|
|
533
|
+
), r.get(
|
|
433
534
|
"/realtime",
|
|
434
|
-
|
|
435
|
-
const { accessToken:
|
|
436
|
-
`${
|
|
535
|
+
b(async (o) => {
|
|
536
|
+
const { accessToken: d, propertyId: u } = await n(o), l = await fetch(
|
|
537
|
+
`${lt}/properties/${u}:runRealtimeReport`,
|
|
437
538
|
{
|
|
438
539
|
method: "POST",
|
|
439
540
|
headers: {
|
|
440
|
-
Authorization: `Bearer ${
|
|
541
|
+
Authorization: `Bearer ${d}`,
|
|
441
542
|
"Content-Type": "application/json"
|
|
442
543
|
},
|
|
443
544
|
body: JSON.stringify({
|
|
@@ -450,22 +551,22 @@ function Yt(e) {
|
|
|
450
551
|
);
|
|
451
552
|
if (!l.ok) {
|
|
452
553
|
const h = await l.json().catch(() => ({}));
|
|
453
|
-
throw
|
|
554
|
+
throw g({ statusCode: l.status, statusMessage: h?.error?.message || "Realtime API failed" });
|
|
454
555
|
}
|
|
455
|
-
const f = await l.json(),
|
|
456
|
-
(h,
|
|
556
|
+
const f = await l.json(), p = (f?.rows || []).reduce(
|
|
557
|
+
(h, y) => h + parseInt(y.metricValues?.[0]?.value || "0", 10),
|
|
457
558
|
0
|
|
458
559
|
), m = (f?.rows || []).map((h) => ({
|
|
459
560
|
page: h.dimensionValues?.[0]?.value || "(not set)",
|
|
460
561
|
activeUsers: parseInt(h.metricValues?.[0]?.value || "0", 10)
|
|
461
562
|
}));
|
|
462
|
-
return { activeUsers:
|
|
563
|
+
return { activeUsers: p, activePages: m };
|
|
463
564
|
})
|
|
464
|
-
),
|
|
565
|
+
), r.get(
|
|
465
566
|
"/top-pages",
|
|
466
|
-
|
|
467
|
-
const { accessToken:
|
|
468
|
-
dateRanges: [{ startDate: f, endDate:
|
|
567
|
+
b(async (o) => {
|
|
568
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = parseInt(l.limit || "10", 10), h = await a(d, u, {
|
|
569
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
469
570
|
dimensions: [{ name: "pagePath" }],
|
|
470
571
|
metrics: [
|
|
471
572
|
{ name: "screenPageViews" },
|
|
@@ -475,23 +576,23 @@ function Yt(e) {
|
|
|
475
576
|
orderBys: [{ metric: { metricName: "screenPageViews" }, desc: !0 }],
|
|
476
577
|
limit: m * 2
|
|
477
578
|
// fetch extra to account for duplicates before aggregation
|
|
478
|
-
}),
|
|
479
|
-
for (const
|
|
480
|
-
const
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
579
|
+
}), y = k(h, "pagePath"), w = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const A of y.rows) {
|
|
581
|
+
const I = A.pagePath.toLowerCase().replace(/\/+$/, ""), M = w.get(I);
|
|
582
|
+
if (M) {
|
|
583
|
+
M.screenPageViews += A.screenPageViews || 0, M.totalUsers += A.totalUsers || 0;
|
|
584
|
+
const D = M.screenPageViews;
|
|
585
|
+
D > 0 && (M.averageSessionDuration = (M.averageSessionDuration * (D - (A.screenPageViews || 0)) + (A.averageSessionDuration || 0) * (A.screenPageViews || 0)) / D);
|
|
485
586
|
} else
|
|
486
|
-
w.set(
|
|
587
|
+
w.set(I, { ...A });
|
|
487
588
|
}
|
|
488
|
-
return { rows: [...w.values()].sort((
|
|
589
|
+
return { rows: [...w.values()].sort((A, I) => (I.screenPageViews || 0) - (A.screenPageViews || 0)).slice(0, m), rowCount: y.rowCount };
|
|
489
590
|
})
|
|
490
|
-
),
|
|
591
|
+
), r.get(
|
|
491
592
|
"/top-sources",
|
|
492
|
-
|
|
493
|
-
const { accessToken:
|
|
494
|
-
dateRanges: [{ startDate: f, endDate:
|
|
593
|
+
b(async (o) => {
|
|
594
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
595
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
495
596
|
dimensions: [{ name: "sessionSource" }],
|
|
496
597
|
metrics: [
|
|
497
598
|
{ name: "sessions" },
|
|
@@ -500,13 +601,13 @@ function Yt(e) {
|
|
|
500
601
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
501
602
|
limit: 10
|
|
502
603
|
});
|
|
503
|
-
return
|
|
604
|
+
return k(m, "sessionSource");
|
|
504
605
|
})
|
|
505
|
-
),
|
|
606
|
+
), r.get(
|
|
506
607
|
"/devices",
|
|
507
|
-
|
|
508
|
-
const { accessToken:
|
|
509
|
-
dateRanges: [{ startDate: f, endDate:
|
|
608
|
+
b(async (o) => {
|
|
609
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
610
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
510
611
|
dimensions: [{ name: "deviceCategory" }],
|
|
511
612
|
metrics: [
|
|
512
613
|
{ name: "sessions" },
|
|
@@ -514,13 +615,13 @@ function Yt(e) {
|
|
|
514
615
|
],
|
|
515
616
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }]
|
|
516
617
|
});
|
|
517
|
-
return
|
|
618
|
+
return k(m, "deviceCategory");
|
|
518
619
|
})
|
|
519
|
-
),
|
|
620
|
+
), r.get(
|
|
520
621
|
"/countries",
|
|
521
|
-
|
|
522
|
-
const { accessToken:
|
|
523
|
-
dateRanges: [{ startDate: f, endDate:
|
|
622
|
+
b(async (o) => {
|
|
623
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
624
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
524
625
|
dimensions: [{ name: "country" }],
|
|
525
626
|
metrics: [
|
|
526
627
|
{ name: "sessions" },
|
|
@@ -529,13 +630,13 @@ function Yt(e) {
|
|
|
529
630
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
530
631
|
limit: 10
|
|
531
632
|
});
|
|
532
|
-
return
|
|
633
|
+
return k(m, "country");
|
|
533
634
|
})
|
|
534
|
-
),
|
|
635
|
+
), r.get(
|
|
535
636
|
"/acquisition/channels",
|
|
536
|
-
|
|
537
|
-
const { accessToken:
|
|
538
|
-
dateRanges: [{ startDate: f, endDate:
|
|
637
|
+
b(async (o) => {
|
|
638
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
639
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
539
640
|
dimensions: [{ name: "sessionDefaultChannelGroup" }],
|
|
540
641
|
metrics: [
|
|
541
642
|
{ name: "sessions" },
|
|
@@ -549,13 +650,13 @@ function Yt(e) {
|
|
|
549
650
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
550
651
|
limit: 15
|
|
551
652
|
});
|
|
552
|
-
return
|
|
653
|
+
return k(m, "sessionDefaultChannelGroup");
|
|
553
654
|
})
|
|
554
|
-
),
|
|
655
|
+
), r.get(
|
|
555
656
|
"/acquisition/source-medium",
|
|
556
|
-
|
|
557
|
-
const { accessToken:
|
|
558
|
-
dateRanges: [{ startDate: f, endDate:
|
|
657
|
+
b(async (o) => {
|
|
658
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
659
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
559
660
|
dimensions: [{ name: "sessionSourceMedium" }],
|
|
560
661
|
metrics: [
|
|
561
662
|
{ name: "sessions" },
|
|
@@ -568,13 +669,13 @@ function Yt(e) {
|
|
|
568
669
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
569
670
|
limit: 20
|
|
570
671
|
});
|
|
571
|
-
return
|
|
672
|
+
return k(m, "sessionSourceMedium");
|
|
572
673
|
})
|
|
573
|
-
),
|
|
674
|
+
), r.get(
|
|
574
675
|
"/acquisition/referrals",
|
|
575
|
-
|
|
576
|
-
const { accessToken:
|
|
577
|
-
dateRanges: [{ startDate: f, endDate:
|
|
676
|
+
b(async (o) => {
|
|
677
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
678
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
578
679
|
dimensions: [{ name: "sessionSource" }],
|
|
579
680
|
dimensionFilter: {
|
|
580
681
|
filter: {
|
|
@@ -591,13 +692,13 @@ function Yt(e) {
|
|
|
591
692
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
592
693
|
limit: 20
|
|
593
694
|
});
|
|
594
|
-
return
|
|
695
|
+
return k(m, "sessionSource");
|
|
595
696
|
})
|
|
596
|
-
),
|
|
697
|
+
), r.get(
|
|
597
698
|
"/acquisition/campaigns",
|
|
598
|
-
|
|
599
|
-
const { accessToken:
|
|
600
|
-
dateRanges: [{ startDate: f, endDate:
|
|
699
|
+
b(async (o) => {
|
|
700
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
701
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
601
702
|
dimensions: [{ name: "sessionCampaignName" }],
|
|
602
703
|
dimensionFilter: {
|
|
603
704
|
notExpression: {
|
|
@@ -616,13 +717,13 @@ function Yt(e) {
|
|
|
616
717
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
617
718
|
limit: 20
|
|
618
719
|
});
|
|
619
|
-
return
|
|
720
|
+
return k(m, "sessionCampaignName");
|
|
620
721
|
})
|
|
621
|
-
),
|
|
722
|
+
), r.get(
|
|
622
723
|
"/content/all-pages",
|
|
623
|
-
|
|
624
|
-
const { accessToken:
|
|
625
|
-
dateRanges: [{ startDate: f, endDate:
|
|
724
|
+
b(async (o) => {
|
|
725
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = parseInt(l.limit || "50", 10), h = await a(d, u, {
|
|
726
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
626
727
|
dimensions: [{ name: "pagePath" }],
|
|
627
728
|
metrics: [
|
|
628
729
|
{ name: "screenPageViews" },
|
|
@@ -636,13 +737,13 @@ function Yt(e) {
|
|
|
636
737
|
orderBys: [{ metric: { metricName: "screenPageViews" }, desc: !0 }],
|
|
637
738
|
limit: m
|
|
638
739
|
});
|
|
639
|
-
return
|
|
740
|
+
return k(h, "pagePath");
|
|
640
741
|
})
|
|
641
|
-
),
|
|
742
|
+
), r.get(
|
|
642
743
|
"/content/landing-pages",
|
|
643
|
-
|
|
644
|
-
const { accessToken:
|
|
645
|
-
dateRanges: [{ startDate: f, endDate:
|
|
744
|
+
b(async (o) => {
|
|
745
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
746
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
646
747
|
dimensions: [{ name: "landingPagePlusQueryString" }],
|
|
647
748
|
metrics: [
|
|
648
749
|
{ name: "sessions" },
|
|
@@ -655,13 +756,13 @@ function Yt(e) {
|
|
|
655
756
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
656
757
|
limit: 30
|
|
657
758
|
});
|
|
658
|
-
return
|
|
759
|
+
return k(m, "landingPagePlusQueryString");
|
|
659
760
|
})
|
|
660
|
-
),
|
|
761
|
+
), r.get(
|
|
661
762
|
"/content/exit-pages",
|
|
662
|
-
|
|
663
|
-
const { accessToken:
|
|
664
|
-
dateRanges: [{ startDate: f, endDate:
|
|
763
|
+
b(async (o) => {
|
|
764
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
765
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
665
766
|
dimensions: [{ name: "pagePath" }],
|
|
666
767
|
metrics: [
|
|
667
768
|
{ name: "sessions" },
|
|
@@ -671,51 +772,51 @@ function Yt(e) {
|
|
|
671
772
|
],
|
|
672
773
|
orderBys: [{ metric: { metricName: "screenPageViews" }, desc: !0 }],
|
|
673
774
|
limit: 20
|
|
674
|
-
}), h =
|
|
675
|
-
return h.rows = h.rows.map((
|
|
676
|
-
...
|
|
677
|
-
exitRate:
|
|
775
|
+
}), h = k(m, "pagePath");
|
|
776
|
+
return h.rows = h.rows.map((y) => ({
|
|
777
|
+
...y,
|
|
778
|
+
exitRate: y.bounceRate || 0
|
|
678
779
|
})), h;
|
|
679
780
|
})
|
|
680
|
-
),
|
|
781
|
+
), r.get(
|
|
681
782
|
"/content/search-terms",
|
|
682
|
-
|
|
683
|
-
const { accessToken:
|
|
783
|
+
b(async (o) => {
|
|
784
|
+
const { accessToken: d, propertyId: u, siteUrl: l } = await n(o), f = v(o), p = f.startDate || "30daysAgo", m = f.endDate || "today";
|
|
684
785
|
if (l)
|
|
685
786
|
try {
|
|
686
|
-
const w = `${
|
|
787
|
+
const w = `${mt}/sites/${encodeURIComponent(l)}/searchAnalytics/query`, C = await fetch(
|
|
687
788
|
w,
|
|
688
789
|
{
|
|
689
790
|
method: "POST",
|
|
690
791
|
headers: {
|
|
691
|
-
Authorization: `Bearer ${
|
|
792
|
+
Authorization: `Bearer ${d}`,
|
|
692
793
|
"Content-Type": "application/json"
|
|
693
794
|
},
|
|
694
795
|
body: JSON.stringify({
|
|
695
|
-
startDate:
|
|
696
|
-
endDate:
|
|
796
|
+
startDate: c(p),
|
|
797
|
+
endDate: c(m),
|
|
697
798
|
dimensions: ["query"],
|
|
698
799
|
rowLimit: 30
|
|
699
800
|
})
|
|
700
801
|
}
|
|
701
802
|
);
|
|
702
803
|
if (C.ok) {
|
|
703
|
-
const
|
|
704
|
-
query:
|
|
705
|
-
clicks:
|
|
706
|
-
impressions:
|
|
707
|
-
ctr:
|
|
708
|
-
position:
|
|
804
|
+
const I = ((await C.json()).rows || []).map((M) => ({
|
|
805
|
+
query: M.keys[0],
|
|
806
|
+
clicks: M.clicks,
|
|
807
|
+
impressions: M.impressions,
|
|
808
|
+
ctr: M.ctr,
|
|
809
|
+
position: M.position
|
|
709
810
|
}));
|
|
710
|
-
if (
|
|
811
|
+
if (I.length > 0)
|
|
711
812
|
return {
|
|
712
|
-
rows:
|
|
713
|
-
rowCount:
|
|
813
|
+
rows: I,
|
|
814
|
+
rowCount: I.length,
|
|
714
815
|
source: "search_console"
|
|
715
816
|
};
|
|
716
817
|
} else {
|
|
717
|
-
const
|
|
718
|
-
console.error(`[GA Module] Search Console API error (${C.status}):`,
|
|
818
|
+
const A = await C.text().catch(() => "");
|
|
819
|
+
console.error(`[GA Module] Search Console API error (${C.status}):`, A);
|
|
719
820
|
}
|
|
720
821
|
} catch (w) {
|
|
721
822
|
console.error("[GA Module] Search Console request failed:", w?.message || w);
|
|
@@ -723,8 +824,8 @@ function Yt(e) {
|
|
|
723
824
|
else
|
|
724
825
|
console.log("[GA Module] No siteUrl configured, skipping Search Console");
|
|
725
826
|
try {
|
|
726
|
-
const w = await a(
|
|
727
|
-
dateRanges: [{ startDate:
|
|
827
|
+
const w = await a(d, u, {
|
|
828
|
+
dateRanges: [{ startDate: p, endDate: m }],
|
|
728
829
|
dimensions: [{ name: "sessionGoogleAdsQuery" }],
|
|
729
830
|
metrics: [
|
|
730
831
|
{ name: "sessions" },
|
|
@@ -755,13 +856,13 @@ function Yt(e) {
|
|
|
755
856
|
},
|
|
756
857
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
757
858
|
limit: 30
|
|
758
|
-
}), C =
|
|
859
|
+
}), C = k(w, "sessionGoogleAdsQuery");
|
|
759
860
|
if (C.rows.length > 0)
|
|
760
861
|
return { ...C, source: "google_ads" };
|
|
761
862
|
} catch {
|
|
762
863
|
}
|
|
763
|
-
const h = await a(
|
|
764
|
-
dateRanges: [{ startDate:
|
|
864
|
+
const h = await a(d, u, {
|
|
865
|
+
dateRanges: [{ startDate: p, endDate: m }],
|
|
765
866
|
dimensions: [{ name: "landingPagePlusQueryString" }],
|
|
766
867
|
metrics: [
|
|
767
868
|
{ name: "sessions" },
|
|
@@ -794,13 +895,13 @@ function Yt(e) {
|
|
|
794
895
|
orderBys: [{ metric: { metricName: "sessions" }, desc: !0 }],
|
|
795
896
|
limit: 30
|
|
796
897
|
});
|
|
797
|
-
return { ...
|
|
898
|
+
return { ...k(h, "landingPagePlusQueryString"), source: "organic_landing_pages" };
|
|
798
899
|
})
|
|
799
|
-
),
|
|
900
|
+
), r.get(
|
|
800
901
|
"/audience/overview",
|
|
801
|
-
|
|
802
|
-
const { accessToken:
|
|
803
|
-
dateRanges: [{ startDate: f, endDate:
|
|
902
|
+
b(async (o) => {
|
|
903
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
904
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
804
905
|
dimensions: [{ name: "newVsReturning" }],
|
|
805
906
|
metrics: [
|
|
806
907
|
{ name: "totalUsers" },
|
|
@@ -811,14 +912,14 @@ function Yt(e) {
|
|
|
811
912
|
],
|
|
812
913
|
metricAggregations: ["TOTAL"]
|
|
813
914
|
});
|
|
814
|
-
return
|
|
915
|
+
return k(m, "newVsReturning");
|
|
815
916
|
})
|
|
816
|
-
),
|
|
917
|
+
), r.get(
|
|
817
918
|
"/audience/technology",
|
|
818
|
-
|
|
819
|
-
const { accessToken:
|
|
820
|
-
dateRanges: [{ startDate: f, endDate:
|
|
821
|
-
dimensions: [{ name:
|
|
919
|
+
b(async (o) => {
|
|
920
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = l.dimension || "browser", y = ["browser", "operatingSystem", "screenResolution"].includes(m) ? m : "browser", w = await a(d, u, {
|
|
921
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
922
|
+
dimensions: [{ name: y }],
|
|
822
923
|
metrics: [
|
|
823
924
|
{ name: "totalUsers" },
|
|
824
925
|
{ name: "sessions" },
|
|
@@ -827,13 +928,13 @@ function Yt(e) {
|
|
|
827
928
|
orderBys: [{ metric: { metricName: "totalUsers" }, desc: !0 }],
|
|
828
929
|
limit: 10
|
|
829
930
|
});
|
|
830
|
-
return
|
|
931
|
+
return k(w, y);
|
|
831
932
|
})
|
|
832
|
-
),
|
|
933
|
+
), r.get(
|
|
833
934
|
"/audience/languages",
|
|
834
|
-
|
|
835
|
-
const { accessToken:
|
|
836
|
-
dateRanges: [{ startDate: f, endDate:
|
|
935
|
+
b(async (o) => {
|
|
936
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
937
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
837
938
|
dimensions: [{ name: "language" }],
|
|
838
939
|
metrics: [
|
|
839
940
|
{ name: "totalUsers" },
|
|
@@ -842,13 +943,13 @@ function Yt(e) {
|
|
|
842
943
|
orderBys: [{ metric: { metricName: "totalUsers" }, desc: !0 }],
|
|
843
944
|
limit: 15
|
|
844
945
|
});
|
|
845
|
-
return
|
|
946
|
+
return k(m, "language");
|
|
846
947
|
})
|
|
847
|
-
),
|
|
948
|
+
), r.get(
|
|
848
949
|
"/audience/hours",
|
|
849
|
-
|
|
850
|
-
const { accessToken:
|
|
851
|
-
dateRanges: [{ startDate: f, endDate:
|
|
950
|
+
b(async (o) => {
|
|
951
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
952
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
852
953
|
dimensions: [{ name: "dayOfWeekName" }, { name: "hour" }],
|
|
853
954
|
metrics: [{ name: "sessions" }],
|
|
854
955
|
orderBys: [
|
|
@@ -858,13 +959,13 @@ function Yt(e) {
|
|
|
858
959
|
limit: 168
|
|
859
960
|
// 7 days × 24 hours
|
|
860
961
|
});
|
|
861
|
-
return
|
|
962
|
+
return ft(m, ["dayOfWeekName", "hour"]);
|
|
862
963
|
})
|
|
863
|
-
),
|
|
964
|
+
), r.get(
|
|
864
965
|
"/audience/cities",
|
|
865
|
-
|
|
866
|
-
const { accessToken:
|
|
867
|
-
dateRanges: [{ startDate: f, endDate:
|
|
966
|
+
b(async (o) => {
|
|
967
|
+
const { accessToken: d, propertyId: u } = await n(o), l = v(o), f = l.startDate || "30daysAgo", p = l.endDate || "today", m = await a(d, u, {
|
|
968
|
+
dateRanges: [{ startDate: f, endDate: p }],
|
|
868
969
|
dimensions: [{ name: "city" }, { name: "country" }],
|
|
869
970
|
metrics: [
|
|
870
971
|
{ name: "totalUsers" },
|
|
@@ -881,95 +982,95 @@ function Yt(e) {
|
|
|
881
982
|
orderBys: [{ metric: { metricName: "totalUsers" }, desc: !0 }],
|
|
882
983
|
limit: 20
|
|
883
984
|
});
|
|
884
|
-
return
|
|
985
|
+
return ft(m, ["city", "country"]);
|
|
885
986
|
})
|
|
886
987
|
);
|
|
887
|
-
function
|
|
888
|
-
const
|
|
889
|
-
if (
|
|
890
|
-
const
|
|
891
|
-
return
|
|
988
|
+
function c(o) {
|
|
989
|
+
const d = o.match(/^(\d+)daysAgo$/);
|
|
990
|
+
if (d) {
|
|
991
|
+
const u = /* @__PURE__ */ new Date();
|
|
992
|
+
return u.setDate(u.getDate() - parseInt(d[1], 10)), u.toISOString().slice(0, 10);
|
|
892
993
|
}
|
|
893
994
|
if (o === "today") return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
894
995
|
if (o === "yesterday") {
|
|
895
|
-
const
|
|
896
|
-
return
|
|
996
|
+
const u = /* @__PURE__ */ new Date();
|
|
997
|
+
return u.setDate(u.getDate() - 1), u.toISOString().slice(0, 10);
|
|
897
998
|
}
|
|
898
999
|
return o;
|
|
899
1000
|
}
|
|
900
|
-
async function
|
|
901
|
-
const l = `${
|
|
1001
|
+
async function i(o, d, u) {
|
|
1002
|
+
const l = `${mt}/sites/${encodeURIComponent(d)}/searchAnalytics/query`, f = await fetch(l, {
|
|
902
1003
|
method: "POST",
|
|
903
1004
|
headers: {
|
|
904
1005
|
Authorization: `Bearer ${o}`,
|
|
905
1006
|
"Content-Type": "application/json"
|
|
906
1007
|
},
|
|
907
|
-
body: JSON.stringify(
|
|
1008
|
+
body: JSON.stringify(u)
|
|
908
1009
|
});
|
|
909
1010
|
if (!f.ok) {
|
|
910
|
-
const
|
|
911
|
-
throw
|
|
1011
|
+
const p = await f.json().catch(() => ({}));
|
|
1012
|
+
throw g({
|
|
912
1013
|
statusCode: f.status,
|
|
913
|
-
statusMessage:
|
|
1014
|
+
statusMessage: p?.error?.message || "Search Console API request failed"
|
|
914
1015
|
});
|
|
915
1016
|
}
|
|
916
1017
|
return f.json();
|
|
917
1018
|
}
|
|
918
|
-
return
|
|
1019
|
+
return r.get(
|
|
919
1020
|
"/seo/keywords",
|
|
920
|
-
|
|
921
|
-
const { accessToken:
|
|
922
|
-
if (!
|
|
923
|
-
throw
|
|
924
|
-
const l =
|
|
1021
|
+
b(async (o) => {
|
|
1022
|
+
const { accessToken: d, siteUrl: u } = await n(o);
|
|
1023
|
+
if (!u)
|
|
1024
|
+
throw g({ statusCode: 400, statusMessage: "Search Console Site URL is not configured. Add it in module settings." });
|
|
1025
|
+
const l = v(o), f = c(l.startDate || "30daysAgo"), p = c(l.endDate || "today"), m = Math.min(parseInt(l.limit) || 50, 100), h = await i(d, u, {
|
|
925
1026
|
startDate: f,
|
|
926
|
-
endDate:
|
|
1027
|
+
endDate: p,
|
|
927
1028
|
dimensions: ["query"],
|
|
928
1029
|
rowLimit: m
|
|
929
1030
|
});
|
|
930
1031
|
return {
|
|
931
|
-
rows: (h.rows || []).map((
|
|
932
|
-
query:
|
|
933
|
-
clicks:
|
|
934
|
-
impressions:
|
|
935
|
-
ctr:
|
|
936
|
-
position:
|
|
1032
|
+
rows: (h.rows || []).map((y) => ({
|
|
1033
|
+
query: y.keys[0],
|
|
1034
|
+
clicks: y.clicks,
|
|
1035
|
+
impressions: y.impressions,
|
|
1036
|
+
ctr: y.ctr,
|
|
1037
|
+
position: y.position
|
|
937
1038
|
})),
|
|
938
1039
|
rowCount: h.rows?.length || 0
|
|
939
1040
|
};
|
|
940
1041
|
})
|
|
941
|
-
),
|
|
1042
|
+
), r.get(
|
|
942
1043
|
"/seo/pages",
|
|
943
|
-
|
|
944
|
-
const { accessToken:
|
|
945
|
-
if (!
|
|
946
|
-
throw
|
|
947
|
-
const l =
|
|
1044
|
+
b(async (o) => {
|
|
1045
|
+
const { accessToken: d, siteUrl: u } = await n(o);
|
|
1046
|
+
if (!u)
|
|
1047
|
+
throw g({ statusCode: 400, statusMessage: "Search Console Site URL is not configured." });
|
|
1048
|
+
const l = v(o), f = c(l.startDate || "30daysAgo"), p = c(l.endDate || "today"), m = Math.min(parseInt(l.limit) || 50, 100), h = await i(d, u, {
|
|
948
1049
|
startDate: f,
|
|
949
|
-
endDate:
|
|
1050
|
+
endDate: p,
|
|
950
1051
|
dimensions: ["page"],
|
|
951
1052
|
rowLimit: m
|
|
952
1053
|
});
|
|
953
1054
|
return {
|
|
954
|
-
rows: (h.rows || []).map((
|
|
955
|
-
page:
|
|
956
|
-
clicks:
|
|
957
|
-
impressions:
|
|
958
|
-
ctr:
|
|
959
|
-
position:
|
|
1055
|
+
rows: (h.rows || []).map((y) => ({
|
|
1056
|
+
page: y.keys[0],
|
|
1057
|
+
clicks: y.clicks,
|
|
1058
|
+
impressions: y.impressions,
|
|
1059
|
+
ctr: y.ctr,
|
|
1060
|
+
position: y.position
|
|
960
1061
|
})),
|
|
961
1062
|
rowCount: h.rows?.length || 0
|
|
962
1063
|
};
|
|
963
1064
|
})
|
|
964
|
-
),
|
|
1065
|
+
), r.get(
|
|
965
1066
|
"/seo/trends",
|
|
966
|
-
|
|
967
|
-
const { accessToken:
|
|
968
|
-
if (!
|
|
969
|
-
throw
|
|
970
|
-
const l =
|
|
1067
|
+
b(async (o) => {
|
|
1068
|
+
const { accessToken: d, siteUrl: u } = await n(o);
|
|
1069
|
+
if (!u)
|
|
1070
|
+
throw g({ statusCode: 400, statusMessage: "Search Console Site URL is not configured." });
|
|
1071
|
+
const l = v(o), f = c(l.startDate || "30daysAgo"), p = c(l.endDate || "today"), h = ((await i(d, u, {
|
|
971
1072
|
startDate: f,
|
|
972
|
-
endDate:
|
|
1073
|
+
endDate: p,
|
|
973
1074
|
dimensions: ["date"],
|
|
974
1075
|
rowLimit: 500
|
|
975
1076
|
})).rows || []).map((w) => ({
|
|
@@ -978,24 +1079,24 @@ function Yt(e) {
|
|
|
978
1079
|
impressions: w.impressions,
|
|
979
1080
|
ctr: w.ctr,
|
|
980
1081
|
position: w.position
|
|
981
|
-
})).sort((w, C) => w.date.localeCompare(C.date)),
|
|
1082
|
+
})).sort((w, C) => w.date.localeCompare(C.date)), y = h.reduce(
|
|
982
1083
|
(w, C) => ({
|
|
983
1084
|
clicks: w.clicks + C.clicks,
|
|
984
1085
|
impressions: w.impressions + C.impressions
|
|
985
1086
|
}),
|
|
986
1087
|
{ clicks: 0, impressions: 0 }
|
|
987
1088
|
);
|
|
988
|
-
return
|
|
1089
|
+
return y.ctr = y.impressions > 0 ? y.clicks / y.impressions : 0, y.avgPosition = h.length > 0 ? h.reduce((w, C) => w + C.position, 0) / h.length : 0, { rows: h, totals: y, rowCount: h.length };
|
|
989
1090
|
})
|
|
990
|
-
),
|
|
1091
|
+
), r.get(
|
|
991
1092
|
"/seo/query-pages",
|
|
992
|
-
|
|
993
|
-
const { accessToken:
|
|
994
|
-
if (!
|
|
995
|
-
throw
|
|
996
|
-
const l =
|
|
1093
|
+
b(async (o) => {
|
|
1094
|
+
const { accessToken: d, siteUrl: u } = await n(o);
|
|
1095
|
+
if (!u)
|
|
1096
|
+
throw g({ statusCode: 400, statusMessage: "Search Console Site URL is not configured." });
|
|
1097
|
+
const l = v(o), f = c(l.startDate || "30daysAgo"), p = c(l.endDate || "today"), m = await i(d, u, {
|
|
997
1098
|
startDate: f,
|
|
998
|
-
endDate:
|
|
1099
|
+
endDate: p,
|
|
999
1100
|
dimensions: ["query", "page"],
|
|
1000
1101
|
rowLimit: 100
|
|
1001
1102
|
});
|
|
@@ -1011,88 +1112,88 @@ function Yt(e) {
|
|
|
1011
1112
|
rowCount: m.rows?.length || 0
|
|
1012
1113
|
};
|
|
1013
1114
|
})
|
|
1014
|
-
),
|
|
1115
|
+
), r.handler;
|
|
1015
1116
|
}
|
|
1016
|
-
function
|
|
1017
|
-
const s = (e.metricHeaders || []).map((f) => f.name),
|
|
1117
|
+
function ms(e, t = !0) {
|
|
1118
|
+
const s = (e.metricHeaders || []).map((f) => f.name), n = (e.dimensionHeaders || []).map((f) => f.name).indexOf("date"), a = [];
|
|
1018
1119
|
(e.rows || []).forEach((f) => {
|
|
1019
|
-
const
|
|
1020
|
-
if (!
|
|
1021
|
-
const h = { date: `${
|
|
1022
|
-
s.forEach((
|
|
1023
|
-
h[
|
|
1120
|
+
const p = n >= 0 ? f.dimensionValues[n]?.value : f.dimensionValues[0]?.value;
|
|
1121
|
+
if (!p || p.length < 8) return;
|
|
1122
|
+
const h = { date: `${p.slice(0, 4)}-${p.slice(4, 6)}-${p.slice(6, 8)}` };
|
|
1123
|
+
s.forEach((y, w) => {
|
|
1124
|
+
h[y] = parseFloat(f.metricValues[w]?.value || "0");
|
|
1024
1125
|
}), a.push(h);
|
|
1025
1126
|
});
|
|
1026
|
-
const
|
|
1027
|
-
e.totals && e.totals.length >= 2 ? s.forEach((f,
|
|
1028
|
-
|
|
1029
|
-
}) : e.totals && e.totals.length === 1 && s.forEach((f,
|
|
1030
|
-
|
|
1127
|
+
const c = {}, i = {};
|
|
1128
|
+
e.totals && e.totals.length >= 2 ? s.forEach((f, p) => {
|
|
1129
|
+
c[f] = parseFloat(e.totals[0]?.metricValues?.[p]?.value || "0"), i[f] = parseFloat(e.totals[1]?.metricValues?.[p]?.value || "0");
|
|
1130
|
+
}) : e.totals && e.totals.length === 1 && s.forEach((f, p) => {
|
|
1131
|
+
c[f] = parseFloat(e.totals[0]?.metricValues?.[p]?.value || "0");
|
|
1031
1132
|
});
|
|
1032
1133
|
const o = {};
|
|
1033
1134
|
s.forEach((f) => {
|
|
1034
|
-
const
|
|
1035
|
-
m !== void 0 && m !== 0 ? o[f] = (
|
|
1135
|
+
const p = c[f] || 0, m = i[f];
|
|
1136
|
+
m !== void 0 && m !== 0 ? o[f] = (p - m) / m * 100 : o[f] = null;
|
|
1036
1137
|
});
|
|
1037
|
-
const
|
|
1138
|
+
const d = /* @__PURE__ */ new Map();
|
|
1038
1139
|
for (const f of a)
|
|
1039
|
-
|
|
1040
|
-
const
|
|
1140
|
+
d.has(f.date) || d.set(f.date, f);
|
|
1141
|
+
const u = Array.from(d.values()).sort((f, p) => f.date.localeCompare(p.date)), l = t && e.totals && e.totals.length >= 2 ? u.slice(-Math.ceil(u.length / 2)) : u;
|
|
1041
1142
|
return {
|
|
1042
1143
|
rows: l,
|
|
1043
|
-
totals:
|
|
1044
|
-
previousTotals:
|
|
1144
|
+
totals: c,
|
|
1145
|
+
previousTotals: i,
|
|
1045
1146
|
changes: o,
|
|
1046
1147
|
rowCount: l.length
|
|
1047
1148
|
};
|
|
1048
1149
|
}
|
|
1049
|
-
function
|
|
1050
|
-
const s = (e.metricHeaders || []).map((
|
|
1150
|
+
function k(e, t) {
|
|
1151
|
+
const s = (e.metricHeaders || []).map((n) => n.name), r = (e.rows || []).map((n) => {
|
|
1051
1152
|
const a = {
|
|
1052
|
-
[t]:
|
|
1153
|
+
[t]: n.dimensionValues[0].value
|
|
1053
1154
|
};
|
|
1054
|
-
return s.forEach((
|
|
1055
|
-
a[
|
|
1155
|
+
return s.forEach((c, i) => {
|
|
1156
|
+
a[c] = parseFloat(n.metricValues[i].value);
|
|
1056
1157
|
}), a;
|
|
1057
1158
|
});
|
|
1058
|
-
return { rows:
|
|
1159
|
+
return { rows: r, rowCount: e.rowCount || r.length };
|
|
1059
1160
|
}
|
|
1060
|
-
function
|
|
1061
|
-
const s = (e.metricHeaders || []).map((
|
|
1161
|
+
function ft(e, t) {
|
|
1162
|
+
const s = (e.metricHeaders || []).map((n) => n.name), r = (e.rows || []).map((n) => {
|
|
1062
1163
|
const a = {};
|
|
1063
|
-
return t.forEach((
|
|
1064
|
-
a[
|
|
1065
|
-
}), s.forEach((
|
|
1066
|
-
a[
|
|
1164
|
+
return t.forEach((c, i) => {
|
|
1165
|
+
a[c] = n.dimensionValues[i]?.value || "";
|
|
1166
|
+
}), s.forEach((c, i) => {
|
|
1167
|
+
a[c] = parseFloat(n.metricValues[i].value);
|
|
1067
1168
|
}), a;
|
|
1068
1169
|
});
|
|
1069
|
-
return { rows:
|
|
1070
|
-
}
|
|
1071
|
-
function
|
|
1072
|
-
const { decrypt: t } = e, s =
|
|
1073
|
-
async function
|
|
1074
|
-
const { supabase: a, instanceId:
|
|
1075
|
-
if (o || !
|
|
1076
|
-
throw
|
|
1077
|
-
const
|
|
1078
|
-
if (!
|
|
1079
|
-
throw
|
|
1080
|
-
const { data:
|
|
1081
|
-
let h =
|
|
1170
|
+
return { rows: r, rowCount: e.rowCount || r.length };
|
|
1171
|
+
}
|
|
1172
|
+
function Ta(e) {
|
|
1173
|
+
const { decrypt: t } = e, s = J();
|
|
1174
|
+
async function r(n) {
|
|
1175
|
+
const { supabase: a, instanceId: c } = n.context.module, { data: i, error: o } = await a.from("project_modules").select("config").eq("id", c).single();
|
|
1176
|
+
if (o || !i?.config)
|
|
1177
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load module config." });
|
|
1178
|
+
const d = i.config, u = d.githubIntegration, l = d.githubRepo, f = d.githubOwner;
|
|
1179
|
+
if (!u || !l || !f)
|
|
1180
|
+
throw g({ statusCode: 400, statusMessage: "No GitHub integration configured for this module." });
|
|
1181
|
+
const { data: p, error: m } = await a.from("integrations").select("config").eq("id", u).single();
|
|
1182
|
+
let h = p?.config;
|
|
1082
1183
|
if (m || !h) {
|
|
1083
|
-
const { data: w, error: C } = await a.from("agency_integrations").select("config").eq("id",
|
|
1184
|
+
const { data: w, error: C } = await a.from("agency_integrations").select("config").eq("id", u).single();
|
|
1084
1185
|
if (C || !w?.config)
|
|
1085
|
-
throw
|
|
1186
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load Github credentials." });
|
|
1086
1187
|
h = w.config;
|
|
1087
1188
|
}
|
|
1088
1189
|
return { token: t(h.token), repo: l, owner: f };
|
|
1089
1190
|
}
|
|
1090
1191
|
return s.post(
|
|
1091
1192
|
"/github/deploy",
|
|
1092
|
-
|
|
1193
|
+
b(async (n) => {
|
|
1093
1194
|
try {
|
|
1094
|
-
const { token: a, repo:
|
|
1095
|
-
await fetch(`https://api.github.com/repos/${
|
|
1195
|
+
const { token: a, repo: c, owner: i } = await r(n);
|
|
1196
|
+
await fetch(`https://api.github.com/repos/${i}/${c}/dispatches`, {
|
|
1096
1197
|
method: "POST",
|
|
1097
1198
|
headers: {
|
|
1098
1199
|
Authorization: `Bearer ${a}`,
|
|
@@ -1103,12 +1204,12 @@ function Kt(e) {
|
|
|
1103
1204
|
})
|
|
1104
1205
|
});
|
|
1105
1206
|
} catch (a) {
|
|
1106
|
-
throw console.error("Error triggering GitHub deployment:", a),
|
|
1207
|
+
throw console.error("Error triggering GitHub deployment:", a), g({ statusCode: 500, statusMessage: "Failed to trigger deployment." });
|
|
1107
1208
|
}
|
|
1108
1209
|
})
|
|
1109
1210
|
), s.handler;
|
|
1110
1211
|
}
|
|
1111
|
-
const
|
|
1212
|
+
const Ot = "firebaseAuthManager", $ = {
|
|
1112
1213
|
viewUsers: "view-auth-users",
|
|
1113
1214
|
viewPii: "view-auth-user-pii",
|
|
1114
1215
|
createUsers: "create-auth-users",
|
|
@@ -1119,39 +1220,39 @@ const Oe = "firebaseAuthManager", v = {
|
|
|
1119
1220
|
manageRoles: "manage-auth-roles",
|
|
1120
1221
|
manageProfiles: "manage-auth-profiles",
|
|
1121
1222
|
deleteUsers: "delete-auth-users"
|
|
1122
|
-
},
|
|
1223
|
+
}, jt = x().trim().email().max(254), Qe = x().min(1).max(128), fs = Qe.refine((e) => e === e.trim(), {
|
|
1123
1224
|
message: "Custom UID may not begin or end with whitespace."
|
|
1124
|
-
}),
|
|
1125
|
-
email:
|
|
1126
|
-
password:
|
|
1127
|
-
uid:
|
|
1128
|
-
displayName: $
|
|
1129
|
-
phoneNumber:
|
|
1130
|
-
emailVerified:
|
|
1131
|
-
disabled:
|
|
1132
|
-
createProfile:
|
|
1133
|
-
}).strict(),
|
|
1134
|
-
reason:
|
|
1135
|
-
displayName: $
|
|
1136
|
-
email:
|
|
1137
|
-
phoneNumber:
|
|
1138
|
-
emailVerified:
|
|
1139
|
-
}).strict().refine((e) =>
|
|
1225
|
+
}), qt = x().trim().regex(/^\+[1-9]\d{1,14}$/, "Phone number must use E.164 format."), ps = x().email().max(254), gs = x().regex(/^\+[1-9]\d{1,14}$/, "Phone number must use E.164 format."), Re = x().trim().min(3).max(500), $t = x().trim().min(1).max(128), hs = O({
|
|
1226
|
+
email: jt,
|
|
1227
|
+
password: x().min(8).max(128),
|
|
1228
|
+
uid: fs.optional(),
|
|
1229
|
+
displayName: $t.optional(),
|
|
1230
|
+
phoneNumber: qt.optional(),
|
|
1231
|
+
emailVerified: _e().optional(),
|
|
1232
|
+
disabled: _e().optional(),
|
|
1233
|
+
createProfile: _e().optional().default(!1)
|
|
1234
|
+
}).strict(), ys = O({
|
|
1235
|
+
reason: Re,
|
|
1236
|
+
displayName: $t.nullable().optional(),
|
|
1237
|
+
email: jt.optional(),
|
|
1238
|
+
phoneNumber: qt.nullable().optional(),
|
|
1239
|
+
emailVerified: _e().optional()
|
|
1240
|
+
}).strict().refine((e) => re(e, "displayName") || re(e, "email") || re(e, "phoneNumber") || re(e, "emailVerified"), {
|
|
1140
1241
|
message: "At least one editable user field is required."
|
|
1141
|
-
}),
|
|
1142
|
-
disabled:
|
|
1143
|
-
reason:
|
|
1144
|
-
}).strict(),
|
|
1145
|
-
reason:
|
|
1146
|
-
}).strict(),
|
|
1147
|
-
role:
|
|
1148
|
-
reason:
|
|
1149
|
-
}).strict(),
|
|
1150
|
-
reason:
|
|
1242
|
+
}), ws = O({
|
|
1243
|
+
disabled: _e(),
|
|
1244
|
+
reason: Re
|
|
1245
|
+
}).strict(), $e = O({
|
|
1246
|
+
reason: Re
|
|
1247
|
+
}).strict(), bs = O({
|
|
1248
|
+
role: x().trim().min(1).max(64).nullable(),
|
|
1249
|
+
reason: Re
|
|
1250
|
+
}).strict(), Cs = O({
|
|
1251
|
+
reason: Re,
|
|
1151
1252
|
// Deliberately do not trim this value: destructive confirmation must be an
|
|
1152
1253
|
// exact byte-for-byte match with the UID shown to the operator.
|
|
1153
|
-
confirmation:
|
|
1154
|
-
}).strict(),
|
|
1254
|
+
confirmation: x().min(1).max(128)
|
|
1255
|
+
}).strict(), As = /* @__PURE__ */ new Set([
|
|
1155
1256
|
"acr",
|
|
1156
1257
|
"amr",
|
|
1157
1258
|
"at_hash",
|
|
@@ -1174,188 +1275,188 @@ const Oe = "firebaseAuthManager", v = {
|
|
|
1174
1275
|
"picture",
|
|
1175
1276
|
"sub",
|
|
1176
1277
|
"user_id"
|
|
1177
|
-
]),
|
|
1178
|
-
function
|
|
1278
|
+
]), Is = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1279
|
+
function re(e, t) {
|
|
1179
1280
|
return Object.prototype.hasOwnProperty.call(e, t);
|
|
1180
1281
|
}
|
|
1181
|
-
function
|
|
1182
|
-
throw
|
|
1282
|
+
function ce(e) {
|
|
1283
|
+
throw g({ statusCode: 400, statusMessage: e });
|
|
1183
1284
|
}
|
|
1184
|
-
function
|
|
1285
|
+
function pt(e, t) {
|
|
1185
1286
|
return typeof e == "boolean" ? e : e === "true" ? !0 : e === "false" ? !1 : t;
|
|
1186
1287
|
}
|
|
1187
|
-
function
|
|
1288
|
+
function Bt(e) {
|
|
1188
1289
|
const t = typeof e == "number" ? e : Number(e);
|
|
1189
1290
|
return Number.isFinite(t) ? Math.min(100, Math.max(10, Math.trunc(t))) : 50;
|
|
1190
1291
|
}
|
|
1191
|
-
function
|
|
1292
|
+
function Ms(e) {
|
|
1192
1293
|
const t = String(e || "users").trim();
|
|
1193
|
-
return t.length === 0 || t.length > 256 || t.includes("/") || t === "." || t === ".." || /^__.*__$/.test(t) ?
|
|
1294
|
+
return t.length === 0 || t.length > 256 || t.includes("/") || t === "." || t === ".." || /^__.*__$/.test(t) ? ce("The configured Firestore profile collection is invalid.") : t;
|
|
1194
1295
|
}
|
|
1195
|
-
function
|
|
1196
|
-
const
|
|
1197
|
-
return
|
|
1296
|
+
function ge(e, t, s) {
|
|
1297
|
+
const r = String(e ?? t).trim() || t;
|
|
1298
|
+
return r.length > 128 || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(r) || Is.has(r) ? ce(`The configured ${s} field is invalid.`) : r;
|
|
1198
1299
|
}
|
|
1199
|
-
function
|
|
1300
|
+
function Ss(e) {
|
|
1200
1301
|
const t = Array.isArray(e) ? e : String(e || "").split(","), s = [];
|
|
1201
|
-
for (const
|
|
1202
|
-
if (typeof
|
|
1203
|
-
return
|
|
1204
|
-
const
|
|
1205
|
-
if (
|
|
1206
|
-
if (
|
|
1207
|
-
return
|
|
1208
|
-
s.includes(
|
|
1302
|
+
for (const r of t) {
|
|
1303
|
+
if (typeof r != "string")
|
|
1304
|
+
return ce("Configured role options must be strings.");
|
|
1305
|
+
const n = r.trim();
|
|
1306
|
+
if (n) {
|
|
1307
|
+
if (n.length > 64)
|
|
1308
|
+
return ce("Configured role options may not exceed 64 characters.");
|
|
1309
|
+
s.includes(n) || s.push(n);
|
|
1209
1310
|
}
|
|
1210
1311
|
}
|
|
1211
|
-
return s.length > 50 ?
|
|
1312
|
+
return s.length > 50 ? ce("No more than 50 role options may be configured.") : s;
|
|
1212
1313
|
}
|
|
1213
|
-
function
|
|
1314
|
+
function Ds(e) {
|
|
1214
1315
|
const t = typeof e.project == "string" ? e.project.trim() : "";
|
|
1215
|
-
t ||
|
|
1216
|
-
const s =
|
|
1217
|
-
s &&
|
|
1218
|
-
const
|
|
1219
|
-
profileUidField:
|
|
1220
|
-
profileNameField:
|
|
1221
|
-
profileEmailField:
|
|
1222
|
-
profileRoleField:
|
|
1223
|
-
profileStatusField:
|
|
1316
|
+
t || ce("Firebase Auth Manager has no Firebase integration configured.");
|
|
1317
|
+
const s = pt(e.manageRoleClaim, !1), r = ge(e.roleClaimKey, "role", "role claim");
|
|
1318
|
+
s && As.has(r) && ce("The configured role claim key is reserved by Firebase.");
|
|
1319
|
+
const n = {
|
|
1320
|
+
profileUidField: ge(e.profileUidField, "uid", "profile UID"),
|
|
1321
|
+
profileNameField: ge(e.profileNameField, "displayName", "profile name"),
|
|
1322
|
+
profileEmailField: ge(e.profileEmailField, "email", "profile email"),
|
|
1323
|
+
profileRoleField: ge(e.profileRoleField, "role", "profile role"),
|
|
1324
|
+
profileStatusField: ge(e.profileStatusField, "status", "profile status")
|
|
1224
1325
|
};
|
|
1225
|
-
return new Set(Object.values(
|
|
1326
|
+
return new Set(Object.values(n)).size !== Object.keys(n).length && ce("Each configured Firestore profile field must use a distinct field name."), {
|
|
1226
1327
|
project: t,
|
|
1227
|
-
pageSize:
|
|
1228
|
-
linkFirestoreProfiles:
|
|
1229
|
-
profileCollection:
|
|
1230
|
-
...
|
|
1328
|
+
pageSize: Bt(e.pageSize),
|
|
1329
|
+
linkFirestoreProfiles: pt(e.linkFirestoreProfiles, !0),
|
|
1330
|
+
profileCollection: Ms(e.profileCollection),
|
|
1331
|
+
...n,
|
|
1231
1332
|
manageRoleClaim: s,
|
|
1232
|
-
roleClaimKey:
|
|
1233
|
-
roleOptions:
|
|
1333
|
+
roleClaimKey: r,
|
|
1334
|
+
roleOptions: Ss(e.roleOptions)
|
|
1234
1335
|
};
|
|
1235
1336
|
}
|
|
1236
|
-
function
|
|
1337
|
+
function gt(e) {
|
|
1237
1338
|
const t = Array.isArray(e) ? e[0] : e;
|
|
1238
1339
|
return typeof t != "string" ? void 0 : t.trim() || void 0;
|
|
1239
1340
|
}
|
|
1240
|
-
function
|
|
1341
|
+
function ht(e) {
|
|
1241
1342
|
const t = Array.isArray(e) ? e[0] : e;
|
|
1242
1343
|
if (!(typeof t != "string" || t.length === 0))
|
|
1243
1344
|
return t;
|
|
1244
1345
|
}
|
|
1245
|
-
function
|
|
1246
|
-
const s =
|
|
1247
|
-
let
|
|
1346
|
+
function Ts(e, t = 50) {
|
|
1347
|
+
const s = gt(e.limit);
|
|
1348
|
+
let r = Bt(t);
|
|
1248
1349
|
if (s !== void 0) {
|
|
1249
1350
|
if (!/^\d+$/.test(s))
|
|
1250
|
-
throw
|
|
1251
|
-
if (
|
|
1252
|
-
throw
|
|
1351
|
+
throw g({ statusCode: 422, statusMessage: "Limit must be a whole number." });
|
|
1352
|
+
if (r = Number(s), r < 1 || r > 100)
|
|
1353
|
+
throw g({ statusCode: 422, statusMessage: "Limit must be between 1 and 100." });
|
|
1253
1354
|
}
|
|
1254
|
-
const
|
|
1255
|
-
if (
|
|
1256
|
-
throw
|
|
1257
|
-
const a =
|
|
1258
|
-
if (
|
|
1259
|
-
throw
|
|
1260
|
-
if (!a) return { limit:
|
|
1261
|
-
if (
|
|
1262
|
-
throw
|
|
1263
|
-
const
|
|
1264
|
-
if (!["uid", "email", "phone"].includes(
|
|
1265
|
-
throw
|
|
1266
|
-
if (
|
|
1267
|
-
const
|
|
1268
|
-
if (!
|
|
1269
|
-
throw
|
|
1270
|
-
return { limit: 1, search: { kind: "phone", value:
|
|
1355
|
+
const n = ht(e.pageToken);
|
|
1356
|
+
if (n && n.length > 4096)
|
|
1357
|
+
throw g({ statusCode: 422, statusMessage: "Page token is too long." });
|
|
1358
|
+
const a = ht(e.search), c = gt(e.searchBy);
|
|
1359
|
+
if (c && !a)
|
|
1360
|
+
throw g({ statusCode: 422, statusMessage: "Search mode requires a search value." });
|
|
1361
|
+
if (!a) return { limit: r, pageToken: n };
|
|
1362
|
+
if (n)
|
|
1363
|
+
throw g({ statusCode: 422, statusMessage: "Exact search cannot be combined with a page token." });
|
|
1364
|
+
const i = c || "uid";
|
|
1365
|
+
if (!["uid", "email", "phone"].includes(i))
|
|
1366
|
+
throw g({ statusCode: 422, statusMessage: "Search mode must be uid, email, or phone." });
|
|
1367
|
+
if (i === "phone") {
|
|
1368
|
+
const d = gs.safeParse(a);
|
|
1369
|
+
if (!d.success)
|
|
1370
|
+
throw g({ statusCode: 422, statusMessage: "Phone search must use E.164 format." });
|
|
1371
|
+
return { limit: 1, search: { kind: "phone", value: d.data } };
|
|
1271
1372
|
}
|
|
1272
|
-
if (
|
|
1273
|
-
const
|
|
1274
|
-
if (!
|
|
1275
|
-
throw
|
|
1276
|
-
return { limit: 1, search: { kind: "email", value:
|
|
1373
|
+
if (i === "email") {
|
|
1374
|
+
const d = ps.safeParse(a);
|
|
1375
|
+
if (!d.success)
|
|
1376
|
+
throw g({ statusCode: 422, statusMessage: "Email search is invalid." });
|
|
1377
|
+
return { limit: 1, search: { kind: "email", value: d.data } };
|
|
1277
1378
|
}
|
|
1278
|
-
const o =
|
|
1379
|
+
const o = Qe.safeParse(a);
|
|
1279
1380
|
if (!o.success)
|
|
1280
|
-
throw
|
|
1381
|
+
throw g({ statusCode: 422, statusMessage: "UID search is invalid." });
|
|
1281
1382
|
return { limit: 1, search: { kind: "uid", value: o.data } };
|
|
1282
1383
|
}
|
|
1283
|
-
function
|
|
1384
|
+
function vs(e) {
|
|
1284
1385
|
const t = new TextEncoder().encode(e);
|
|
1285
1386
|
let s = "";
|
|
1286
|
-
for (const
|
|
1387
|
+
for (const r of t) s += String.fromCharCode(r);
|
|
1287
1388
|
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
1288
1389
|
}
|
|
1289
|
-
function
|
|
1390
|
+
function _s(e) {
|
|
1290
1391
|
if (!/^[A-Za-z0-9_-]{1,1024}$/.test(e))
|
|
1291
|
-
throw
|
|
1392
|
+
throw g({ statusCode: 400, statusMessage: "Firebase UID token is invalid." });
|
|
1292
1393
|
const t = e.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(e.length / 4) * 4, "=");
|
|
1293
1394
|
let s;
|
|
1294
1395
|
try {
|
|
1295
|
-
const
|
|
1296
|
-
s = new TextDecoder("utf-8", { fatal: !0 }).decode(
|
|
1396
|
+
const r = atob(t), n = Uint8Array.from(r, (a) => a.charCodeAt(0));
|
|
1397
|
+
s = new TextDecoder("utf-8", { fatal: !0 }).decode(n);
|
|
1297
1398
|
} catch {
|
|
1298
|
-
throw
|
|
1399
|
+
throw g({ statusCode: 400, statusMessage: "Firebase UID token is invalid." });
|
|
1299
1400
|
}
|
|
1300
|
-
if (
|
|
1301
|
-
throw
|
|
1401
|
+
if (vs(s) !== e || !Qe.safeParse(s).success)
|
|
1402
|
+
throw g({ statusCode: 400, statusMessage: "Firebase UID token is invalid." });
|
|
1302
1403
|
return s;
|
|
1303
1404
|
}
|
|
1304
|
-
function
|
|
1405
|
+
function Ze(e, t) {
|
|
1305
1406
|
return e.isAdmin === !0 || Array.isArray(e.permissions) && e.permissions.includes(t);
|
|
1306
1407
|
}
|
|
1307
|
-
function
|
|
1308
|
-
return t.kind === "uid" ||
|
|
1408
|
+
function Rs(e, t) {
|
|
1409
|
+
return t.kind === "uid" || Ze(e, $.viewPii);
|
|
1309
1410
|
}
|
|
1310
|
-
function
|
|
1411
|
+
function xs(e, t) {
|
|
1311
1412
|
let s = e.length ^ t.length;
|
|
1312
|
-
const
|
|
1313
|
-
for (let
|
|
1314
|
-
s |= (e.charCodeAt(
|
|
1413
|
+
const r = Math.max(e.length, t.length);
|
|
1414
|
+
for (let n = 0; n < r; n += 1)
|
|
1415
|
+
s |= (e.charCodeAt(n) || 0) ^ (t.charCodeAt(n) || 0);
|
|
1315
1416
|
return s === 0;
|
|
1316
1417
|
}
|
|
1317
|
-
function
|
|
1318
|
-
if (!
|
|
1319
|
-
throw
|
|
1418
|
+
function H(e, t) {
|
|
1419
|
+
if (!Ze(e, t))
|
|
1420
|
+
throw g({ statusCode: 403, statusMessage: "You do not have permission to perform this action." });
|
|
1320
1421
|
}
|
|
1321
|
-
function
|
|
1422
|
+
function Lt(e) {
|
|
1322
1423
|
if (!e) return null;
|
|
1323
1424
|
const t = e.lastIndexOf("@");
|
|
1324
1425
|
if (t <= 0) return "••••";
|
|
1325
|
-
const s = e.slice(0, t),
|
|
1326
|
-
return `${s.slice(0, 1)}•••@${a.slice(0, 1)}•••${
|
|
1426
|
+
const s = e.slice(0, t), r = e.slice(t + 1), n = r.lastIndexOf("."), a = n > 0 ? r.slice(0, n) : r, c = n > 0 ? r.slice(n) : "";
|
|
1427
|
+
return `${s.slice(0, 1)}•••@${a.slice(0, 1)}•••${c}`;
|
|
1327
1428
|
}
|
|
1328
|
-
function
|
|
1429
|
+
function Fs(e) {
|
|
1329
1430
|
return e ? `+••••••${e.replace(/\D/g, "").slice(-2)}` : null;
|
|
1330
1431
|
}
|
|
1331
|
-
function
|
|
1432
|
+
function Ns(e) {
|
|
1332
1433
|
return e.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[redacted-email]").replace(/\+[1-9][\d\s().-]{5,}\d/g, "[redacted-phone]").replace(
|
|
1333
1434
|
/\b(password|passcode|token|secret|api[-_\s]?key|authorization)\b\s*[:=]?\s*\S+/gi,
|
|
1334
1435
|
"$1 [redacted]"
|
|
1335
1436
|
);
|
|
1336
1437
|
}
|
|
1337
|
-
function
|
|
1438
|
+
function zt(e) {
|
|
1338
1439
|
return e ? `${e.slice(0, 1)}•••` : null;
|
|
1339
1440
|
}
|
|
1340
|
-
function
|
|
1441
|
+
function B(e) {
|
|
1341
1442
|
return typeof e == "string" ? e : null;
|
|
1342
1443
|
}
|
|
1343
|
-
function
|
|
1444
|
+
function Es(e) {
|
|
1344
1445
|
const t = [];
|
|
1345
1446
|
if (Array.isArray(e?.providerData))
|
|
1346
1447
|
for (const s of e.providerData) {
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1448
|
+
const r = B(s?.providerId);
|
|
1449
|
+
r && t.push(r);
|
|
1349
1450
|
}
|
|
1350
1451
|
return [...new Set(t)].sort();
|
|
1351
1452
|
}
|
|
1352
|
-
function
|
|
1453
|
+
function Vt(e) {
|
|
1353
1454
|
return Array.isArray(e?.multiFactor?.enrolledFactors) ? e.multiFactor.enrolledFactors.length : 0;
|
|
1354
1455
|
}
|
|
1355
|
-
function
|
|
1356
|
-
return t.manageRoleClaim ?
|
|
1456
|
+
function Xe(e, t) {
|
|
1457
|
+
return t.manageRoleClaim ? B(e?.customClaims?.[t.roleClaimKey]) : null;
|
|
1357
1458
|
}
|
|
1358
|
-
function
|
|
1459
|
+
function Ps() {
|
|
1359
1460
|
return {
|
|
1360
1461
|
enabled: !1,
|
|
1361
1462
|
exists: !1,
|
|
@@ -1365,40 +1466,40 @@ function Dt() {
|
|
|
1365
1466
|
status: null
|
|
1366
1467
|
};
|
|
1367
1468
|
}
|
|
1368
|
-
function
|
|
1369
|
-
const
|
|
1469
|
+
function ks(e, t, s) {
|
|
1470
|
+
const r = e ? B(e[s.profileNameField]) : null, n = e ? B(e[s.profileEmailField]) : null;
|
|
1370
1471
|
return {
|
|
1371
1472
|
enabled: !0,
|
|
1372
1473
|
exists: !!e,
|
|
1373
|
-
name: t ?
|
|
1374
|
-
email: t ?
|
|
1375
|
-
role: t && e ?
|
|
1376
|
-
status: t && e ?
|
|
1474
|
+
name: t ? r : zt(r),
|
|
1475
|
+
email: t ? n : Lt(n),
|
|
1476
|
+
role: t && e ? B(e[s.profileRoleField]) : null,
|
|
1477
|
+
status: t && e ? B(e[s.profileStatusField]) : null
|
|
1377
1478
|
};
|
|
1378
1479
|
}
|
|
1379
|
-
function
|
|
1380
|
-
const s =
|
|
1480
|
+
function je(e, t) {
|
|
1481
|
+
const s = B(e?.email), r = B(e?.displayName), n = B(e?.phoneNumber);
|
|
1381
1482
|
let a;
|
|
1382
|
-
return t.config.linkFirestoreProfiles ? t.profileAvailable === !1 ? a = null : a =
|
|
1483
|
+
return t.config.linkFirestoreProfiles ? t.profileAvailable === !1 ? a = null : a = ks(t.profile || null, t.canViewPii, t.config) : a = Ps(), {
|
|
1383
1484
|
uid: String(e?.uid || ""),
|
|
1384
|
-
displayName: t.canViewPii ?
|
|
1385
|
-
email: t.canViewPii ? s :
|
|
1386
|
-
phoneNumber: t.canViewPii ?
|
|
1387
|
-
photoURL: t.canViewPii ?
|
|
1485
|
+
displayName: t.canViewPii ? r : zt(r),
|
|
1486
|
+
email: t.canViewPii ? s : Lt(s),
|
|
1487
|
+
phoneNumber: t.canViewPii ? n : Fs(n),
|
|
1488
|
+
photoURL: t.canViewPii ? B(e?.photoURL) : null,
|
|
1388
1489
|
emailVerified: e?.emailVerified === !0,
|
|
1389
1490
|
disabled: e?.disabled === !0,
|
|
1390
|
-
providers:
|
|
1391
|
-
createdAt:
|
|
1392
|
-
lastSignInAt:
|
|
1393
|
-
lastRefreshAt:
|
|
1394
|
-
mfaFactorCount:
|
|
1395
|
-
role:
|
|
1491
|
+
providers: Es(e),
|
|
1492
|
+
createdAt: B(e?.metadata?.creationTime),
|
|
1493
|
+
lastSignInAt: B(e?.metadata?.lastSignInTime),
|
|
1494
|
+
lastRefreshAt: B(e?.metadata?.lastRefreshTime),
|
|
1495
|
+
mfaFactorCount: Vt(e),
|
|
1496
|
+
role: Xe(e, t.config),
|
|
1396
1497
|
profile: a
|
|
1397
1498
|
};
|
|
1398
1499
|
}
|
|
1399
|
-
function
|
|
1500
|
+
function yt(e, t) {
|
|
1400
1501
|
if (!e) return null;
|
|
1401
|
-
const s =
|
|
1502
|
+
const s = je(e, {
|
|
1402
1503
|
canViewPii: !1,
|
|
1403
1504
|
config: t,
|
|
1404
1505
|
profileAvailable: !1
|
|
@@ -1415,27 +1516,27 @@ function Pe(e, t) {
|
|
|
1415
1516
|
role: s.role
|
|
1416
1517
|
};
|
|
1417
1518
|
}
|
|
1418
|
-
function
|
|
1419
|
-
const
|
|
1420
|
-
return s === null ? delete
|
|
1519
|
+
function Us(e, t, s) {
|
|
1520
|
+
const r = { ...e || {} };
|
|
1521
|
+
return s === null ? delete r[t] : r[t] = s, r;
|
|
1421
1522
|
}
|
|
1422
|
-
function
|
|
1523
|
+
function wt(e, t) {
|
|
1423
1524
|
const s = {
|
|
1424
1525
|
[t.profileUidField]: String(e.uid),
|
|
1425
1526
|
[t.profileStatusField]: e.disabled === !0 ? "disabled" : "active"
|
|
1426
1527
|
};
|
|
1427
1528
|
typeof e.displayName == "string" && (s[t.profileNameField] = e.displayName), typeof e.email == "string" && (s[t.profileEmailField] = e.email);
|
|
1428
|
-
const
|
|
1429
|
-
return
|
|
1529
|
+
const r = Xe(e, t);
|
|
1530
|
+
return r !== null && (s[t.profileRoleField] = r), s;
|
|
1430
1531
|
}
|
|
1431
|
-
function
|
|
1532
|
+
function xe(e) {
|
|
1432
1533
|
if (!e || typeof e != "object") return;
|
|
1433
1534
|
const t = e;
|
|
1434
1535
|
if (typeof t.code == "string") return t.code;
|
|
1435
1536
|
if (typeof t.errorInfo?.code == "string") return t.errorInfo.code;
|
|
1436
1537
|
}
|
|
1437
|
-
function
|
|
1438
|
-
const s =
|
|
1538
|
+
function Os(e, t = "Firebase Authentication request failed.") {
|
|
1539
|
+
const s = xe(e);
|
|
1439
1540
|
return s === "auth/user-not-found" ? { statusCode: 404, statusMessage: "Firebase user not found." } : s && [
|
|
1440
1541
|
"auth/email-already-exists",
|
|
1441
1542
|
"auth/phone-number-already-exists",
|
|
@@ -1449,91 +1550,91 @@ function Rt(e, t = "Firebase Authentication request failed.") {
|
|
|
1449
1550
|
"auth/configuration-not-found"
|
|
1450
1551
|
].includes(s) ? { statusCode: 424, statusMessage: "The Firebase integration is not authorized for this operation." } : { statusCode: 502, statusMessage: t };
|
|
1451
1552
|
}
|
|
1452
|
-
function
|
|
1553
|
+
function W(e) {
|
|
1453
1554
|
const t = e?.context?.module;
|
|
1454
1555
|
if (!t || !t.supabase || typeof t.instanceId != "string" || typeof t.projectId != "string" || typeof t.userId != "string")
|
|
1455
|
-
throw
|
|
1556
|
+
throw g({ statusCode: 500, statusMessage: "Firebase Auth Manager request context is unavailable." });
|
|
1456
1557
|
return {
|
|
1457
1558
|
...t,
|
|
1458
1559
|
isAdmin: t.isAdmin === !0,
|
|
1459
1560
|
permissions: Array.isArray(t.permissions) ? t.permissions : []
|
|
1460
1561
|
};
|
|
1461
1562
|
}
|
|
1462
|
-
async function
|
|
1463
|
-
const s = t.safeParse(await
|
|
1563
|
+
async function X(e, t) {
|
|
1564
|
+
const s = t.safeParse(await j(e));
|
|
1464
1565
|
if (!s.success)
|
|
1465
|
-
throw
|
|
1566
|
+
throw g({
|
|
1466
1567
|
statusCode: 422,
|
|
1467
1568
|
statusMessage: s.error.issues[0]?.message || "Request body is invalid."
|
|
1468
1569
|
});
|
|
1469
1570
|
return s.data;
|
|
1470
1571
|
}
|
|
1471
|
-
function
|
|
1472
|
-
const t =
|
|
1572
|
+
function ee(e) {
|
|
1573
|
+
const t = U(e, "uid");
|
|
1473
1574
|
if (typeof t != "string")
|
|
1474
|
-
throw
|
|
1475
|
-
return
|
|
1575
|
+
throw g({ statusCode: 400, statusMessage: "A valid Firebase UID is required." });
|
|
1576
|
+
return _s(t);
|
|
1476
1577
|
}
|
|
1477
|
-
function
|
|
1578
|
+
function G(e, t) {
|
|
1478
1579
|
if (typeof e?.statusCode == "number" && typeof e?.statusMessage == "string")
|
|
1479
1580
|
throw e;
|
|
1480
|
-
const s =
|
|
1481
|
-
throw console.error("Firebase Auth Manager request failed",
|
|
1581
|
+
const s = Os(e, t), r = xe(e);
|
|
1582
|
+
throw console.error("Firebase Auth Manager request failed", r ? { code: r } : { code: "unknown" }), g(s);
|
|
1482
1583
|
}
|
|
1483
|
-
function
|
|
1484
|
-
return
|
|
1584
|
+
function js(e) {
|
|
1585
|
+
return xe(e) === "auth/user-not-found";
|
|
1485
1586
|
}
|
|
1486
|
-
function
|
|
1487
|
-
return
|
|
1587
|
+
function Ye(e) {
|
|
1588
|
+
return Ze(e, $.viewPii);
|
|
1488
1589
|
}
|
|
1489
|
-
function
|
|
1590
|
+
function qs(e, t) {
|
|
1490
1591
|
const s = [];
|
|
1491
|
-
for (let
|
|
1492
|
-
s.push(e.slice(
|
|
1592
|
+
for (let r = 0; r < e.length; r += t)
|
|
1593
|
+
s.push(e.slice(r, r + t));
|
|
1493
1594
|
return s;
|
|
1494
1595
|
}
|
|
1495
|
-
function
|
|
1596
|
+
function qe(e) {
|
|
1496
1597
|
return e.length > 0 && e !== "." && e !== ".." && !e.includes("/") && !/^__.*__$/.test(e);
|
|
1497
1598
|
}
|
|
1498
|
-
async function
|
|
1499
|
-
const
|
|
1500
|
-
if (!s.linkFirestoreProfiles || t.length === 0) return
|
|
1501
|
-
const
|
|
1502
|
-
for (let
|
|
1503
|
-
const l = o[
|
|
1504
|
-
f && l?.exists &&
|
|
1599
|
+
async function $s(e, t, s) {
|
|
1600
|
+
const r = /* @__PURE__ */ new Map();
|
|
1601
|
+
if (!s.linkFirestoreProfiles || t.length === 0) return r;
|
|
1602
|
+
const n = [...new Set(t.map((u) => String(u.uid)).filter(Boolean))], a = e.collection(s.profileCollection), c = n.filter(qe), i = c.map((u) => a.doc(u)), o = i.length === 0 ? [] : typeof e.getAll == "function" ? await e.getAll(...i) : await Promise.all(i.map((u) => u.get()));
|
|
1603
|
+
for (let u = 0; u < o.length; u += 1) {
|
|
1604
|
+
const l = o[u], f = c[u];
|
|
1605
|
+
f && l?.exists && r.set(f, l.data());
|
|
1505
1606
|
}
|
|
1506
|
-
const
|
|
1507
|
-
for (const
|
|
1508
|
-
const l = await a.where(s.profileUidField, "in",
|
|
1607
|
+
const d = n.filter((u) => !r.has(u));
|
|
1608
|
+
for (const u of qs(d, 30)) {
|
|
1609
|
+
const l = await a.where(s.profileUidField, "in", u).get();
|
|
1509
1610
|
for (const f of l.docs || []) {
|
|
1510
|
-
const
|
|
1511
|
-
typeof m == "string" &&
|
|
1611
|
+
const p = f.data(), m = p[s.profileUidField];
|
|
1612
|
+
typeof m == "string" && u.includes(m) && !r.has(m) && r.set(m, p);
|
|
1512
1613
|
}
|
|
1513
1614
|
}
|
|
1514
|
-
return
|
|
1515
|
-
}
|
|
1516
|
-
async function
|
|
1517
|
-
const
|
|
1518
|
-
if (
|
|
1519
|
-
const
|
|
1520
|
-
if (
|
|
1521
|
-
return { id: String(
|
|
1615
|
+
return r;
|
|
1616
|
+
}
|
|
1617
|
+
async function bt(e, t, s) {
|
|
1618
|
+
const r = e.collection(s.profileCollection);
|
|
1619
|
+
if (qe(t)) {
|
|
1620
|
+
const c = await r.doc(t).get();
|
|
1621
|
+
if (c.exists)
|
|
1622
|
+
return { id: String(c.id), data: c.data() };
|
|
1522
1623
|
}
|
|
1523
|
-
const a = (await
|
|
1624
|
+
const a = (await r.where(s.profileUidField, "==", t).limit(1).get()).docs?.[0];
|
|
1524
1625
|
return a ? { id: String(a.id), data: a.data() } : null;
|
|
1525
1626
|
}
|
|
1526
|
-
async function
|
|
1527
|
-
const s =
|
|
1528
|
-
if (
|
|
1529
|
-
throw
|
|
1530
|
-
if (!
|
|
1531
|
-
throw
|
|
1532
|
-
const a =
|
|
1533
|
-
if (
|
|
1534
|
-
throw
|
|
1535
|
-
if (!
|
|
1536
|
-
throw
|
|
1627
|
+
async function Y(e, t) {
|
|
1628
|
+
const s = W(e), { data: r, error: n } = await s.supabase.from("project_modules").select("type, config").eq("id", s.instanceId).eq("project_id", s.projectId).maybeSingle();
|
|
1629
|
+
if (n)
|
|
1630
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load Firebase Auth Manager configuration." });
|
|
1631
|
+
if (!r || r.type !== Ot || !r.config)
|
|
1632
|
+
throw g({ statusCode: 404, statusMessage: "Firebase Auth Manager module instance not found." });
|
|
1633
|
+
const a = Ds(r.config), { data: c, error: i } = await s.supabase.from("integrations").select("id, project_id, type").eq("id", a.project).eq("project_id", s.projectId).eq("type", "FIREBASE").maybeSingle();
|
|
1634
|
+
if (i)
|
|
1635
|
+
throw g({ statusCode: 500, statusMessage: "Failed to verify the Firebase integration." });
|
|
1636
|
+
if (!c)
|
|
1637
|
+
throw g({ statusCode: 424, statusMessage: "The configured Firebase integration is unavailable for this project." });
|
|
1537
1638
|
const o = await t.initFirebase(e, a.project);
|
|
1538
1639
|
return {
|
|
1539
1640
|
module: s,
|
|
@@ -1544,427 +1645,427 @@ async function O(e, t) {
|
|
|
1544
1645
|
firestore: o.firestore()
|
|
1545
1646
|
};
|
|
1546
1647
|
}
|
|
1547
|
-
async function
|
|
1548
|
-
const
|
|
1648
|
+
async function te(e, t, s) {
|
|
1649
|
+
const r = {
|
|
1549
1650
|
project_id: t.module.projectId,
|
|
1550
1651
|
module_id: t.module.instanceId,
|
|
1551
1652
|
user_id: t.module.userId,
|
|
1552
1653
|
message: `Firebase Auth: ${s.action}`,
|
|
1553
1654
|
level: "INFO",
|
|
1554
1655
|
params: {
|
|
1555
|
-
source:
|
|
1656
|
+
source: Ot,
|
|
1556
1657
|
action: s.action,
|
|
1557
1658
|
targetUid: s.targetUid,
|
|
1558
1659
|
integrationId: t.integrationId,
|
|
1559
|
-
reason:
|
|
1560
|
-
before:
|
|
1561
|
-
after:
|
|
1660
|
+
reason: Ns(s.reason),
|
|
1661
|
+
before: yt(s.before, t.config),
|
|
1662
|
+
after: yt(s.after, t.config),
|
|
1562
1663
|
...s.details || {}
|
|
1563
1664
|
}
|
|
1564
1665
|
};
|
|
1565
1666
|
try {
|
|
1566
|
-
const { error:
|
|
1567
|
-
return
|
|
1568
|
-
} catch (
|
|
1569
|
-
const a =
|
|
1667
|
+
const { error: n } = await t.module.supabase.from("project_logs").insert(r);
|
|
1668
|
+
return n ? (console.error("Firebase Auth Manager audit insert failed", { code: n.code || "unknown" }), !1) : !0;
|
|
1669
|
+
} catch (n) {
|
|
1670
|
+
const a = n && typeof n == "object" && "code" in n ? String(n.code) : "unknown";
|
|
1570
1671
|
return console.error("Firebase Auth Manager audit insert failed", { code: a }), !1;
|
|
1571
1672
|
}
|
|
1572
1673
|
}
|
|
1573
|
-
async function
|
|
1674
|
+
async function Bs(e, t) {
|
|
1574
1675
|
try {
|
|
1575
1676
|
return t.kind === "email" ? [await e.getUserByEmail(t.value)] : t.kind === "phone" ? [await e.getUserByPhoneNumber(t.value)] : [await e.getUser(t.value)];
|
|
1576
1677
|
} catch (s) {
|
|
1577
|
-
if (
|
|
1678
|
+
if (js(s)) return [];
|
|
1578
1679
|
throw s;
|
|
1579
1680
|
}
|
|
1580
1681
|
}
|
|
1581
|
-
async function
|
|
1682
|
+
async function Ct(e, t, s) {
|
|
1582
1683
|
try {
|
|
1583
1684
|
return await e.getUser(t);
|
|
1584
|
-
} catch (
|
|
1685
|
+
} catch (r) {
|
|
1585
1686
|
return console.error("Firebase Auth Manager post-mutation refresh failed", {
|
|
1586
|
-
code:
|
|
1687
|
+
code: xe(r) || "unknown"
|
|
1587
1688
|
}), s;
|
|
1588
1689
|
}
|
|
1589
1690
|
}
|
|
1590
|
-
async function
|
|
1591
|
-
let s = /* @__PURE__ */ new Map(),
|
|
1691
|
+
async function ue(e, t) {
|
|
1692
|
+
let s = /* @__PURE__ */ new Map(), r = !0;
|
|
1592
1693
|
if (t.config.linkFirestoreProfiles)
|
|
1593
1694
|
try {
|
|
1594
|
-
s = await
|
|
1595
|
-
} catch (
|
|
1596
|
-
|
|
1597
|
-
const a =
|
|
1695
|
+
s = await $s(t.firestore, e, t.config);
|
|
1696
|
+
} catch (n) {
|
|
1697
|
+
r = !1;
|
|
1698
|
+
const a = xe(n);
|
|
1598
1699
|
console.error("Firebase Auth Manager profile join failed", { code: a || "unknown" });
|
|
1599
1700
|
}
|
|
1600
|
-
return e.map((
|
|
1601
|
-
canViewPii:
|
|
1701
|
+
return e.map((n) => je(n, {
|
|
1702
|
+
canViewPii: Ye(t.module),
|
|
1602
1703
|
config: t.config,
|
|
1603
|
-
profile: s.get(String(
|
|
1604
|
-
profileAvailable:
|
|
1704
|
+
profile: s.get(String(n.uid)) || null,
|
|
1705
|
+
profileAvailable: r
|
|
1605
1706
|
}));
|
|
1606
1707
|
}
|
|
1607
|
-
function
|
|
1608
|
-
const t =
|
|
1609
|
-
return t.get("/users",
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1708
|
+
function va(e) {
|
|
1709
|
+
const t = J();
|
|
1710
|
+
return t.get("/users", b(async (s) => {
|
|
1711
|
+
const r = W(s);
|
|
1712
|
+
H(r, $.viewUsers);
|
|
1612
1713
|
try {
|
|
1613
|
-
const
|
|
1614
|
-
if (a.search && !
|
|
1615
|
-
throw
|
|
1714
|
+
const n = await Y(s, e), a = Ts(v(s), n.config.pageSize);
|
|
1715
|
+
if (a.search && !Rs(n.module, a.search))
|
|
1716
|
+
throw g({
|
|
1616
1717
|
statusCode: 403,
|
|
1617
1718
|
statusMessage: "Viewing personal data is required to search by email or phone number."
|
|
1618
1719
|
});
|
|
1619
|
-
let
|
|
1720
|
+
let c, i = null;
|
|
1620
1721
|
if (a.search)
|
|
1621
|
-
|
|
1722
|
+
c = await Bs(n.auth, a.search);
|
|
1622
1723
|
else {
|
|
1623
|
-
const o = await
|
|
1624
|
-
|
|
1724
|
+
const o = await n.auth.listUsers(a.limit, a.pageToken);
|
|
1725
|
+
c = Array.isArray(o.users) ? o.users : [], i = typeof o.pageToken == "string" ? o.pageToken : null;
|
|
1625
1726
|
}
|
|
1626
1727
|
return {
|
|
1627
|
-
users: await
|
|
1628
|
-
nextPageToken:
|
|
1728
|
+
users: await ue(c, n),
|
|
1729
|
+
nextPageToken: i
|
|
1629
1730
|
};
|
|
1630
|
-
} catch (
|
|
1631
|
-
|
|
1731
|
+
} catch (n) {
|
|
1732
|
+
G(n, "Failed to list Firebase users.");
|
|
1632
1733
|
}
|
|
1633
|
-
})), t.get("/users/:uid",
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1636
|
-
const
|
|
1734
|
+
})), t.get("/users/:uid", b(async (s) => {
|
|
1735
|
+
const r = W(s);
|
|
1736
|
+
H(r, $.viewUsers);
|
|
1737
|
+
const n = ee(s);
|
|
1637
1738
|
try {
|
|
1638
|
-
const a = await
|
|
1639
|
-
return { user:
|
|
1739
|
+
const a = await Y(s, e), c = await a.auth.getUser(n), [i] = await ue([c], a);
|
|
1740
|
+
return { user: i };
|
|
1640
1741
|
} catch (a) {
|
|
1641
|
-
|
|
1742
|
+
G(a, "Failed to load the Firebase user.");
|
|
1642
1743
|
}
|
|
1643
|
-
})), t.post("/users",
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1744
|
+
})), t.post("/users", b(async (s) => {
|
|
1745
|
+
const r = W(s);
|
|
1746
|
+
H(r, $.createUsers);
|
|
1747
|
+
const n = await X(s, hs);
|
|
1748
|
+
n.createProfile && H(r, $.manageProfiles);
|
|
1648
1749
|
try {
|
|
1649
|
-
const a = await
|
|
1650
|
-
if (
|
|
1651
|
-
throw
|
|
1652
|
-
if (
|
|
1653
|
-
throw
|
|
1750
|
+
const a = await Y(s, e);
|
|
1751
|
+
if (n.createProfile && !a.config.linkFirestoreProfiles)
|
|
1752
|
+
throw g({ statusCode: 409, statusMessage: "Firestore profile linking is disabled for this module." });
|
|
1753
|
+
if (n.uid && a.config.linkFirestoreProfiles && await bt(a.firestore, n.uid, a.config))
|
|
1754
|
+
throw g({
|
|
1654
1755
|
statusCode: 409,
|
|
1655
1756
|
statusMessage: "A retained Firestore profile already uses this UID. Resolve that profile before reusing the Authentication UID."
|
|
1656
1757
|
});
|
|
1657
|
-
const
|
|
1658
|
-
email:
|
|
1659
|
-
password:
|
|
1660
|
-
uid:
|
|
1661
|
-
displayName:
|
|
1662
|
-
phoneNumber:
|
|
1663
|
-
emailVerified:
|
|
1664
|
-
disabled:
|
|
1758
|
+
const c = await a.auth.createUser({
|
|
1759
|
+
email: n.email,
|
|
1760
|
+
password: n.password,
|
|
1761
|
+
uid: n.uid,
|
|
1762
|
+
displayName: n.displayName,
|
|
1763
|
+
phoneNumber: n.phoneNumber,
|
|
1764
|
+
emailVerified: n.emailVerified,
|
|
1765
|
+
disabled: n.disabled
|
|
1665
1766
|
});
|
|
1666
|
-
let
|
|
1667
|
-
if (
|
|
1668
|
-
|
|
1767
|
+
let i = null, o = !1, d = null;
|
|
1768
|
+
if (n.createProfile) {
|
|
1769
|
+
i = wt(c, a.config);
|
|
1669
1770
|
try {
|
|
1670
1771
|
const l = a.firestore.collection(a.config.profileCollection);
|
|
1671
|
-
await (
|
|
1772
|
+
await (qe(c.uid) ? l.doc(c.uid) : l.doc()).create(i), o = !0;
|
|
1672
1773
|
} catch {
|
|
1673
|
-
|
|
1774
|
+
i = null, d = "The Authentication user was created, but its Firestore profile could not be created.";
|
|
1674
1775
|
}
|
|
1675
1776
|
}
|
|
1676
|
-
const
|
|
1777
|
+
const u = await te(s, a, {
|
|
1677
1778
|
action: "user.create",
|
|
1678
|
-
targetUid:
|
|
1779
|
+
targetUid: c.uid,
|
|
1679
1780
|
reason: "Created through Firebase Auth Manager.",
|
|
1680
1781
|
before: null,
|
|
1681
|
-
after:
|
|
1782
|
+
after: c,
|
|
1682
1783
|
details: {
|
|
1683
|
-
profileRequested:
|
|
1784
|
+
profileRequested: n.createProfile === !0,
|
|
1684
1785
|
profileCreated: o
|
|
1685
1786
|
}
|
|
1686
1787
|
});
|
|
1687
|
-
return
|
|
1688
|
-
user:
|
|
1689
|
-
canViewPii:
|
|
1788
|
+
return os(s, 201), {
|
|
1789
|
+
user: je(c, {
|
|
1790
|
+
canViewPii: Ye(a.module),
|
|
1690
1791
|
config: a.config,
|
|
1691
|
-
profile:
|
|
1792
|
+
profile: i
|
|
1692
1793
|
}),
|
|
1693
1794
|
profileCreated: o,
|
|
1694
|
-
profileError:
|
|
1695
|
-
auditRecorded:
|
|
1795
|
+
profileError: d,
|
|
1796
|
+
auditRecorded: u
|
|
1696
1797
|
};
|
|
1697
1798
|
} catch (a) {
|
|
1698
|
-
|
|
1799
|
+
G(a, "Failed to create the Firebase user.");
|
|
1699
1800
|
}
|
|
1700
|
-
})), t.patch("/users/:uid",
|
|
1701
|
-
const
|
|
1702
|
-
|
|
1703
|
-
const
|
|
1801
|
+
})), t.patch("/users/:uid", b(async (s) => {
|
|
1802
|
+
const r = W(s);
|
|
1803
|
+
H(r, $.editUsers);
|
|
1804
|
+
const n = ee(s), a = await X(s, ys);
|
|
1704
1805
|
try {
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
const
|
|
1806
|
+
const c = await Y(s, e), i = await c.auth.getUser(n), o = {};
|
|
1807
|
+
re(a, "displayName") && (o.displayName = a.displayName), re(a, "email") && (o.email = a.email), re(a, "phoneNumber") && (o.phoneNumber = a.phoneNumber), re(a, "emailVerified") && (o.emailVerified = a.emailVerified);
|
|
1808
|
+
const d = await c.auth.updateUser(n, o), u = await te(s, c, {
|
|
1708
1809
|
action: "user.update",
|
|
1709
|
-
targetUid:
|
|
1810
|
+
targetUid: n,
|
|
1710
1811
|
reason: a.reason,
|
|
1711
|
-
before:
|
|
1712
|
-
after:
|
|
1812
|
+
before: i,
|
|
1813
|
+
after: d,
|
|
1713
1814
|
details: { changedFieldCount: Object.keys(o).length }
|
|
1714
|
-
}), [l] = await
|
|
1715
|
-
return { user: l, auditRecorded:
|
|
1716
|
-
} catch (
|
|
1717
|
-
|
|
1815
|
+
}), [l] = await ue([d], c);
|
|
1816
|
+
return { user: l, auditRecorded: u };
|
|
1817
|
+
} catch (c) {
|
|
1818
|
+
G(c, "Failed to update the Firebase user.");
|
|
1718
1819
|
}
|
|
1719
|
-
})), t.post("/users/:uid/status",
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1820
|
+
})), t.post("/users/:uid/status", b(async (s) => {
|
|
1821
|
+
const r = W(s);
|
|
1822
|
+
H(r, $.manageStatus);
|
|
1823
|
+
const n = ee(s), a = await X(s, ws);
|
|
1723
1824
|
try {
|
|
1724
|
-
const
|
|
1825
|
+
const c = await Y(s, e), i = await c.auth.getUser(n), o = i.disabled !== a.disabled, d = o ? await c.auth.updateUser(n, { disabled: a.disabled }) : i, u = await te(s, c, {
|
|
1725
1826
|
action: a.disabled ? "user.disable" : "user.enable",
|
|
1726
|
-
targetUid:
|
|
1827
|
+
targetUid: n,
|
|
1727
1828
|
reason: a.reason,
|
|
1728
|
-
before:
|
|
1729
|
-
after:
|
|
1829
|
+
before: i,
|
|
1830
|
+
after: d,
|
|
1730
1831
|
details: { changed: o }
|
|
1731
|
-
}), [l] = await
|
|
1732
|
-
return { user: l, changed: o, auditRecorded:
|
|
1733
|
-
} catch (
|
|
1734
|
-
|
|
1832
|
+
}), [l] = await ue([d], c);
|
|
1833
|
+
return { user: l, changed: o, auditRecorded: u };
|
|
1834
|
+
} catch (c) {
|
|
1835
|
+
G(c, "Failed to change the Firebase user status.");
|
|
1735
1836
|
}
|
|
1736
|
-
})), t.post("/users/:uid/revoke-sessions",
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
const
|
|
1837
|
+
})), t.post("/users/:uid/revoke-sessions", b(async (s) => {
|
|
1838
|
+
const r = W(s);
|
|
1839
|
+
H(r, $.manageSessions);
|
|
1840
|
+
const n = ee(s), a = await X(s, $e);
|
|
1740
1841
|
try {
|
|
1741
|
-
const
|
|
1742
|
-
await
|
|
1743
|
-
const o = await
|
|
1842
|
+
const c = await Y(s, e), i = await c.auth.getUser(n);
|
|
1843
|
+
await c.auth.revokeRefreshTokens(n);
|
|
1844
|
+
const o = await te(s, c, {
|
|
1744
1845
|
action: "user.revoke_sessions",
|
|
1745
|
-
targetUid:
|
|
1846
|
+
targetUid: n,
|
|
1746
1847
|
reason: a.reason,
|
|
1747
|
-
before:
|
|
1748
|
-
after:
|
|
1848
|
+
before: i,
|
|
1849
|
+
after: i,
|
|
1749
1850
|
details: { sessionsRevoked: !0 }
|
|
1750
|
-
}),
|
|
1751
|
-
return { user:
|
|
1752
|
-
} catch (
|
|
1753
|
-
|
|
1851
|
+
}), d = await Ct(c.auth, n, i), [u] = await ue([d], c);
|
|
1852
|
+
return { user: u, sessionsRevoked: !0, auditRecorded: o };
|
|
1853
|
+
} catch (c) {
|
|
1854
|
+
G(c, "Failed to revoke Firebase user sessions.");
|
|
1754
1855
|
}
|
|
1755
|
-
})), t.post("/users/:uid/reset-mfa",
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1758
|
-
const
|
|
1856
|
+
})), t.post("/users/:uid/reset-mfa", b(async (s) => {
|
|
1857
|
+
const r = W(s);
|
|
1858
|
+
H(r, $.manageMfa);
|
|
1859
|
+
const n = ee(s), a = await X(s, $e);
|
|
1759
1860
|
try {
|
|
1760
|
-
const
|
|
1861
|
+
const c = await Y(s, e), i = await c.auth.getUser(n), o = Vt(i) > 0, d = o ? await c.auth.updateUser(n, { multiFactor: { enrolledFactors: [] } }) : i, u = await te(s, c, {
|
|
1761
1862
|
action: "user.reset_mfa",
|
|
1762
|
-
targetUid:
|
|
1863
|
+
targetUid: n,
|
|
1763
1864
|
reason: a.reason,
|
|
1764
|
-
before:
|
|
1765
|
-
after:
|
|
1865
|
+
before: i,
|
|
1866
|
+
after: d,
|
|
1766
1867
|
details: { changed: o }
|
|
1767
|
-
}), [l] = await
|
|
1768
|
-
return { user: l, changed: o, auditRecorded:
|
|
1769
|
-
} catch (
|
|
1770
|
-
|
|
1868
|
+
}), [l] = await ue([d], c);
|
|
1869
|
+
return { user: l, changed: o, auditRecorded: u };
|
|
1870
|
+
} catch (c) {
|
|
1871
|
+
G(c, "Failed to reset Firebase multi-factor authentication.");
|
|
1771
1872
|
}
|
|
1772
|
-
})), t.post("/users/:uid/role",
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
const
|
|
1873
|
+
})), t.post("/users/:uid/role", b(async (s) => {
|
|
1874
|
+
const r = W(s);
|
|
1875
|
+
H(r, $.manageRoles);
|
|
1876
|
+
const n = ee(s), a = await X(s, bs);
|
|
1776
1877
|
try {
|
|
1777
|
-
const
|
|
1778
|
-
if (!
|
|
1779
|
-
throw
|
|
1780
|
-
if (a.role !== null && !
|
|
1781
|
-
throw
|
|
1782
|
-
const
|
|
1783
|
-
let
|
|
1784
|
-
if (
|
|
1785
|
-
const
|
|
1786
|
-
await
|
|
1878
|
+
const c = await Y(s, e);
|
|
1879
|
+
if (!c.config.manageRoleClaim)
|
|
1880
|
+
throw g({ statusCode: 409, statusMessage: "Role claim management is disabled for this module." });
|
|
1881
|
+
if (a.role !== null && !c.config.roleOptions.includes(a.role))
|
|
1882
|
+
throw g({ statusCode: 422, statusMessage: "Role must be one of the configured values." });
|
|
1883
|
+
const i = await c.auth.getUser(n), d = Xe(i, c.config) !== a.role;
|
|
1884
|
+
let u = i;
|
|
1885
|
+
if (d) {
|
|
1886
|
+
const p = Us(i.customClaims, c.config.roleClaimKey, a.role);
|
|
1887
|
+
await c.auth.setCustomUserClaims(n, p), u = { ...i, customClaims: p };
|
|
1787
1888
|
}
|
|
1788
|
-
const l = await
|
|
1889
|
+
const l = await te(s, c, {
|
|
1789
1890
|
action: "user.set_role",
|
|
1790
|
-
targetUid:
|
|
1891
|
+
targetUid: n,
|
|
1791
1892
|
reason: a.reason,
|
|
1792
|
-
before:
|
|
1793
|
-
after:
|
|
1794
|
-
details: { changed:
|
|
1893
|
+
before: i,
|
|
1894
|
+
after: u,
|
|
1895
|
+
details: { changed: d, role: a.role }
|
|
1795
1896
|
});
|
|
1796
|
-
|
|
1797
|
-
const [f] = await
|
|
1798
|
-
return { user: f, changed:
|
|
1799
|
-
} catch (
|
|
1800
|
-
|
|
1897
|
+
d && (u = await Ct(c.auth, n, u));
|
|
1898
|
+
const [f] = await ue([u], c);
|
|
1899
|
+
return { user: f, changed: d, auditRecorded: l };
|
|
1900
|
+
} catch (c) {
|
|
1901
|
+
G(c, "Failed to update the Firebase role claim.");
|
|
1801
1902
|
}
|
|
1802
|
-
})), t.post("/users/:uid/profile",
|
|
1803
|
-
const
|
|
1804
|
-
|
|
1805
|
-
const
|
|
1903
|
+
})), t.post("/users/:uid/profile", b(async (s) => {
|
|
1904
|
+
const r = W(s);
|
|
1905
|
+
H(r, $.manageProfiles);
|
|
1906
|
+
const n = ee(s), a = await X(s, $e);
|
|
1806
1907
|
try {
|
|
1807
|
-
const
|
|
1808
|
-
if (!
|
|
1809
|
-
throw
|
|
1810
|
-
const
|
|
1811
|
-
let
|
|
1908
|
+
const c = await Y(s, e);
|
|
1909
|
+
if (!c.config.linkFirestoreProfiles)
|
|
1910
|
+
throw g({ statusCode: 409, statusMessage: "Firestore profile linking is disabled for this module." });
|
|
1911
|
+
const i = await c.auth.getUser(n), o = await bt(c.firestore, n, c.config);
|
|
1912
|
+
let d = o?.data || null, u = !1;
|
|
1812
1913
|
if (!o) {
|
|
1813
|
-
|
|
1814
|
-
const f =
|
|
1815
|
-
await (
|
|
1914
|
+
d = wt(i, c.config);
|
|
1915
|
+
const f = c.firestore.collection(c.config.profileCollection);
|
|
1916
|
+
await (qe(n) ? f.doc(n) : f.doc()).create(d), u = !0;
|
|
1816
1917
|
}
|
|
1817
|
-
const l = await
|
|
1918
|
+
const l = await te(s, c, {
|
|
1818
1919
|
action: "user.create_profile",
|
|
1819
|
-
targetUid:
|
|
1920
|
+
targetUid: n,
|
|
1820
1921
|
reason: a.reason,
|
|
1821
|
-
before:
|
|
1822
|
-
after:
|
|
1823
|
-
details: { changed:
|
|
1922
|
+
before: i,
|
|
1923
|
+
after: i,
|
|
1924
|
+
details: { changed: u }
|
|
1824
1925
|
});
|
|
1825
1926
|
return {
|
|
1826
|
-
user:
|
|
1827
|
-
canViewPii:
|
|
1828
|
-
config:
|
|
1829
|
-
profile:
|
|
1927
|
+
user: je(i, {
|
|
1928
|
+
canViewPii: Ye(c.module),
|
|
1929
|
+
config: c.config,
|
|
1930
|
+
profile: d
|
|
1830
1931
|
}),
|
|
1831
|
-
profileCreated:
|
|
1932
|
+
profileCreated: u,
|
|
1832
1933
|
auditRecorded: l
|
|
1833
1934
|
};
|
|
1834
|
-
} catch (
|
|
1835
|
-
|
|
1935
|
+
} catch (c) {
|
|
1936
|
+
G(c, "Failed to create the Firestore profile.");
|
|
1836
1937
|
}
|
|
1837
|
-
})), t.delete("/users/:uid",
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
const
|
|
1841
|
-
if (!
|
|
1842
|
-
throw
|
|
1938
|
+
})), t.delete("/users/:uid", b(async (s) => {
|
|
1939
|
+
const r = W(s);
|
|
1940
|
+
H(r, $.deleteUsers);
|
|
1941
|
+
const n = ee(s), a = await X(s, Cs);
|
|
1942
|
+
if (!xs(n, a.confirmation))
|
|
1943
|
+
throw g({ statusCode: 422, statusMessage: "Deletion confirmation must exactly match the Firebase UID." });
|
|
1843
1944
|
try {
|
|
1844
|
-
const
|
|
1845
|
-
await
|
|
1846
|
-
const o = await
|
|
1945
|
+
const c = await Y(s, e), i = await c.auth.getUser(n);
|
|
1946
|
+
await c.auth.deleteUser(n);
|
|
1947
|
+
const o = await te(s, c, {
|
|
1847
1948
|
action: "user.delete",
|
|
1848
|
-
targetUid:
|
|
1949
|
+
targetUid: n,
|
|
1849
1950
|
reason: a.reason,
|
|
1850
|
-
before:
|
|
1951
|
+
before: i,
|
|
1851
1952
|
after: null,
|
|
1852
1953
|
details: { firestoreProfilePreserved: !0 }
|
|
1853
1954
|
});
|
|
1854
1955
|
return {
|
|
1855
|
-
uid:
|
|
1956
|
+
uid: n,
|
|
1856
1957
|
deleted: !0,
|
|
1857
1958
|
firestoreProfilePreserved: !0,
|
|
1858
1959
|
auditRecorded: o
|
|
1859
1960
|
};
|
|
1860
|
-
} catch (
|
|
1861
|
-
|
|
1961
|
+
} catch (c) {
|
|
1962
|
+
G(c, "Failed to delete the Firebase user.");
|
|
1862
1963
|
}
|
|
1863
1964
|
})), t.handler;
|
|
1864
1965
|
}
|
|
1865
|
-
const
|
|
1966
|
+
const et = 1, At = 700 * 1024, It = 20, Ls = 5e4, zs = 120, Vs = 80, Mt = 2e3, St = 120, Hs = "studio_forms", Ht = /* @__PURE__ */ new Set(["draft", "published", "archived"]), Ws = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]), L = {
|
|
1866
1967
|
view: "view-forms",
|
|
1867
1968
|
create: "create-form",
|
|
1868
1969
|
edit: "edit-form",
|
|
1869
1970
|
publish: "publish-form",
|
|
1870
1971
|
archive: "archive-form"
|
|
1871
1972
|
};
|
|
1872
|
-
function
|
|
1873
|
-
throw
|
|
1973
|
+
function R(e) {
|
|
1974
|
+
throw g({ statusCode: 400, statusMessage: e });
|
|
1874
1975
|
}
|
|
1875
|
-
function
|
|
1876
|
-
throw
|
|
1976
|
+
function ae(e) {
|
|
1977
|
+
throw g({ statusCode: 409, statusMessage: e });
|
|
1877
1978
|
}
|
|
1878
|
-
function
|
|
1879
|
-
(!e || typeof e != "object" || Array.isArray(e)) &&
|
|
1979
|
+
function tt(e, t) {
|
|
1980
|
+
(!e || typeof e != "object" || Array.isArray(e)) && R(`${t} must be an object.`);
|
|
1880
1981
|
const s = Object.getPrototypeOf(e);
|
|
1881
|
-
return s !== Object.prototype && s !== null &&
|
|
1982
|
+
return s !== Object.prototype && s !== null && R(`${t} must be a plain object.`), e;
|
|
1882
1983
|
}
|
|
1883
|
-
function
|
|
1884
|
-
typeof e != "string" &&
|
|
1885
|
-
const
|
|
1886
|
-
return
|
|
1984
|
+
function Wt(e, t, s) {
|
|
1985
|
+
typeof e != "string" && R(`${t} is required.`);
|
|
1986
|
+
const r = e.trim();
|
|
1987
|
+
return r || R(`${t} is required.`), r.length > s && R(`${t} cannot exceed ${s} characters.`), r;
|
|
1887
1988
|
}
|
|
1888
|
-
function
|
|
1989
|
+
function Gs(e) {
|
|
1889
1990
|
if (e == null) return "";
|
|
1890
|
-
typeof e != "string" &&
|
|
1991
|
+
typeof e != "string" && R("Form description must be a string.");
|
|
1891
1992
|
const t = e.trim();
|
|
1892
|
-
return t.length >
|
|
1993
|
+
return t.length > Mt && R(`Form description cannot exceed ${Mt} characters.`), t;
|
|
1893
1994
|
}
|
|
1894
|
-
function
|
|
1895
|
-
const t =
|
|
1896
|
-
return /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(t) ||
|
|
1995
|
+
function Ys(e) {
|
|
1996
|
+
const t = Wt(e, "Form key", Vs).toLowerCase();
|
|
1997
|
+
return /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(t) || R("Form key must start with a letter and contain only lowercase letters, numbers, hyphens, or underscores."), t;
|
|
1897
1998
|
}
|
|
1898
|
-
function
|
|
1999
|
+
function Js(e) {
|
|
1899
2000
|
if (e == null || e === "")
|
|
1900
|
-
return
|
|
1901
|
-
typeof e != "string" &&
|
|
2001
|
+
return Hs;
|
|
2002
|
+
typeof e != "string" && R("The configured forms collection must be a string.");
|
|
1902
2003
|
const t = e.trim();
|
|
1903
|
-
(!t || t.length > 500) &&
|
|
2004
|
+
(!t || t.length > 500) && R("The configured forms collection path is invalid."), (t.startsWith("/") || t.endsWith("/") || t.includes("//")) && R("The configured forms collection path is invalid.");
|
|
1904
2005
|
const s = t.split("/");
|
|
1905
|
-
s.length % 2 === 0 &&
|
|
1906
|
-
for (const
|
|
1907
|
-
(!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(
|
|
2006
|
+
s.length % 2 === 0 && R("The configured forms collection path must point to a collection.");
|
|
2007
|
+
for (const r of s)
|
|
2008
|
+
(!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(r) || r === "." || r === ".." || /^__.*__$/.test(r)) && R("The configured forms collection path contains an invalid segment.");
|
|
1908
2009
|
return t;
|
|
1909
2010
|
}
|
|
1910
|
-
function
|
|
1911
|
-
(!e || typeof e != "object") &&
|
|
2011
|
+
function Ks(e) {
|
|
2012
|
+
(!e || typeof e != "object") && R("Form definition must be an object or array.");
|
|
1912
2013
|
const t = /* @__PURE__ */ new Set();
|
|
1913
2014
|
let s = 0;
|
|
1914
|
-
const
|
|
1915
|
-
if (s += 1, s >
|
|
1916
|
-
if (typeof
|
|
1917
|
-
Number.isFinite(
|
|
2015
|
+
const r = (c, i, o) => {
|
|
2016
|
+
if (s += 1, s > Ls && R("Form definition is too complex."), i > It && R(`Form definition cannot be nested deeper than ${It} levels.`), c === null || typeof c == "string" || typeof c == "boolean") return;
|
|
2017
|
+
if (typeof c == "number") {
|
|
2018
|
+
Number.isFinite(c) || R("Form definition contains an invalid number.");
|
|
1918
2019
|
return;
|
|
1919
2020
|
}
|
|
1920
|
-
if (typeof
|
|
1921
|
-
o &&
|
|
1922
|
-
for (const
|
|
1923
|
-
t.delete(
|
|
2021
|
+
if (typeof c != "object" && R("Form definition contains a value that cannot be stored."), t.has(c) && R("Form definition cannot contain circular references."), t.add(c), Array.isArray(c)) {
|
|
2022
|
+
o && R("Form definition cannot contain an array directly inside another array.");
|
|
2023
|
+
for (const u of c) r(u, i + 1, !0);
|
|
2024
|
+
t.delete(c);
|
|
1924
2025
|
return;
|
|
1925
2026
|
}
|
|
1926
|
-
const
|
|
1927
|
-
|
|
1928
|
-
for (const [
|
|
1929
|
-
|
|
1930
|
-
t.delete(
|
|
2027
|
+
const d = Object.getPrototypeOf(c);
|
|
2028
|
+
d !== Object.prototype && d !== null && R("Form definition can contain only plain JSON objects.");
|
|
2029
|
+
for (const [u, l] of Object.entries(c))
|
|
2030
|
+
Ws.has(u) && R(`Form definition contains a forbidden object key: ${u}.`), new TextEncoder().encode(u).byteLength > 1500 && R("Form definition contains an object key that is too long."), r(l, i + 1, !1);
|
|
2031
|
+
t.delete(c);
|
|
1931
2032
|
};
|
|
1932
|
-
|
|
1933
|
-
let
|
|
2033
|
+
r(e, 1, !1);
|
|
2034
|
+
let n;
|
|
1934
2035
|
try {
|
|
1935
|
-
|
|
2036
|
+
n = JSON.stringify(e);
|
|
1936
2037
|
} catch {
|
|
1937
|
-
|
|
2038
|
+
R("Form definition must be valid JSON.");
|
|
1938
2039
|
}
|
|
1939
|
-
new TextEncoder().encode(
|
|
2040
|
+
new TextEncoder().encode(n).byteLength > At && R(`Form definition cannot exceed ${At} bytes.`);
|
|
1940
2041
|
}
|
|
1941
|
-
function
|
|
1942
|
-
const t =
|
|
1943
|
-
return
|
|
1944
|
-
name:
|
|
1945
|
-
key:
|
|
1946
|
-
description:
|
|
2042
|
+
function Gt(e) {
|
|
2043
|
+
const t = tt(e, "Request body"), s = t.definition;
|
|
2044
|
+
return Ks(s), {
|
|
2045
|
+
name: Wt(t.name, "Form name", zs),
|
|
2046
|
+
key: Ys(t.key),
|
|
2047
|
+
description: Gs(t.description),
|
|
1947
2048
|
definition: s
|
|
1948
2049
|
};
|
|
1949
2050
|
}
|
|
1950
|
-
function
|
|
1951
|
-
return
|
|
2051
|
+
function Qs(e) {
|
|
2052
|
+
return Gt(e);
|
|
1952
2053
|
}
|
|
1953
|
-
function
|
|
1954
|
-
return (!Number.isInteger(e) || e < 1) &&
|
|
2054
|
+
function Yt(e) {
|
|
2055
|
+
return (!Number.isInteger(e) || e < 1) && R("expectedRevision must be a positive integer."), e;
|
|
1955
2056
|
}
|
|
1956
|
-
function
|
|
1957
|
-
const t =
|
|
2057
|
+
function Zs(e) {
|
|
2058
|
+
const t = tt(e, "Request body");
|
|
1958
2059
|
return {
|
|
1959
|
-
...
|
|
1960
|
-
expectedRevision:
|
|
2060
|
+
...Gt(t),
|
|
2061
|
+
expectedRevision: Yt(t.expectedRevision)
|
|
1961
2062
|
};
|
|
1962
2063
|
}
|
|
1963
|
-
function
|
|
1964
|
-
const t =
|
|
1965
|
-
return { expectedRevision:
|
|
2064
|
+
function Be(e) {
|
|
2065
|
+
const t = tt(e, "Request body");
|
|
2066
|
+
return { expectedRevision: Yt(t.expectedRevision) };
|
|
1966
2067
|
}
|
|
1967
|
-
function
|
|
2068
|
+
function Xs(e) {
|
|
1968
2069
|
return {
|
|
1969
2070
|
id: e.revisionId,
|
|
1970
2071
|
formId: e.formId,
|
|
@@ -1972,7 +2073,7 @@ function $t(e) {
|
|
|
1972
2073
|
name: e.form.name,
|
|
1973
2074
|
description: e.form.description || "",
|
|
1974
2075
|
definition: e.form.draftDefinition,
|
|
1975
|
-
schemaVersion: e.form.schemaVersion ||
|
|
2076
|
+
schemaVersion: e.form.schemaVersion || et,
|
|
1976
2077
|
revision: e.revision,
|
|
1977
2078
|
draftRevision: e.form.draftRevision,
|
|
1978
2079
|
previousRevisionId: e.form.publishedRevisionId || null,
|
|
@@ -1980,295 +2081,1126 @@ function $t(e) {
|
|
|
1980
2081
|
publishedBy: e.publishedBy
|
|
1981
2082
|
};
|
|
1982
2083
|
}
|
|
1983
|
-
function
|
|
1984
|
-
typeof e != "string" &&
|
|
2084
|
+
function he(e) {
|
|
2085
|
+
typeof e != "string" && R("Form ID is required.");
|
|
1985
2086
|
const t = e.trim();
|
|
1986
|
-
return (!t || t === "." || t === ".." || t.includes("/") || /[\u0000-\u001f\u007f]/.test(t) || new TextEncoder().encode(t).byteLength > 1500) &&
|
|
2087
|
+
return (!t || t === "." || t === ".." || t.includes("/") || /[\u0000-\u001f\u007f]/.test(t) || new TextEncoder().encode(t).byteLength > 1500) && R("Form ID is invalid."), t;
|
|
1987
2088
|
}
|
|
1988
|
-
function
|
|
2089
|
+
function Me(e) {
|
|
1989
2090
|
const s = e?.context?.module?.userId || e?.context?.user?.id || null;
|
|
1990
2091
|
return typeof s == "string" && s.trim() ? s : null;
|
|
1991
2092
|
}
|
|
1992
|
-
function
|
|
2093
|
+
function z(e, t) {
|
|
1993
2094
|
const s = e?.context?.module;
|
|
1994
2095
|
if (!s)
|
|
1995
|
-
throw
|
|
2096
|
+
throw g({ statusCode: 500, statusMessage: "Module context is unavailable." });
|
|
1996
2097
|
if (!s.userId)
|
|
1997
|
-
throw
|
|
2098
|
+
throw g({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
1998
2099
|
if (s.isAdmin === !0) return;
|
|
1999
2100
|
if (!(Array.isArray(s.permissions) ? s.permissions : []).includes(t))
|
|
2000
|
-
throw
|
|
2101
|
+
throw g({ statusCode: 403, statusMessage: `Missing module permission: ${t}.` });
|
|
2001
2102
|
}
|
|
2002
|
-
function
|
|
2103
|
+
function ye(e) {
|
|
2003
2104
|
if (!e || typeof e != "object" || Array.isArray(e))
|
|
2004
|
-
throw
|
|
2105
|
+
throw g({ statusCode: 500, statusMessage: "Stored form data is invalid." });
|
|
2005
2106
|
const t = e;
|
|
2006
|
-
if (typeof t.name != "string" || typeof t.key != "string" || !
|
|
2007
|
-
throw
|
|
2107
|
+
if (typeof t.name != "string" || typeof t.key != "string" || !Ht.has(String(t.status)) || !Number.isInteger(t.revision) || !Number.isInteger(t.draftRevision) || !t.draftDefinition || typeof t.draftDefinition != "object")
|
|
2108
|
+
throw g({ statusCode: 500, statusMessage: "Stored form data is invalid." });
|
|
2008
2109
|
return t;
|
|
2009
2110
|
}
|
|
2010
|
-
function
|
|
2011
|
-
e.revision !== t &&
|
|
2111
|
+
function Ne(e, t) {
|
|
2112
|
+
e.revision !== t && ae(`Form has changed. Expected revision ${t}, current revision is ${e.revision}.`);
|
|
2012
2113
|
}
|
|
2013
|
-
function
|
|
2014
|
-
(Array.isArray(e) || !Array.isArray(e.layout) || e.layout.length === 0) &&
|
|
2114
|
+
function ea(e) {
|
|
2115
|
+
(Array.isArray(e) || !Array.isArray(e.layout) || e.layout.length === 0) && R("Add at least one form element before publishing.");
|
|
2015
2116
|
}
|
|
2016
|
-
function
|
|
2017
|
-
const { draftDefinition:
|
|
2117
|
+
function le(e, t, s, r = !0) {
|
|
2118
|
+
const { draftDefinition: n, ...a } = t, c = r ? { id: e, ...a, draftDefinition: n } : { id: e, ...a }, i = s ? s(c) : c, o = Number.isInteger(i.publishedRevision) ? i.publishedRevision : null, d = Number.isInteger(i.publishedDraftRevision) ? i.publishedDraftRevision : null;
|
|
2018
2119
|
return {
|
|
2019
|
-
id:
|
|
2020
|
-
name:
|
|
2021
|
-
key:
|
|
2022
|
-
description:
|
|
2023
|
-
...
|
|
2024
|
-
status:
|
|
2025
|
-
revision:
|
|
2026
|
-
draftRevision:
|
|
2120
|
+
id: i.id,
|
|
2121
|
+
name: i.name,
|
|
2122
|
+
key: i.key,
|
|
2123
|
+
description: i.description || "",
|
|
2124
|
+
...r ? { definition: n } : {},
|
|
2125
|
+
status: i.status,
|
|
2126
|
+
revision: i.revision,
|
|
2127
|
+
draftRevision: i.draftRevision,
|
|
2027
2128
|
publishedRevision: o,
|
|
2028
|
-
publishedRevisionId:
|
|
2029
|
-
hasUnpublishedChanges: !
|
|
2030
|
-
schemaVersion:
|
|
2031
|
-
createdAt:
|
|
2032
|
-
createdBy:
|
|
2033
|
-
updatedAt:
|
|
2034
|
-
updatedBy:
|
|
2035
|
-
publishedAt:
|
|
2036
|
-
publishedBy:
|
|
2037
|
-
archivedAt:
|
|
2038
|
-
archivedBy:
|
|
2129
|
+
publishedRevisionId: i.publishedRevisionId || null,
|
|
2130
|
+
hasUnpublishedChanges: !i.publishedRevisionId || i.draftRevision !== d,
|
|
2131
|
+
schemaVersion: i.schemaVersion || et,
|
|
2132
|
+
createdAt: i.createdAt || null,
|
|
2133
|
+
createdBy: i.createdBy || null,
|
|
2134
|
+
updatedAt: i.updatedAt || null,
|
|
2135
|
+
updatedBy: i.updatedBy || null,
|
|
2136
|
+
publishedAt: i.publishedAt || null,
|
|
2137
|
+
publishedBy: i.publishedBy || null,
|
|
2138
|
+
archivedAt: i.archivedAt || null,
|
|
2139
|
+
archivedBy: i.archivedBy || null
|
|
2039
2140
|
};
|
|
2040
2141
|
}
|
|
2041
|
-
function
|
|
2142
|
+
function Dt(e, t) {
|
|
2042
2143
|
if (!(e == null || e === ""))
|
|
2043
|
-
return (Array.isArray(e) || typeof e != "string") &&
|
|
2144
|
+
return (Array.isArray(e) || typeof e != "string") && R(`${t} must be a single string.`), e.trim();
|
|
2044
2145
|
}
|
|
2045
|
-
function
|
|
2146
|
+
function Tt(e) {
|
|
2046
2147
|
if (typeof e == "string") {
|
|
2047
2148
|
const t = Date.parse(e);
|
|
2048
2149
|
return Number.isNaN(t) ? 0 : t;
|
|
2049
2150
|
}
|
|
2050
2151
|
return e instanceof Date ? e.getTime() : e && typeof e.toMillis == "function" ? e.toMillis() : 0;
|
|
2051
2152
|
}
|
|
2052
|
-
function
|
|
2053
|
-
throw Number.isInteger(t?.statusCode) && t.statusCode >= 400 && t.statusCode < 600 ? t : (console.error(`Form Builder handler — ${e} error:`, t),
|
|
2153
|
+
function me(e, t, s) {
|
|
2154
|
+
throw Number.isInteger(t?.statusCode) && t.statusCode >= 400 && t.statusCode < 600 ? t : (console.error(`Form Builder handler — ${e} error:`, t), g({ statusCode: 500, statusMessage: s }));
|
|
2054
2155
|
}
|
|
2055
|
-
function
|
|
2056
|
-
const { initFirebase: t, normalizeTimestamps: s } = e,
|
|
2057
|
-
async function
|
|
2058
|
-
const
|
|
2059
|
-
if (!
|
|
2060
|
-
throw
|
|
2061
|
-
const { data: l, error: f } = await
|
|
2156
|
+
function _a(e) {
|
|
2157
|
+
const { initFirebase: t, normalizeTimestamps: s } = e, r = J();
|
|
2158
|
+
async function n(a) {
|
|
2159
|
+
const c = a?.context?.module, { supabase: i, instanceId: o, projectId: d, moduleType: u } = c || {};
|
|
2160
|
+
if (!i || !o)
|
|
2161
|
+
throw g({ statusCode: 500, statusMessage: "Module context is unavailable." });
|
|
2162
|
+
const { data: l, error: f } = await i.from("project_modules").select("config, project_id, type").eq("id", o).single();
|
|
2062
2163
|
if (f || !l?.config)
|
|
2063
|
-
throw
|
|
2064
|
-
if (
|
|
2065
|
-
throw
|
|
2066
|
-
if (
|
|
2067
|
-
throw
|
|
2068
|
-
const
|
|
2164
|
+
throw g({ statusCode: 500, statusMessage: "Failed to load module config." });
|
|
2165
|
+
if (d && l.project_id !== d)
|
|
2166
|
+
throw g({ statusCode: 403, statusMessage: "Module project context does not match." });
|
|
2167
|
+
if (u && l.type !== u)
|
|
2168
|
+
throw g({ statusCode: 409, statusMessage: "Module type context does not match." });
|
|
2169
|
+
const p = l.config, m = p.databaseIntegration;
|
|
2069
2170
|
if (typeof m != "string" || !m.trim())
|
|
2070
|
-
throw
|
|
2171
|
+
throw g({
|
|
2071
2172
|
statusCode: 400,
|
|
2072
2173
|
statusMessage: "Form Builder has no Firebase integration configured."
|
|
2073
2174
|
});
|
|
2074
2175
|
if (m.length > 200 || !/^[A-Za-z0-9_-]+$/.test(m))
|
|
2075
|
-
throw
|
|
2076
|
-
const h =
|
|
2077
|
-
if (w || !
|
|
2078
|
-
throw
|
|
2176
|
+
throw g({ statusCode: 500, statusMessage: "Configured Firebase integration ID is invalid." });
|
|
2177
|
+
const h = d || l.project_id, { data: y, error: w } = await i.from("integrations").select("id").eq("id", m).eq("project_id", h).eq("type", "FIREBASE").single();
|
|
2178
|
+
if (w || !y)
|
|
2179
|
+
throw g({
|
|
2079
2180
|
statusCode: 400,
|
|
2080
2181
|
statusMessage: "Configured Firebase integration is unavailable for this project."
|
|
2081
2182
|
});
|
|
2082
|
-
const C =
|
|
2183
|
+
const C = Js(p.formsCollection), A = he(o), M = (await t(a, m)).firestore();
|
|
2083
2184
|
return {
|
|
2084
|
-
firestore:
|
|
2185
|
+
firestore: M,
|
|
2085
2186
|
// Access is granted per module instance in Studio, so storage must be
|
|
2086
2187
|
// isolated at the same boundary even when multiple instances retain the
|
|
2087
2188
|
// default root collection.
|
|
2088
|
-
forms:
|
|
2189
|
+
forms: M.collection(C).doc(A).collection("forms")
|
|
2089
2190
|
};
|
|
2090
2191
|
}
|
|
2091
|
-
return
|
|
2092
|
-
|
|
2192
|
+
return r.get("/forms", b(async (a) => {
|
|
2193
|
+
z(a, L.view);
|
|
2093
2194
|
try {
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
const { forms:
|
|
2097
|
-
return
|
|
2195
|
+
const c = v(a), i = Dt(c.status, "status"), o = Dt(c.search, "search");
|
|
2196
|
+
i && !Ht.has(i) && R("status must be draft, published, or archived."), o && o.length > St && R(`search cannot exceed ${St} characters.`);
|
|
2197
|
+
const { forms: d } = await n(a), u = i ? await d.where("status", "==", i).get() : await d.get(), l = o?.toLocaleLowerCase();
|
|
2198
|
+
return u.docs.map((f) => le(
|
|
2098
2199
|
f.id,
|
|
2099
|
-
|
|
2200
|
+
ye(f.data()),
|
|
2100
2201
|
s,
|
|
2101
2202
|
!1
|
|
2102
|
-
)).filter((f) => l ? [f.name, f.key, f.description].some((
|
|
2103
|
-
} catch (
|
|
2104
|
-
|
|
2203
|
+
)).filter((f) => l ? [f.name, f.key, f.description].some((p) => String(p || "").toLocaleLowerCase().includes(l)) : !0).sort((f, p) => Tt(p.updatedAt) - Tt(f.updatedAt));
|
|
2204
|
+
} catch (c) {
|
|
2205
|
+
me("list", c, "Failed to list forms.");
|
|
2105
2206
|
}
|
|
2106
|
-
})),
|
|
2107
|
-
|
|
2108
|
-
const
|
|
2207
|
+
})), r.get("/forms/:formId", b(async (a) => {
|
|
2208
|
+
z(a, L.view);
|
|
2209
|
+
const c = he(U(a, "formId"));
|
|
2109
2210
|
try {
|
|
2110
|
-
const { forms:
|
|
2211
|
+
const { forms: i } = await n(a), o = await i.doc(c).get();
|
|
2111
2212
|
if (!o.exists)
|
|
2112
|
-
throw
|
|
2113
|
-
return
|
|
2114
|
-
} catch (
|
|
2115
|
-
|
|
2213
|
+
throw g({ statusCode: 404, statusMessage: "Form not found." });
|
|
2214
|
+
return le(o.id, ye(o.data()), s);
|
|
2215
|
+
} catch (i) {
|
|
2216
|
+
me("get", i, "Failed to load form.");
|
|
2116
2217
|
}
|
|
2117
|
-
})),
|
|
2118
|
-
|
|
2218
|
+
})), r.post("/forms", b(async (a) => {
|
|
2219
|
+
z(a, L.view), z(a, L.create);
|
|
2119
2220
|
try {
|
|
2120
|
-
const
|
|
2121
|
-
name:
|
|
2122
|
-
key:
|
|
2123
|
-
description:
|
|
2221
|
+
const c = Qs(await j(a)), i = Me(a), o = (/* @__PURE__ */ new Date()).toISOString(), { firestore: d, forms: u } = await n(a), l = u.doc(c.key), f = {
|
|
2222
|
+
name: c.name,
|
|
2223
|
+
key: c.key,
|
|
2224
|
+
description: c.description,
|
|
2124
2225
|
status: "draft",
|
|
2125
|
-
draftDefinition:
|
|
2126
|
-
schemaVersion:
|
|
2226
|
+
draftDefinition: c.definition,
|
|
2227
|
+
schemaVersion: et,
|
|
2127
2228
|
revision: 1,
|
|
2128
2229
|
draftRevision: 1,
|
|
2129
2230
|
publishedRevision: null,
|
|
2130
2231
|
publishedRevisionId: null,
|
|
2131
2232
|
publishedDraftRevision: null,
|
|
2132
2233
|
createdAt: o,
|
|
2133
|
-
createdBy:
|
|
2234
|
+
createdBy: i,
|
|
2134
2235
|
updatedAt: o,
|
|
2135
|
-
updatedBy:
|
|
2236
|
+
updatedBy: i,
|
|
2136
2237
|
draftUpdatedAt: o,
|
|
2137
|
-
draftUpdatedBy:
|
|
2238
|
+
draftUpdatedBy: i,
|
|
2138
2239
|
publishedAt: null,
|
|
2139
2240
|
publishedBy: null,
|
|
2140
2241
|
archivedAt: null,
|
|
2141
2242
|
archivedBy: null,
|
|
2142
2243
|
statusBeforeArchive: null
|
|
2143
2244
|
};
|
|
2144
|
-
return await
|
|
2145
|
-
(await
|
|
2146
|
-
}),
|
|
2147
|
-
} catch (
|
|
2148
|
-
|
|
2245
|
+
return await d.runTransaction(async (p) => {
|
|
2246
|
+
(await p.get(l)).exists && ae(`A form with key "${c.key}" already exists.`), p.create(l, f);
|
|
2247
|
+
}), le(l.id, f, s);
|
|
2248
|
+
} catch (c) {
|
|
2249
|
+
me("create", c, "Failed to create form.");
|
|
2149
2250
|
}
|
|
2150
|
-
})),
|
|
2151
|
-
|
|
2152
|
-
const
|
|
2251
|
+
})), r.patch("/forms/:formId/draft", b(async (a) => {
|
|
2252
|
+
z(a, L.view), z(a, L.edit);
|
|
2253
|
+
const c = he(U(a, "formId"));
|
|
2153
2254
|
try {
|
|
2154
|
-
const
|
|
2155
|
-
return await
|
|
2156
|
-
const m = await
|
|
2255
|
+
const i = Zs(await j(a)), o = Me(a), d = (/* @__PURE__ */ new Date()).toISOString(), { firestore: u, forms: l } = await n(a), f = l.doc(c);
|
|
2256
|
+
return await u.runTransaction(async (p) => {
|
|
2257
|
+
const m = await p.get(f);
|
|
2157
2258
|
if (!m.exists)
|
|
2158
|
-
throw
|
|
2159
|
-
const h =
|
|
2160
|
-
|
|
2161
|
-
const
|
|
2162
|
-
name:
|
|
2163
|
-
key:
|
|
2164
|
-
description:
|
|
2165
|
-
draftDefinition:
|
|
2259
|
+
throw g({ statusCode: 404, statusMessage: "Form not found." });
|
|
2260
|
+
const h = ye(m.data());
|
|
2261
|
+
Ne(h, i.expectedRevision), h.status === "archived" && ae("Restore this form before editing its draft."), h.key !== i.key && ae("A form key cannot be changed after the form is created.");
|
|
2262
|
+
const y = {
|
|
2263
|
+
name: i.name,
|
|
2264
|
+
key: i.key,
|
|
2265
|
+
description: i.description,
|
|
2266
|
+
draftDefinition: i.definition,
|
|
2166
2267
|
revision: h.revision + 1,
|
|
2167
2268
|
draftRevision: h.draftRevision + 1,
|
|
2168
|
-
updatedAt:
|
|
2269
|
+
updatedAt: d,
|
|
2169
2270
|
updatedBy: o,
|
|
2170
|
-
draftUpdatedAt:
|
|
2271
|
+
draftUpdatedAt: d,
|
|
2171
2272
|
draftUpdatedBy: o
|
|
2172
2273
|
};
|
|
2173
|
-
return
|
|
2274
|
+
return p.update(f, y), le(c, { ...h, ...y }, s);
|
|
2174
2275
|
});
|
|
2175
|
-
} catch (
|
|
2176
|
-
|
|
2276
|
+
} catch (i) {
|
|
2277
|
+
me("update draft", i, "Failed to update form draft.");
|
|
2177
2278
|
}
|
|
2178
|
-
})),
|
|
2179
|
-
|
|
2180
|
-
const
|
|
2279
|
+
})), r.post("/forms/:formId/publish", b(async (a) => {
|
|
2280
|
+
z(a, L.view), z(a, L.publish);
|
|
2281
|
+
const c = he(U(a, "formId"));
|
|
2181
2282
|
try {
|
|
2182
|
-
const { expectedRevision:
|
|
2183
|
-
return await
|
|
2283
|
+
const { expectedRevision: i } = Be(await j(a)), o = Me(a), d = (/* @__PURE__ */ new Date()).toISOString(), { firestore: u, forms: l } = await n(a), f = l.doc(c), p = f.collection("revisions").doc();
|
|
2284
|
+
return await u.runTransaction(async (m) => {
|
|
2184
2285
|
const h = await m.get(f);
|
|
2185
2286
|
if (!h.exists)
|
|
2186
|
-
throw
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2189
|
-
const w =
|
|
2190
|
-
formId:
|
|
2191
|
-
form:
|
|
2287
|
+
throw g({ statusCode: 404, statusMessage: "Form not found." });
|
|
2288
|
+
const y = ye(h.data());
|
|
2289
|
+
Ne(y, i), y.status === "archived" && ae("Restore this form before publishing it."), ea(y.draftDefinition), y.publishedRevisionId && y.publishedDraftRevision === y.draftRevision && ae("This form has no unpublished changes.");
|
|
2290
|
+
const w = y.revision + 1, C = Xs({
|
|
2291
|
+
formId: c,
|
|
2292
|
+
form: y,
|
|
2192
2293
|
revision: w,
|
|
2193
|
-
revisionId:
|
|
2194
|
-
publishedAt:
|
|
2294
|
+
revisionId: p.id,
|
|
2295
|
+
publishedAt: d,
|
|
2195
2296
|
publishedBy: o
|
|
2196
|
-
}),
|
|
2297
|
+
}), A = {
|
|
2197
2298
|
status: "published",
|
|
2198
2299
|
revision: w,
|
|
2199
2300
|
publishedRevision: w,
|
|
2200
|
-
publishedRevisionId:
|
|
2201
|
-
publishedDraftRevision:
|
|
2202
|
-
publishedAt:
|
|
2301
|
+
publishedRevisionId: p.id,
|
|
2302
|
+
publishedDraftRevision: y.draftRevision,
|
|
2303
|
+
publishedAt: d,
|
|
2203
2304
|
publishedBy: o,
|
|
2204
|
-
updatedAt:
|
|
2305
|
+
updatedAt: d,
|
|
2205
2306
|
updatedBy: o,
|
|
2206
2307
|
archivedAt: null,
|
|
2207
2308
|
archivedBy: null,
|
|
2208
2309
|
statusBeforeArchive: null
|
|
2209
2310
|
};
|
|
2210
|
-
return m.create(
|
|
2311
|
+
return m.create(p, C), m.update(f, A), le(c, { ...y, ...A }, s);
|
|
2211
2312
|
});
|
|
2212
|
-
} catch (
|
|
2213
|
-
|
|
2313
|
+
} catch (i) {
|
|
2314
|
+
me("publish", i, "Failed to publish form.");
|
|
2214
2315
|
}
|
|
2215
|
-
})),
|
|
2216
|
-
|
|
2217
|
-
const
|
|
2316
|
+
})), r.post("/forms/:formId/archive", b(async (a) => {
|
|
2317
|
+
z(a, L.view), z(a, L.archive);
|
|
2318
|
+
const c = he(U(a, "formId"));
|
|
2218
2319
|
try {
|
|
2219
|
-
const { expectedRevision:
|
|
2220
|
-
return await
|
|
2221
|
-
const m = await
|
|
2320
|
+
const { expectedRevision: i } = Be(await j(a)), o = Me(a), d = (/* @__PURE__ */ new Date()).toISOString(), { firestore: u, forms: l } = await n(a), f = l.doc(c);
|
|
2321
|
+
return await u.runTransaction(async (p) => {
|
|
2322
|
+
const m = await p.get(f);
|
|
2222
2323
|
if (!m.exists)
|
|
2223
|
-
throw
|
|
2224
|
-
const h =
|
|
2225
|
-
|
|
2226
|
-
const
|
|
2324
|
+
throw g({ statusCode: 404, statusMessage: "Form not found." });
|
|
2325
|
+
const h = ye(m.data());
|
|
2326
|
+
Ne(h, i), h.status === "archived" && ae("Form is already archived.");
|
|
2327
|
+
const y = {
|
|
2227
2328
|
status: "archived",
|
|
2228
2329
|
statusBeforeArchive: h.status,
|
|
2229
2330
|
revision: h.revision + 1,
|
|
2230
|
-
archivedAt:
|
|
2331
|
+
archivedAt: d,
|
|
2231
2332
|
archivedBy: o,
|
|
2232
|
-
updatedAt:
|
|
2333
|
+
updatedAt: d,
|
|
2233
2334
|
updatedBy: o
|
|
2234
2335
|
};
|
|
2235
|
-
return
|
|
2336
|
+
return p.update(f, y), le(c, { ...h, ...y }, s);
|
|
2236
2337
|
});
|
|
2237
|
-
} catch (
|
|
2238
|
-
|
|
2338
|
+
} catch (i) {
|
|
2339
|
+
me("archive", i, "Failed to archive form.");
|
|
2239
2340
|
}
|
|
2240
|
-
})),
|
|
2241
|
-
|
|
2242
|
-
const
|
|
2341
|
+
})), r.post("/forms/:formId/restore", b(async (a) => {
|
|
2342
|
+
z(a, L.view), z(a, L.archive);
|
|
2343
|
+
const c = he(U(a, "formId"));
|
|
2243
2344
|
try {
|
|
2244
|
-
const { expectedRevision:
|
|
2245
|
-
return await
|
|
2246
|
-
const m = await
|
|
2345
|
+
const { expectedRevision: i } = Be(await j(a)), o = Me(a), d = (/* @__PURE__ */ new Date()).toISOString(), { firestore: u, forms: l } = await n(a), f = l.doc(c);
|
|
2346
|
+
return await u.runTransaction(async (p) => {
|
|
2347
|
+
const m = await p.get(f);
|
|
2247
2348
|
if (!m.exists)
|
|
2248
|
-
throw
|
|
2249
|
-
const h =
|
|
2250
|
-
|
|
2349
|
+
throw g({ statusCode: 404, statusMessage: "Form not found." });
|
|
2350
|
+
const h = ye(m.data());
|
|
2351
|
+
Ne(h, i), h.status !== "archived" && ae("Only archived forms can be restored.");
|
|
2251
2352
|
const w = {
|
|
2252
2353
|
status: h.publishedRevisionId ? "published" : "draft",
|
|
2253
2354
|
statusBeforeArchive: null,
|
|
2254
2355
|
revision: h.revision + 1,
|
|
2255
2356
|
archivedAt: null,
|
|
2256
2357
|
archivedBy: null,
|
|
2257
|
-
updatedAt:
|
|
2358
|
+
updatedAt: d,
|
|
2258
2359
|
updatedBy: o
|
|
2259
2360
|
};
|
|
2260
|
-
return
|
|
2361
|
+
return p.update(f, w), le(c, { ...h, ...w }, s);
|
|
2261
2362
|
});
|
|
2262
|
-
} catch (
|
|
2263
|
-
|
|
2363
|
+
} catch (i) {
|
|
2364
|
+
me("restore", i, "Failed to restore form.");
|
|
2365
|
+
}
|
|
2366
|
+
})), r.handler;
|
|
2367
|
+
}
|
|
2368
|
+
const Jt = ["authentication", "billing", "notifications", "onboarding", "system"], Kt = ["draft", "active", "inactive"];
|
|
2369
|
+
function ta(e) {
|
|
2370
|
+
const t = e.context?.module;
|
|
2371
|
+
if (!t?.supabase || !t.instanceId || !t.projectId || !t.userId || t.moduleType !== "mailTemplates")
|
|
2372
|
+
throw g({ statusCode: 403, statusMessage: "Mail Templates module access is required." });
|
|
2373
|
+
return t;
|
|
2374
|
+
}
|
|
2375
|
+
function we(e, t) {
|
|
2376
|
+
const s = ta(e), r = Array.isArray(t) ? t : t ? [t] : [];
|
|
2377
|
+
if (r.length && !s.isAdmin && !r.some((n) => s.permissions?.includes(n)))
|
|
2378
|
+
throw g({ statusCode: 403, statusMessage: "You do not have permission for this template action." });
|
|
2379
|
+
return se(e, "Cache-Control", "private, no-store"), s;
|
|
2380
|
+
}
|
|
2381
|
+
function sa(e) {
|
|
2382
|
+
const t = typeof e == "string" ? e.trim() : "";
|
|
2383
|
+
if (!t || t.length > 512 || /[\\\u0000-\u001f\u007f]/.test(t) || t.split("/").length % 2 !== 1 || t.split("/").some((s) => !s || s === "." || s === ".." || /^__.*__$/.test(s)))
|
|
2384
|
+
throw g({ statusCode: 400, statusMessage: "Configure a valid Firestore templates collection." });
|
|
2385
|
+
return t;
|
|
2386
|
+
}
|
|
2387
|
+
function Le(e) {
|
|
2388
|
+
const t = U(e, "templateId");
|
|
2389
|
+
if (!t || t.length > 200 || /[\/\\\u0000-\u001f\u007f]/.test(t) || t === "." || t === ".." || /^__.*__$/.test(t))
|
|
2390
|
+
throw g({ statusCode: 400, statusMessage: "Invalid template ID." });
|
|
2391
|
+
return t;
|
|
2392
|
+
}
|
|
2393
|
+
function Ue(e, t, s, r = !1) {
|
|
2394
|
+
if (r && e === void 0) return "";
|
|
2395
|
+
if (typeof e != "string" || e.length > s || !r && !e.trim())
|
|
2396
|
+
throw g({ statusCode: 400, statusMessage: `${t} is required and must be at most ${s} characters.` });
|
|
2397
|
+
return e.trim();
|
|
2398
|
+
}
|
|
2399
|
+
function ze(e) {
|
|
2400
|
+
if (!e || !Array.isArray(e.blocks) || e.blocks.length > 200 || JSON.stringify(e.blocks).length > 4e5)
|
|
2401
|
+
throw g({ statusCode: 400, statusMessage: "Provide up to 200 email blocks (400 KB maximum)." });
|
|
2402
|
+
if (!e.styles || typeof e.styles != "object" || Array.isArray(e.styles) || JSON.stringify(e.styles).length > 2e4)
|
|
2403
|
+
throw g({ statusCode: 400, statusMessage: "Provide valid email styles." });
|
|
2404
|
+
return { blocks: e.blocks, styles: e.styles, preheader: Ue(e.preheader, "Preheader", 300, !0) };
|
|
2405
|
+
}
|
|
2406
|
+
function aa(e) {
|
|
2407
|
+
if (e.preserveVariables === !0) return {};
|
|
2408
|
+
const t = e.variables && typeof e.variables == "object" && !Array.isArray(e.variables) ? e.variables : {};
|
|
2409
|
+
if (JSON.stringify(t).length > 5e4) throw g({ statusCode: 400, statusMessage: "Preview variables are too large." });
|
|
2410
|
+
return { year: (/* @__PURE__ */ new Date()).getFullYear().toString(), ...Object.fromEntries(Object.entries(t).filter(([s, r]) => /^\w+$/.test(s) && typeof r == "string")) };
|
|
2411
|
+
}
|
|
2412
|
+
function oa(e, t) {
|
|
2413
|
+
return e.replace(/\{\{(\w+)\}\}/g, (s, r) => Object.prototype.hasOwnProperty.call(t, r) ? t[r] : s);
|
|
2414
|
+
}
|
|
2415
|
+
function Se(e) {
|
|
2416
|
+
throw Number.isInteger(e?.statusCode) && e.statusCode >= 400 && e.statusCode < 600 ? e : g({ statusCode: 500, statusMessage: "The template request could not be completed. Check the Firebase integration and try again." });
|
|
2417
|
+
}
|
|
2418
|
+
function Ve(e, t = !0) {
|
|
2419
|
+
const s = e.data() || {};
|
|
2420
|
+
return {
|
|
2421
|
+
id: e.id,
|
|
2422
|
+
name: typeof s.name == "string" ? s.name : "Untitled template",
|
|
2423
|
+
subject: typeof s.subject == "string" ? s.subject : "",
|
|
2424
|
+
description: typeof s.description == "string" ? s.description : "",
|
|
2425
|
+
preheader: typeof s.preheader == "string" ? s.preheader : "",
|
|
2426
|
+
category: Jt.includes(s.category) ? s.category : "notifications",
|
|
2427
|
+
status: Kt.includes(s.status) ? s.status : "draft",
|
|
2428
|
+
blocks: Array.isArray(s.blocks) ? s.blocks : [],
|
|
2429
|
+
styles: s.styles && typeof s.styles == "object" ? s.styles : {},
|
|
2430
|
+
createdAt: typeof s.createdAt == "string" ? s.createdAt : s.createdAt?.toDate?.().toISOString() || null,
|
|
2431
|
+
updatedAt: typeof s.updatedAt == "string" ? s.updatedAt : s.updatedAt?.toDate?.().toISOString() || null,
|
|
2432
|
+
...t ? { mjml: typeof s.mjml == "string" ? s.mjml : "", html: typeof s.html == "string" ? s.html : "" } : {}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function Ra(e) {
|
|
2436
|
+
const t = J();
|
|
2437
|
+
async function s(a, c) {
|
|
2438
|
+
const { data: i, error: o } = await c.supabase.from("project_modules").select("type, project_id, config").eq("id", c.instanceId).maybeSingle();
|
|
2439
|
+
if (o || i?.type !== "mailTemplates" || i.project_id !== c.projectId)
|
|
2440
|
+
throw g({ statusCode: 404, statusMessage: "Mail Templates module is unavailable." });
|
|
2441
|
+
const d = i.config?.project;
|
|
2442
|
+
if (typeof d != "string" || !d) throw g({ statusCode: 400, statusMessage: "Choose a Firebase integration in the module settings." });
|
|
2443
|
+
const u = sa(i.config?.templateCollection || "mail-templates"), { data: l, error: f } = await c.supabase.from("integrations").select("id, type").eq("id", d).eq("project_id", c.projectId).maybeSingle();
|
|
2444
|
+
if (f) throw g({ statusCode: 500, statusMessage: "Could not verify the Firebase integration." });
|
|
2445
|
+
if (!l || l.type !== "FIREBASE")
|
|
2446
|
+
throw g({ statusCode: 400, statusMessage: "The Firebase integration is unavailable for this project." });
|
|
2447
|
+
const m = (await e.initFirebase(a, l.id)).firestore();
|
|
2448
|
+
return { db: m, collection: m.collection(u) };
|
|
2449
|
+
}
|
|
2450
|
+
function r(a, c = {}) {
|
|
2451
|
+
try {
|
|
2452
|
+
const i = e.mailTemplates.render({ ...ze(a), variables: c });
|
|
2453
|
+
if (i.errors.length) throw g({ statusCode: 400, statusMessage: "Fix the MJML validation errors before saving.", data: { errors: i.errors } });
|
|
2454
|
+
return i;
|
|
2455
|
+
} catch (i) {
|
|
2456
|
+
throw i.statusCode ? i : g({ statusCode: 400, statusMessage: "The email contains invalid blocks or styles." });
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
function n(a) {
|
|
2460
|
+
const c = Ue(a?.name, "Template name", 120), i = Ue(a?.subject, "Subject", 300), o = Ue(a?.description, "Description", 1e3, !0), d = a.category ?? "notifications", u = a.status ?? "draft";
|
|
2461
|
+
if (!Jt.includes(d) || !Kt.includes(u)) throw g({ statusCode: 400, statusMessage: "Invalid template category or status." });
|
|
2462
|
+
const l = r(a), f = { name: c, subject: i, description: o, category: d, status: u, ...ze(a), html: l.html, mjml: l.mjml };
|
|
2463
|
+
if (new TextEncoder().encode(JSON.stringify(f)).length > 9e5) throw g({ statusCode: 400, statusMessage: "This compiled template exceeds the storage limit. Reduce the content." });
|
|
2464
|
+
return f;
|
|
2465
|
+
}
|
|
2466
|
+
return t.get("/templates", b(async (a) => {
|
|
2467
|
+
const c = we(a);
|
|
2468
|
+
try {
|
|
2469
|
+
const { collection: i } = await s(a, c), o = await i.limit(101).get();
|
|
2470
|
+
if (o.docs.length > 100) throw g({ statusCode: 400, statusMessage: "This collection contains more than 100 templates. Configure a dedicated templates collection." });
|
|
2471
|
+
return o.docs.map((d) => Ve(d, !1)).sort((d, u) => (u.updatedAt || "").localeCompare(d.updatedAt || "") || d.name.localeCompare(u.name));
|
|
2472
|
+
} catch (i) {
|
|
2473
|
+
Se(i);
|
|
2474
|
+
}
|
|
2475
|
+
})), t.get("/templates/:templateId", b(async (a) => {
|
|
2476
|
+
const c = we(a), i = Le(a);
|
|
2477
|
+
try {
|
|
2478
|
+
const { collection: o } = await s(a, c), d = await o.doc(i).get();
|
|
2479
|
+
if (!d.exists) throw g({ statusCode: 404, statusMessage: "Template not found." });
|
|
2480
|
+
return Ve(d);
|
|
2481
|
+
} catch (o) {
|
|
2482
|
+
Se(o);
|
|
2483
|
+
}
|
|
2484
|
+
})), t.post("/templates", b(async (a) => {
|
|
2485
|
+
const c = we(a, "create-template"), i = n(await j(a));
|
|
2486
|
+
try {
|
|
2487
|
+
const { collection: o } = await s(a, c), d = o.doc(), u = (/* @__PURE__ */ new Date()).toISOString(), l = { ...i, createdAt: u, updatedAt: u };
|
|
2488
|
+
return await d.create(l), { id: d.id, ...l };
|
|
2489
|
+
} catch (o) {
|
|
2490
|
+
Se(o);
|
|
2491
|
+
}
|
|
2492
|
+
})), t.patch("/templates/:templateId", b(async (a) => {
|
|
2493
|
+
const c = we(a, "edit-template"), i = Le(a), o = await j(a), d = n(o);
|
|
2494
|
+
try {
|
|
2495
|
+
const { db: u, collection: l } = await s(a, c), f = l.doc(i);
|
|
2496
|
+
return await u.runTransaction(async (p) => {
|
|
2497
|
+
const m = await p.get(f);
|
|
2498
|
+
if (!m.exists) throw g({ statusCode: 404, statusMessage: "Template not found." });
|
|
2499
|
+
const h = Ve(m);
|
|
2500
|
+
if (o.updatedAt !== void 0 && o.updatedAt !== h.updatedAt)
|
|
2501
|
+
throw g({ statusCode: 409, statusMessage: "This template was updated elsewhere. Reload it before saving." });
|
|
2502
|
+
const y = Math.max(Date.now(), (Date.parse(h.updatedAt || "") || 0) + 1), w = { ...d, updatedAt: new Date(y).toISOString() };
|
|
2503
|
+
return p.update(f, w), { ...h, ...w };
|
|
2504
|
+
});
|
|
2505
|
+
} catch (u) {
|
|
2506
|
+
Se(u);
|
|
2507
|
+
}
|
|
2508
|
+
})), t.delete("/templates/:templateId", b(async (a) => {
|
|
2509
|
+
const c = we(a, "delete-template"), i = Le(a);
|
|
2510
|
+
try {
|
|
2511
|
+
const { db: o, collection: d } = await s(a, c), u = d.doc(i);
|
|
2512
|
+
return await o.runTransaction(async (l) => {
|
|
2513
|
+
if (!(await l.get(u)).exists) throw g({ statusCode: 404, statusMessage: "Template not found." });
|
|
2514
|
+
l.delete(u);
|
|
2515
|
+
}), { success: !0 };
|
|
2516
|
+
} catch (o) {
|
|
2517
|
+
Se(o);
|
|
2518
|
+
}
|
|
2519
|
+
})), t.post("/render", b(async (a) => {
|
|
2520
|
+
we(a);
|
|
2521
|
+
const c = await j(a), i = ze(c), o = aa(c);
|
|
2522
|
+
try {
|
|
2523
|
+
return { ...e.mailTemplates.render({ ...i, variables: o }), subject: oa(typeof c.subject == "string" ? c.subject : "", o) };
|
|
2524
|
+
} catch {
|
|
2525
|
+
throw g({ statusCode: 400, statusMessage: "The email contains invalid blocks or styles." });
|
|
2526
|
+
}
|
|
2527
|
+
})), t.handler;
|
|
2528
|
+
}
|
|
2529
|
+
const na = /* @__PURE__ */ new Set(["FIREBASE", "FTP", "SFTP", "S3", "CLOUDFLARE_R2"]);
|
|
2530
|
+
function ra(e) {
|
|
2531
|
+
const t = e?.context?.module;
|
|
2532
|
+
if (!t?.supabase || !t?.instanceId || !t?.projectId || t.moduleType !== "filezone")
|
|
2533
|
+
throw g({ statusCode: 500, statusMessage: "File Zone context is unavailable." });
|
|
2534
|
+
return t;
|
|
2535
|
+
}
|
|
2536
|
+
function He(e, t = !1) {
|
|
2537
|
+
const s = ra(e);
|
|
2538
|
+
if (t ? !s.isAdmin : !s.isAdmin && !s.permissions.includes("view-files"))
|
|
2539
|
+
throw g({ statusCode: 403, statusMessage: t ? "Only Studio administrators can upload files." : "You cannot view files in this module." });
|
|
2540
|
+
return s;
|
|
2541
|
+
}
|
|
2542
|
+
function De(e, t = !1) {
|
|
2543
|
+
if (e == null || e === "") {
|
|
2544
|
+
if (t) return "";
|
|
2545
|
+
throw g({ statusCode: 400, statusMessage: "A file path is required." });
|
|
2546
|
+
}
|
|
2547
|
+
if (typeof e != "string" || e.length > 1024 || e.startsWith("/") || e.endsWith("/") || /[\\\u0000-\u001f\u007f]/.test(e) || e.split("/").some((s) => !s || s === "." || s === ".."))
|
|
2548
|
+
throw g({ statusCode: 400, statusMessage: "Invalid storage path." });
|
|
2549
|
+
return e;
|
|
2550
|
+
}
|
|
2551
|
+
function vt(e) {
|
|
2552
|
+
const t = e.replace(/\\/g, "/").split("/").pop()?.trim() || "";
|
|
2553
|
+
if (!t || t.length > 180 || /[\u0000-\u001f\u007f]/.test(t) || t === "." || t === "..")
|
|
2554
|
+
throw g({ statusCode: 400, statusMessage: "File name is invalid or too long." });
|
|
2555
|
+
return t;
|
|
2556
|
+
}
|
|
2557
|
+
function Ee(...e) {
|
|
2558
|
+
return e.filter(Boolean).join("/");
|
|
2559
|
+
}
|
|
2560
|
+
function _t(e) {
|
|
2561
|
+
return e.replace(/^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}-/, "");
|
|
2562
|
+
}
|
|
2563
|
+
function Rt(e) {
|
|
2564
|
+
const t = e.split(".").pop()?.toLowerCase() || "";
|
|
2565
|
+
return { pdf: "application/pdf", jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", txt: "text/plain", csv: "text/csv" }[t] || "application/octet-stream";
|
|
2566
|
+
}
|
|
2567
|
+
function We(e, t) {
|
|
2568
|
+
throw Number.isInteger(e?.statusCode) && e.statusCode >= 400 && e.statusCode < 600 ? e : (console.error("File Zone " + t + " failed:", e), g({ statusCode: 500, statusMessage: "Could not " + t + " file." }));
|
|
2569
|
+
}
|
|
2570
|
+
function xa({ filezone: e }) {
|
|
2571
|
+
const t = J();
|
|
2572
|
+
async function s(r) {
|
|
2573
|
+
const { data: n, error: a } = await r.supabase.from("project_modules").select("type, project_id, config").eq("id", r.instanceId).maybeSingle();
|
|
2574
|
+
if (a || n?.type !== "filezone" || n.project_id !== r.projectId)
|
|
2575
|
+
throw g({ statusCode: 404, statusMessage: "File Zone module is unavailable." });
|
|
2576
|
+
const c = n.config?.storageIntegration;
|
|
2577
|
+
if (typeof c != "string" || !/^[a-f0-9-]{36}$/i.test(c))
|
|
2578
|
+
throw g({ statusCode: 400, statusMessage: "File Zone has no valid storage integration." });
|
|
2579
|
+
const { data: i, error: o } = await r.supabase.from("integrations").select("id, type, config").eq("id", c).eq("project_id", r.projectId).maybeSingle();
|
|
2580
|
+
if (o) throw g({ statusCode: 500, statusMessage: "Could not check storage integration." });
|
|
2581
|
+
if (!i || !na.has(i.type))
|
|
2582
|
+
throw g({ statusCode: 400, statusMessage: "The storage integration is unavailable for this project." });
|
|
2583
|
+
return { provider: i, root: De(n.config?.folderPath, !0) };
|
|
2584
|
+
}
|
|
2585
|
+
return t.get("/files", b(async (r) => {
|
|
2586
|
+
const n = He(r), a = v(r), c = De(a.folder, !0), i = a.cursor === void 0 ? null : typeof a.cursor == "string" && a.cursor.length <= 4096 ? a.cursor : null;
|
|
2587
|
+
if (a.cursor !== void 0 && i === null) throw g({ statusCode: 400, statusMessage: "Invalid file page." });
|
|
2588
|
+
try {
|
|
2589
|
+
const { provider: o, root: d } = await s(n), { objects: u, nextCursor: l } = await e.list(r, o, Ee(d, c), i), f = u.flatMap((p) => {
|
|
2590
|
+
const m = d ? p.key.startsWith(d + "/") ? p.key.slice(d.length + 1) : "" : p.key;
|
|
2591
|
+
if (!m || m.split("/").slice(0, -1).join("/") !== c) return [];
|
|
2592
|
+
try {
|
|
2593
|
+
De(m);
|
|
2594
|
+
} catch {
|
|
2595
|
+
return [];
|
|
2596
|
+
}
|
|
2597
|
+
const h = _t(p.name);
|
|
2598
|
+
return [{
|
|
2599
|
+
path: m,
|
|
2600
|
+
name: h,
|
|
2601
|
+
directory: p.directory,
|
|
2602
|
+
size_bytes: p.size,
|
|
2603
|
+
content_type: p.directory ? null : p.contentType || Rt(h),
|
|
2604
|
+
modified_at: p.modified
|
|
2605
|
+
}];
|
|
2606
|
+
});
|
|
2607
|
+
return se(r, "Cache-Control", "private, no-store"), { files: f, nextCursor: l };
|
|
2608
|
+
} catch (o) {
|
|
2609
|
+
We(o, "list");
|
|
2610
|
+
}
|
|
2611
|
+
})), t.post("/files", b(async (r) => {
|
|
2612
|
+
const n = He(r, !0), a = De(v(r).folder, !0), { provider: c, root: i } = await s(n);
|
|
2613
|
+
let o;
|
|
2614
|
+
try {
|
|
2615
|
+
o = await e.parseUpload(r);
|
|
2616
|
+
const d = vt(o.name);
|
|
2617
|
+
if (!Number.isInteger(o.size) || o.size < 1 || o.size > e.maxBytes)
|
|
2618
|
+
throw g({ statusCode: 413, statusMessage: "File must be between 1 byte and 25 MB." });
|
|
2619
|
+
const u = globalThis.crypto.randomUUID() + "-" + d, l = Ee(i, a, u), f = o.contentType && /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i.test(o.contentType) ? o.contentType.toLowerCase() : "application/octet-stream";
|
|
2620
|
+
try {
|
|
2621
|
+
await e.upload(r, c, l, o.localPath, f);
|
|
2622
|
+
} catch (p) {
|
|
2623
|
+
throw await e.remove(r, c, l).catch((m) => console.error("File Zone partial upload cleanup failed:", m)), p;
|
|
2624
|
+
}
|
|
2625
|
+
return se(r, "Cache-Control", "private, no-store"), {
|
|
2626
|
+
path: Ee(a, u),
|
|
2627
|
+
name: d,
|
|
2628
|
+
directory: !1,
|
|
2629
|
+
size_bytes: o.size,
|
|
2630
|
+
content_type: f,
|
|
2631
|
+
modified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2632
|
+
};
|
|
2633
|
+
} catch (d) {
|
|
2634
|
+
We(d, "upload");
|
|
2635
|
+
} finally {
|
|
2636
|
+
await o?.cleanup();
|
|
2637
|
+
}
|
|
2638
|
+
})), t.get("/files/content", b(async (r) => {
|
|
2639
|
+
const n = He(r), a = v(r), c = De(a.path), i = _t(vt(c.split("/").pop())), { provider: o, root: d } = await s(n);
|
|
2640
|
+
try {
|
|
2641
|
+
const u = await e.read(r, o, Ee(d, c)), l = Rt(i), f = a.view === "1" && /^(application\/pdf|image\/(jpeg|png|gif|webp))$/.test(l), p = i.replace(/[^\x20-\x7e]|["\\]/g, "_");
|
|
2642
|
+
return se(r, "Content-Type", f ? l : "application/octet-stream"), se(r, "Content-Disposition", (f ? "inline" : "attachment") + '; filename="' + p + `"; filename*=UTF-8''` + encodeURIComponent(i)), se(r, "X-Content-Type-Options", "nosniff"), se(r, "Cache-Control", "private, no-store"), se(r, "Content-Security-Policy", "sandbox"), u;
|
|
2643
|
+
} catch (u) {
|
|
2644
|
+
We(u, "read");
|
|
2645
|
+
}
|
|
2646
|
+
})), t.handler;
|
|
2647
|
+
}
|
|
2648
|
+
const ia = "https://graph.facebook.com/v25.0/";
|
|
2649
|
+
class F extends Error {
|
|
2650
|
+
constructor(t, s, r = null) {
|
|
2651
|
+
super(s), this.statusCode = t, this.code = r, this.name = "WhatsAppProviderError";
|
|
2652
|
+
}
|
|
2653
|
+
statusCode;
|
|
2654
|
+
code;
|
|
2655
|
+
}
|
|
2656
|
+
function Qt(e = fetch) {
|
|
2657
|
+
return async (t, s, r = {}) => {
|
|
2658
|
+
if (!/^\d{1,30}(?:\/(?:phone_numbers|message_templates|template_analytics|whatsapp_business_profile))?$/.test(s))
|
|
2659
|
+
throw new F(500, "Invalid WhatsApp API operation.");
|
|
2660
|
+
const n = new URL(s, ia);
|
|
2661
|
+
for (const [i, o] of Object.entries(r.query || {}))
|
|
2662
|
+
o !== void 0 && i !== "access_token" && n.searchParams.set(i, String(o));
|
|
2663
|
+
const a = new AbortController(), c = setTimeout(() => a.abort(), Math.max(1, Math.min(15e3, r.timeoutMs || 15e3)));
|
|
2664
|
+
try {
|
|
2665
|
+
const i = await e(n.toString(), {
|
|
2666
|
+
method: r.method || "GET",
|
|
2667
|
+
headers: { Authorization: `Bearer ${t}`, "Content-Type": "application/json" },
|
|
2668
|
+
...r.body ? { body: JSON.stringify(r.body) } : {},
|
|
2669
|
+
signal: a.signal,
|
|
2670
|
+
redirect: "error"
|
|
2671
|
+
});
|
|
2672
|
+
if (Number(i.headers.get("content-length")) > 4e6)
|
|
2673
|
+
throw new F(502, "Meta returned too much data. Narrow the request.");
|
|
2674
|
+
const o = i.body?.getReader(), d = [];
|
|
2675
|
+
let u = 0;
|
|
2676
|
+
if (o)
|
|
2677
|
+
for (; ; ) {
|
|
2678
|
+
const m = await o.read();
|
|
2679
|
+
if (m.done) break;
|
|
2680
|
+
if (u += m.value.byteLength, u > 4e6)
|
|
2681
|
+
throw await o.cancel(), new F(502, "Meta returned too much data. Narrow the request.");
|
|
2682
|
+
d.push(m.value);
|
|
2683
|
+
}
|
|
2684
|
+
const l = new Uint8Array(u);
|
|
2685
|
+
let f = 0;
|
|
2686
|
+
for (const m of d)
|
|
2687
|
+
l.set(m, f), f += m.byteLength;
|
|
2688
|
+
let p;
|
|
2689
|
+
try {
|
|
2690
|
+
p = JSON.parse(new TextDecoder().decode(l));
|
|
2691
|
+
} catch {
|
|
2692
|
+
throw new F(502, "Meta returned an unreadable response.");
|
|
2693
|
+
}
|
|
2694
|
+
if (!i.ok || p?.error) {
|
|
2695
|
+
const m = Number.isSafeInteger(p?.error?.code) ? p.error.code : null;
|
|
2696
|
+
throw m === 190 || i.status === 401 ? new F(424, "The WhatsApp access token is invalid or expired. Reconnect it in Studio project integrations.", m) : [10, 200, 294].includes(m) || i.status === 403 ? new F(424, "Meta denied this operation. Check the token permissions and access to this WhatsApp account.", m) : [4, 17, 32, 613, 80007, 130429].includes(m) || i.status === 429 ? new F(429, "Meta is limiting requests. Wait before trying again.", m) : new F(
|
|
2697
|
+
i.status >= 500 ? 502 : 422,
|
|
2698
|
+
`Meta could not complete this request${m === null ? "" : ` (code ${m})`}. Check the account and template requirements.`,
|
|
2699
|
+
m
|
|
2700
|
+
);
|
|
2701
|
+
}
|
|
2702
|
+
if (!p || typeof p != "object" || Array.isArray(p))
|
|
2703
|
+
throw new F(502, "Meta returned an unexpected response.");
|
|
2704
|
+
return p;
|
|
2705
|
+
} catch (i) {
|
|
2706
|
+
throw i instanceof F ? i : new F(
|
|
2707
|
+
a.signal.aborted ? 504 : 502,
|
|
2708
|
+
a.signal.aborted ? "The WhatsApp request timed out. Refresh before retrying a change." : "Could not reach the WhatsApp API."
|
|
2709
|
+
);
|
|
2710
|
+
} finally {
|
|
2711
|
+
clearTimeout(c);
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
function Je(e, t, s) {
|
|
2716
|
+
const r = Date.now() + s;
|
|
2717
|
+
let n = 0;
|
|
2718
|
+
return async (a, c, i = {}) => {
|
|
2719
|
+
const o = r - Date.now();
|
|
2720
|
+
if (++n > t || o <= 0)
|
|
2721
|
+
throw new F(504, "The WhatsApp data request reached its time or page limit. Try a shorter period.");
|
|
2722
|
+
return e(a, c, { ...i, timeoutMs: Math.min(15e3, o) });
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function fe(e) {
|
|
2726
|
+
throw e instanceof F ? g({ statusCode: e.statusCode, statusMessage: e.message }) : g({ statusCode: 502, statusMessage: "Could not complete the WhatsApp request." });
|
|
2727
|
+
}
|
|
2728
|
+
function S(e, t = 2048) {
|
|
2729
|
+
return typeof e == "string" && e.length ? e.slice(0, t) : null;
|
|
2730
|
+
}
|
|
2731
|
+
function Te(e) {
|
|
2732
|
+
const t = e.paging?.cursors?.after;
|
|
2733
|
+
return e.paging?.next && typeof t == "string" && t.length <= 2048 && !/[\r\n]/.test(t) ? t : null;
|
|
2734
|
+
}
|
|
2735
|
+
function ve(e) {
|
|
2736
|
+
if (!Array.isArray(e.data) || e.data.length > 1e4)
|
|
2737
|
+
throw new F(502, "Meta returned an unexpected response.");
|
|
2738
|
+
return e.data.filter((t) => t !== null && typeof t == "object" && !Array.isArray(t));
|
|
2739
|
+
}
|
|
2740
|
+
const st = (e) => !/[{}]/.test(e), Ge = x().trim().min(1).max(25).refine(st, "Button labels cannot contain variables."), ca = rs("type", [
|
|
2741
|
+
O({ type: ke("QUICK_REPLY"), text: Ge }).strict(),
|
|
2742
|
+
O({ type: ke("URL"), text: Ge, url: x().trim().url().max(2e3).refine((e) => /^https:\/\//i.test(e) && st(e), "Use a static HTTPS URL without variables.") }).strict(),
|
|
2743
|
+
O({ type: ke("PHONE_NUMBER"), text: Ge, phoneNumber: x().regex(/^\+[1-9]\d{1,14}$/, "Use an international phone number starting with +.") }).strict()
|
|
2744
|
+
]), Zt = O({
|
|
2745
|
+
name: x().min(1).max(512).regex(/^[a-z0-9_]+$/, "Template names use lowercase letters, numbers and underscores."),
|
|
2746
|
+
language: x().regex(/^[a-z]{2,3}(?:_[A-Z]{2})?$/, "Use a WhatsApp language code, for example en_US or nl."),
|
|
2747
|
+
category: Ke(["UTILITY", "MARKETING"]),
|
|
2748
|
+
headerText: x().max(60).optional(),
|
|
2749
|
+
headerExample: x().trim().max(1024).optional(),
|
|
2750
|
+
bodyText: x().min(1).max(1024).refine((e) => e.trim().length > 0, "Body text is required."),
|
|
2751
|
+
bodyExamples: it(x().trim().max(1024)).max(20).optional(),
|
|
2752
|
+
footerText: x().max(60).refine(st, "Footer text cannot contain variables.").optional(),
|
|
2753
|
+
buttons: it(ca).max(3).optional()
|
|
2754
|
+
}).strict();
|
|
2755
|
+
function ie(e) {
|
|
2756
|
+
const t = [...e.matchAll(/\{\{([1-9]\d*)\}\}/g)].map((n) => n[1]), s = e.replace(/\{\{[1-9]\d*\}\}/g, "");
|
|
2757
|
+
if (/[{}]/.test(s)) throw new Error("Use numbered variables such as {{1}} and {{2}}.");
|
|
2758
|
+
const r = [...new Set(t)].sort((n, a) => Number(n) - Number(a));
|
|
2759
|
+
if (r.some((n, a) => Number(n) !== a + 1)) throw new Error("Number variables consecutively starting at {{1}}.");
|
|
2760
|
+
return r;
|
|
2761
|
+
}
|
|
2762
|
+
function Xt(e, t) {
|
|
2763
|
+
const s = ie(e.headerText || ""), r = ie(e.bodyText);
|
|
2764
|
+
if (s.length > 1) throw new Error("A text header supports one numbered variable.");
|
|
2765
|
+
if (r.length > 20) throw new Error("This editor supports up to 20 body variables.");
|
|
2766
|
+
if (t && s.length && !e.headerExample?.trim()) throw new Error("Provide an example for the header variable.");
|
|
2767
|
+
if (t && (e.bodyExamples?.length || 0) !== r.length) throw new Error("Provide exactly one example for each body variable.");
|
|
2768
|
+
if (t && e.bodyExamples?.some((c) => !c.trim())) throw new Error("Variable examples cannot be empty.");
|
|
2769
|
+
const n = e.buttons || [], a = n.filter((c) => c.type === "QUICK_REPLY").length;
|
|
2770
|
+
if (a && a !== n.length) throw new Error("Use either quick replies or action buttons in this editor.");
|
|
2771
|
+
if (n.filter((c) => c.type === "URL").length > 1 || n.filter((c) => c.type === "PHONE_NUMBER").length > 1)
|
|
2772
|
+
throw new Error("Use at most one website and one phone button.");
|
|
2773
|
+
}
|
|
2774
|
+
function xt(e) {
|
|
2775
|
+
const t = Zt.safeParse(e);
|
|
2776
|
+
if (!t.success) throw g({ statusCode: 400, statusMessage: t.error.issues[0]?.message || "Invalid template." });
|
|
2777
|
+
try {
|
|
2778
|
+
Xt(t.data, !0);
|
|
2779
|
+
} catch (s) {
|
|
2780
|
+
throw g({ statusCode: 400, statusMessage: s.message });
|
|
2781
|
+
}
|
|
2782
|
+
return t.data;
|
|
2783
|
+
}
|
|
2784
|
+
function Ft(e) {
|
|
2785
|
+
const t = [];
|
|
2786
|
+
return e.headerText && t.push({
|
|
2787
|
+
type: "HEADER",
|
|
2788
|
+
format: "TEXT",
|
|
2789
|
+
text: e.headerText,
|
|
2790
|
+
...ie(e.headerText).length ? { example: { header_text: [e.headerExample] } } : {}
|
|
2791
|
+
}), t.push({
|
|
2792
|
+
type: "BODY",
|
|
2793
|
+
text: e.bodyText,
|
|
2794
|
+
...ie(e.bodyText).length ? { example: { body_text: [e.bodyExamples] } } : {}
|
|
2795
|
+
}), e.footerText && t.push({ type: "FOOTER", text: e.footerText }), e.buttons?.length && t.push({ type: "BUTTONS", buttons: e.buttons.map((s) => s.type === "PHONE_NUMBER" ? { type: s.type, text: s.text, phone_number: s.phoneNumber } : { ...s }) }), t;
|
|
2796
|
+
}
|
|
2797
|
+
const Pe = (e, t) => Object.keys(e).every((s) => t.includes(s));
|
|
2798
|
+
function da(e) {
|
|
2799
|
+
if (!["UTILITY", "MARKETING"].includes(e.category) || e.parameter_format && e.parameter_format !== "POSITIONAL" || e.source === "AUTO_GENERATED" || !Array.isArray(e.components)) return null;
|
|
2800
|
+
const t = { name: e.name, language: e.language, category: e.category, bodyText: "" }, s = /* @__PURE__ */ new Set();
|
|
2801
|
+
for (const n of e.components) {
|
|
2802
|
+
if (!n || typeof n != "object" || s.has(n.type) || (s.add(n.type), !Pe(n, ["type", "format", "text", "example", "buttons"]))) return null;
|
|
2803
|
+
if (n.type === "HEADER") {
|
|
2804
|
+
if (n.format !== "TEXT" || typeof n.text != "string" || n.buttons || n.example && !Pe(n.example, ["header_text"])) return null;
|
|
2805
|
+
t.headerText = n.text, t.headerExample = n.example?.header_text?.[0] || "";
|
|
2806
|
+
} else if (n.type === "BODY") {
|
|
2807
|
+
if (typeof n.text != "string" || n.format || n.buttons || n.example && !Pe(n.example, ["body_text"])) return null;
|
|
2808
|
+
t.bodyText = n.text, t.bodyExamples = n.example?.body_text?.[0] || [];
|
|
2809
|
+
} else if (n.type === "FOOTER") {
|
|
2810
|
+
if (typeof n.text != "string" || n.format || n.buttons || n.example) return null;
|
|
2811
|
+
t.footerText = n.text;
|
|
2812
|
+
} else if (n.type === "BUTTONS") {
|
|
2813
|
+
if (!Array.isArray(n.buttons) || n.format || n.text || n.example) return null;
|
|
2814
|
+
t.buttons = [];
|
|
2815
|
+
for (const a of n.buttons) {
|
|
2816
|
+
if (!a || typeof a != "object" || !Pe(a, ["type", "text", "url", "phone_number"])) return null;
|
|
2817
|
+
if (a.type === "QUICK_REPLY" && !a.url && !a.phone_number) t.buttons.push({ type: "QUICK_REPLY", text: a.text });
|
|
2818
|
+
else if (a.type === "URL" && !a.phone_number) t.buttons.push({ type: "URL", text: a.text, url: a.url });
|
|
2819
|
+
else if (a.type === "PHONE_NUMBER" && !a.url) t.buttons.push({ type: "PHONE_NUMBER", text: a.text, phoneNumber: a.phone_number });
|
|
2820
|
+
else return null;
|
|
2821
|
+
}
|
|
2822
|
+
} else return null;
|
|
2823
|
+
}
|
|
2824
|
+
if (!s.has("BODY")) return null;
|
|
2825
|
+
const r = Zt.safeParse(t);
|
|
2826
|
+
if (!r.success) return null;
|
|
2827
|
+
try {
|
|
2828
|
+
Xt(r.data, !1);
|
|
2829
|
+
} catch {
|
|
2830
|
+
return null;
|
|
2831
|
+
}
|
|
2832
|
+
return r.data;
|
|
2833
|
+
}
|
|
2834
|
+
function Nt(e) {
|
|
2835
|
+
const t = da(e), s = ["APPROVED", "REJECTED", "PAUSED"].includes(e.status), r = (Array.isArray(e.components) ? e.components : []).slice(0, 20).map((n) => ({
|
|
2836
|
+
type: S(n?.type, 64) || "UNKNOWN",
|
|
2837
|
+
...S(n?.format, 64) ? { format: n.format } : {},
|
|
2838
|
+
...S(n?.text, 32768) ? { text: n.text.slice(0, 32768) } : {},
|
|
2839
|
+
...Array.isArray(n?.buttons) ? { buttons: n.buttons.slice(0, 20).map((a) => ({
|
|
2840
|
+
type: S(a?.type, 64) || "UNKNOWN",
|
|
2841
|
+
...S(a?.text, 1024) ? { text: a.text.slice(0, 1024) } : {},
|
|
2842
|
+
...S(a?.url, 2048) ? { url: a.url.slice(0, 2048) } : {},
|
|
2843
|
+
...S(a?.phone_number, 64) ? { phoneNumber: a.phone_number.slice(0, 64) } : {}
|
|
2844
|
+
})) } : {}
|
|
2845
|
+
}));
|
|
2846
|
+
return {
|
|
2847
|
+
id: S(e.id, 30) || "",
|
|
2848
|
+
name: S(e.name, 512) || "",
|
|
2849
|
+
language: S(e.language, 20) || "",
|
|
2850
|
+
category: S(e.category, 64) || "UNKNOWN",
|
|
2851
|
+
status: S(e.status, 64) || "UNKNOWN",
|
|
2852
|
+
quality: S(e.quality_score?.score, 64),
|
|
2853
|
+
rejectionReason: S(e.rejected_reason),
|
|
2854
|
+
components: r,
|
|
2855
|
+
editable: t !== null && s,
|
|
2856
|
+
editReason: t ? s ? null : "Meta permits editing only approved, rejected or paused templates." : "This template uses a format this editor cannot safely change. Manage it in WhatsApp Manager.",
|
|
2857
|
+
draft: t
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
function ua(e, t) {
|
|
2861
|
+
if (!e.editable || !e.draft) throw g({ statusCode: 409, statusMessage: e.editReason || "This template cannot be edited here." });
|
|
2862
|
+
if (t.name !== e.name || t.language !== e.language || t.category !== e.category)
|
|
2863
|
+
throw g({ statusCode: 400, statusMessage: "Name, language and category must remain unchanged. Create a new template to change them." });
|
|
2864
|
+
const s = e.draft;
|
|
2865
|
+
if (JSON.stringify(ie(s.bodyText)) !== JSON.stringify(ie(t.bodyText)) || JSON.stringify(ie(s.headerText || "")) !== JSON.stringify(ie(t.headerText || "")))
|
|
2866
|
+
throw g({ statusCode: 400, statusMessage: "Create a new template to change variables used by existing automations." });
|
|
2867
|
+
if (JSON.stringify((s.buttons || []).map((r) => r.type)) !== JSON.stringify((t.buttons || []).map((r) => r.type)))
|
|
2868
|
+
throw g({ statusCode: 400, statusMessage: "Create a new template to change button count, types or order used by existing automations." });
|
|
2869
|
+
}
|
|
2870
|
+
const pe = (e) => typeof e == "number" && Number.isFinite(e) && e >= 0 ? e : null, oe = (e) => e.length && e.every((t) => t !== null) ? e.reduce((t, s) => t + s, 0) : null;
|
|
2871
|
+
function la(e) {
|
|
2872
|
+
if (!e.analytics || !Array.isArray(e.analytics.data_points))
|
|
2873
|
+
return { state: "unavailable", reason: "Meta did not provide message analytics for this account and period.", data: null };
|
|
2874
|
+
const t = e.analytics.data_points.filter((s) => Number.isSafeInteger(s?.start) && Number.isSafeInteger(s?.end)).map((s) => ({ start: s.start, end: s.end, sent: pe(s.sent), delivered: pe(s.delivered) }));
|
|
2875
|
+
return t.length !== e.analytics.data_points.length ? { state: "unavailable", reason: "Meta returned incomplete message analytics.", data: null } : {
|
|
2876
|
+
state: t.length ? "available" : "empty",
|
|
2877
|
+
reason: t.length ? null : "Meta returned no message data for this period.",
|
|
2878
|
+
data: { sent: oe(t.map((s) => s.sent)), delivered: oe(t.map((s) => s.delivered)), points: t }
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
function ma(e, t, s, r) {
|
|
2882
|
+
if (e.some((o) => !Array.isArray(o.data_points)))
|
|
2883
|
+
return { state: "unavailable", reason: "Meta returned an unsupported template analytics response.", data: null };
|
|
2884
|
+
const n = new Map(t.map((o) => [o.id, o])), a = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set();
|
|
2885
|
+
for (const o of e)
|
|
2886
|
+
for (const d of o.data_points) {
|
|
2887
|
+
if (!d || !n.has(d.template_id) || !Number.isSafeInteger(d.start) || !Number.isSafeInteger(d.end)) continue;
|
|
2888
|
+
const u = `${d.template_id}:${d.start}:${d.end}`;
|
|
2889
|
+
if (c.has(u)) continue;
|
|
2890
|
+
c.add(u);
|
|
2891
|
+
const l = a.get(d.template_id) || [];
|
|
2892
|
+
l.push(d), a.set(d.template_id, l);
|
|
2893
|
+
}
|
|
2894
|
+
const i = t.map((o) => {
|
|
2895
|
+
const d = a.get(o.id) || [];
|
|
2896
|
+
return {
|
|
2897
|
+
id: o.id,
|
|
2898
|
+
name: S(o.name, 512) || o.id,
|
|
2899
|
+
language: S(o.language, 20) || "",
|
|
2900
|
+
sent: oe(d.map((u) => pe(u.sent))),
|
|
2901
|
+
delivered: oe(d.map((u) => pe(u.delivered))),
|
|
2902
|
+
read: oe(d.map((u) => pe(u.read))),
|
|
2903
|
+
clicked: oe(d.map((u) => Array.isArray(u.clicked) ? u.clicked.length ? oe(u.clicked.map((l) => pe(l?.count))) : 0 : null)),
|
|
2904
|
+
amountSpent: oe(d.map((u) => Array.isArray(u.cost) ? pe(u.cost.find((l) => l?.type === "amount_spent")?.value) : null))
|
|
2905
|
+
};
|
|
2906
|
+
});
|
|
2907
|
+
return {
|
|
2908
|
+
state: c.size ? "available" : "empty",
|
|
2909
|
+
reason: r ? "Only part of the account could be loaded. Metrics shown cover completed template queries only." : c.size ? null : "Meta returned no template metrics for this period. Missing values are not zero.",
|
|
2910
|
+
data: { templates: i, currency: s, truncated: r }
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
const Fe = x().regex(/^\d{1,30}$/, "Use the numeric ID from Meta."), es = x().trim().min(10, "Enter a valid access token.").max(16384).regex(/^\S+$/, "The token cannot contain whitespace."), at = O({
|
|
2914
|
+
wabaId: Fe,
|
|
2915
|
+
accessToken: es
|
|
2916
|
+
}).strict(), fa = at.extend({ phoneNumberId: Fe }), pa = at.extend({ phoneNumberId: Fe.optional() }), ts = "id,display_phone_number,verified_name,quality_rating,code_verification_status";
|
|
2917
|
+
function ss(e) {
|
|
2918
|
+
return {
|
|
2919
|
+
id: S(e.id, 30) || "",
|
|
2920
|
+
displayPhoneNumber: S(e.display_phone_number, 64),
|
|
2921
|
+
verifiedName: S(e.verified_name, 256),
|
|
2922
|
+
qualityRating: S(e.quality_rating, 64),
|
|
2923
|
+
verificationStatus: S(e.code_verification_status, 64)
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
async function ga(e, t = Qt()) {
|
|
2927
|
+
const s = pa.safeParse(e);
|
|
2928
|
+
if (!s.success) throw new F(400, s.error.issues[0]?.message || "Invalid WhatsApp connection.");
|
|
2929
|
+
const { wabaId: r, accessToken: n, phoneNumberId: a } = s.data, c = 5, i = Je(t, c + 1, 3e4), o = await i(n, r, { query: { fields: "id,name" } });
|
|
2930
|
+
if (o.id !== r) throw new F(422, "Meta did not return the requested WhatsApp Business account.");
|
|
2931
|
+
const d = [];
|
|
2932
|
+
let u = null;
|
|
2933
|
+
const l = /* @__PURE__ */ new Set();
|
|
2934
|
+
for (let f = 0; f < c; f++) {
|
|
2935
|
+
const p = await i(n, `${r}/phone_numbers`, { query: { fields: ts, limit: 100, after: u || void 0 } });
|
|
2936
|
+
for (const m of ve(p))
|
|
2937
|
+
Fe.safeParse(m.id).success && !d.some((h) => h.id === m.id) && d.push(ss(m));
|
|
2938
|
+
if (u = Te(p), !u) break;
|
|
2939
|
+
if (l.has(u)) throw new F(502, "Meta returned a repeated page. Try again later.");
|
|
2940
|
+
l.add(u);
|
|
2941
|
+
}
|
|
2942
|
+
if (a && !d.some((f) => f.id === a))
|
|
2943
|
+
throw new F(400, u ? "The selected number could not be verified within this account. Use a token with access to the correct number." : "The selected phone number does not belong to this WhatsApp Business account.");
|
|
2944
|
+
return { wabaId: r, wabaName: S(o.name, 256), phones: d, truncated: u !== null };
|
|
2945
|
+
}
|
|
2946
|
+
const Fa = {
|
|
2947
|
+
verify: ga,
|
|
2948
|
+
testSchema: at,
|
|
2949
|
+
connectSchema: fa,
|
|
2950
|
+
toHttpError: fe
|
|
2951
|
+
}, Et = "whatsappBusiness", Pt = "id,name,language,category,status,quality_score,rejected_reason,components,parameter_format,source", kt = 20, Ie = Fe, ha = O({
|
|
2952
|
+
wabaId: Ie,
|
|
2953
|
+
phoneNumberId: Ie,
|
|
2954
|
+
displayPhoneNumber: x().max(64).nullish().transform((e) => e || ""),
|
|
2955
|
+
verifiedName: x().max(256).nullish().transform((e) => e || ""),
|
|
2956
|
+
accessToken: x().min(1).max(32768)
|
|
2957
|
+
}), ya = x().min(1).max(2048).refine((e) => !/[\r\n]/.test(e)), Ut = O({
|
|
2958
|
+
search: x().trim().min(1).max(200).optional(),
|
|
2959
|
+
name: x().min(1).max(512).regex(/^[a-z0-9_]+$/).optional(),
|
|
2960
|
+
after: ya.optional(),
|
|
2961
|
+
status: Ke(["APPROVED", "ARCHIVED", "DELETED", "DISABLED", "IN_APPEAL", "LIMIT_EXCEEDED", "PAUSED", "PENDING", "PENDING_DELETION", "REJECTED"]).optional()
|
|
2962
|
+
}).strict(), wa = (e) => g({ statusCode: 400, statusMessage: e });
|
|
2963
|
+
function be(e, t) {
|
|
2964
|
+
const s = e.safeParse(t);
|
|
2965
|
+
if (!s.success) throw wa(s.error.issues[0]?.message || "Invalid request.");
|
|
2966
|
+
return s.data;
|
|
2967
|
+
}
|
|
2968
|
+
function Oe(e) {
|
|
2969
|
+
return {
|
|
2970
|
+
view: e.isAdmin || e.permissions.includes("view-whatsapp"),
|
|
2971
|
+
manageTemplates: e.isAdmin || e.permissions.includes("manage-whatsapp-templates"),
|
|
2972
|
+
manageInsights: e.isAdmin || e.permissions.includes("manage-whatsapp-insights")
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
function ba(e, t) {
|
|
2976
|
+
return {
|
|
2977
|
+
connected: t !== null,
|
|
2978
|
+
canManageConnection: e.isAdmin,
|
|
2979
|
+
permissions: Oe(e),
|
|
2980
|
+
wabaId: t?.wabaId || null,
|
|
2981
|
+
phoneNumberId: t?.phoneNumberId || null,
|
|
2982
|
+
displayPhoneNumber: t?.displayPhoneNumber || null,
|
|
2983
|
+
verifiedName: t?.verifiedName || null,
|
|
2984
|
+
updatedAt: null
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
function Ce(e) {
|
|
2988
|
+
return { state: "unavailable", reason: e instanceof F ? e.message : "Meta did not make this information available.", data: null };
|
|
2989
|
+
}
|
|
2990
|
+
function Na(e) {
|
|
2991
|
+
const t = e.graphRequest || Qt(), s = J();
|
|
2992
|
+
async function r(i, o = "view") {
|
|
2993
|
+
ns(i, "Cache-Control", "private, no-store");
|
|
2994
|
+
const d = i.context.module;
|
|
2995
|
+
if (!d?.supabase || !d.instanceId || !d.projectId || d.moduleType !== Et || !d.userId || !Array.isArray(d.permissions))
|
|
2996
|
+
throw g({ statusCode: 403, statusMessage: "WhatsApp module access is unavailable." });
|
|
2997
|
+
if (!Oe(d).view || !Oe(d)[o])
|
|
2998
|
+
throw g({ statusCode: 403, statusMessage: "You do not have permission for this WhatsApp action." });
|
|
2999
|
+
const { data: u, error: l } = await d.supabase.from("project_modules").select("id, config").eq("id", d.instanceId).eq("project_id", d.projectId).eq("type", Et).maybeSingle();
|
|
3000
|
+
if (l) throw g({ statusCode: 503, statusMessage: "Could not verify the WhatsApp module." });
|
|
3001
|
+
if (!u) throw g({ statusCode: 404, statusMessage: "WhatsApp module not found in this project." });
|
|
3002
|
+
const f = u.config?.account;
|
|
3003
|
+
if (f && !x().uuid().safeParse(f).success)
|
|
3004
|
+
throw g({ statusCode: 424, statusMessage: "Select a WhatsApp integration from this project in the module settings." });
|
|
3005
|
+
return { ...d, accountId: f || void 0 };
|
|
3006
|
+
}
|
|
3007
|
+
async function n(i) {
|
|
3008
|
+
if (!i.accountId) return null;
|
|
3009
|
+
const { data: o, error: d } = await i.supabase.from("integrations").select("id, config").eq("id", i.accountId).eq("project_id", i.projectId).eq("type", "WHATSAPP").maybeSingle();
|
|
3010
|
+
if (d) throw g({ statusCode: 503, statusMessage: "Could not load the selected WhatsApp integration." });
|
|
3011
|
+
if (!o) throw g({ statusCode: 424, statusMessage: "The selected WhatsApp integration is unavailable in this project. Update the module settings." });
|
|
3012
|
+
const u = ha.safeParse(o.config);
|
|
3013
|
+
if (!u.success) throw g({ statusCode: 424, statusMessage: "The selected WhatsApp integration is incomplete. Reconnect it in project integrations." });
|
|
3014
|
+
return u.data;
|
|
3015
|
+
}
|
|
3016
|
+
async function a(i) {
|
|
3017
|
+
const o = await n(i);
|
|
3018
|
+
if (!o) throw g({ statusCode: 424, statusMessage: "Select a WhatsApp integration in the module settings first." });
|
|
3019
|
+
let d;
|
|
3020
|
+
try {
|
|
3021
|
+
if (d = e.decrypt(o.accessToken), !es.safeParse(d).success) throw new Error("Invalid stored token");
|
|
3022
|
+
} catch {
|
|
3023
|
+
throw g({ statusCode: 503, statusMessage: "The saved WhatsApp credential could not be read. Ask an administrator to reconnect it." });
|
|
3024
|
+
}
|
|
3025
|
+
return { row: o, token: d };
|
|
3026
|
+
}
|
|
3027
|
+
async function c(i, o, d, u) {
|
|
3028
|
+
const l = Je(t, kt, 3e4);
|
|
3029
|
+
let f = null;
|
|
3030
|
+
const p = /* @__PURE__ */ new Set();
|
|
3031
|
+
for (let m = 0; m < kt; m++) {
|
|
3032
|
+
const h = await l(i, `${o}/message_templates`, {
|
|
3033
|
+
query: { fields: Pt, limit: 100, name: u, after: f || void 0 }
|
|
3034
|
+
}), y = ve(h).find((w) => w.id === d);
|
|
3035
|
+
if (y) return Nt(y);
|
|
3036
|
+
if (f = Te(h), !f) throw g({ statusCode: 404, statusMessage: "Template not found in the connected WhatsApp account." });
|
|
3037
|
+
if (p.has(f)) throw new F(502, "Meta returned a repeated page. Try again later.");
|
|
3038
|
+
p.add(f);
|
|
3039
|
+
}
|
|
3040
|
+
throw g({ statusCode: 409, statusMessage: "This account has too many templates to search at once. Open the template from an exact-name search." });
|
|
3041
|
+
}
|
|
3042
|
+
return s.get("/connection", b(async (i) => {
|
|
3043
|
+
const o = await r(i);
|
|
3044
|
+
return ba(o, await n(o));
|
|
3045
|
+
})), s.get("/templates", b(async (i) => {
|
|
3046
|
+
const o = await r(i), { row: d, token: u } = await a(o), l = be(Ut, v(i));
|
|
3047
|
+
try {
|
|
3048
|
+
const f = await t(u, `${d.wabaId}/message_templates`, {
|
|
3049
|
+
query: { fields: Pt, limit: 50, name: l.name, name_or_content: l.search, status: l.status ? JSON.stringify([l.status]) : void 0, after: l.after }
|
|
3050
|
+
});
|
|
3051
|
+
return { templates: ve(f).filter((p) => !l.name || p.name === l.name).map(Nt), nextCursor: Te(f) };
|
|
3052
|
+
} catch (f) {
|
|
3053
|
+
fe(f);
|
|
3054
|
+
}
|
|
3055
|
+
})), s.get("/templates/:id", b(async (i) => {
|
|
3056
|
+
const o = await r(i), { row: d, token: u } = await a(o), l = be(Ie, U(i, "id")), f = be(O({ name: Ut.shape.name }).strict(), v(i));
|
|
3057
|
+
try {
|
|
3058
|
+
return await c(u, d.wabaId, l, f.name);
|
|
3059
|
+
} catch (p) {
|
|
3060
|
+
throw p instanceof F && fe(p), p;
|
|
3061
|
+
}
|
|
3062
|
+
})), s.post("/templates", b(async (i) => {
|
|
3063
|
+
const o = await r(i, "manageTemplates"), { row: d, token: u } = await a(o), l = xt(await j(i));
|
|
3064
|
+
try {
|
|
3065
|
+
const f = await t(u, `${d.wabaId}/message_templates`, { method: "POST", body: {
|
|
3066
|
+
name: l.name,
|
|
3067
|
+
language: l.language,
|
|
3068
|
+
category: l.category,
|
|
3069
|
+
parameter_format: "POSITIONAL",
|
|
3070
|
+
components: Ft(l)
|
|
3071
|
+
} });
|
|
3072
|
+
if (!Ie.safeParse(f.id).success) throw new F(502, "Meta did not confirm the template ID. Refresh the template list before trying again.");
|
|
3073
|
+
return { id: f.id, status: S(f.status, 64), category: S(f.category, 64), success: !0 };
|
|
3074
|
+
} catch (f) {
|
|
3075
|
+
fe(f);
|
|
3076
|
+
}
|
|
3077
|
+
})), s.patch("/templates/:id", b(async (i) => {
|
|
3078
|
+
const o = await r(i, "manageTemplates"), { row: d, token: u } = await a(o), l = be(Ie, U(i, "id")), f = xt(await j(i));
|
|
3079
|
+
let p;
|
|
3080
|
+
try {
|
|
3081
|
+
p = await c(u, d.wabaId, l, f.name);
|
|
3082
|
+
} catch (m) {
|
|
3083
|
+
throw m instanceof F && fe(m), m;
|
|
3084
|
+
}
|
|
3085
|
+
ua(p, f);
|
|
3086
|
+
try {
|
|
3087
|
+
if ((await t(u, l, { method: "POST", body: { components: Ft(f) } })).success !== !0) throw new F(502, "Meta did not confirm the change. Refresh the template before trying again.");
|
|
3088
|
+
return { id: l, status: null, category: p.category, success: !0 };
|
|
3089
|
+
} catch (m) {
|
|
3090
|
+
fe(m);
|
|
3091
|
+
}
|
|
3092
|
+
})), s.get("/overview", b(async (i) => {
|
|
3093
|
+
const o = await r(i), { row: d, token: u } = await a(o), [l, f, p, m] = await Promise.allSettled([
|
|
3094
|
+
t(u, d.wabaId, { query: { fields: "id,name,status,currency,business_verification_status,account_review_status" } }),
|
|
3095
|
+
t(u, d.phoneNumberId, { query: { fields: ts } }),
|
|
3096
|
+
t(u, d.phoneNumberId, { query: { fields: "health_status" } }),
|
|
3097
|
+
t(u, `${d.phoneNumberId}/whatsapp_business_profile`, { query: { fields: "about,address,description,email,vertical,websites" } })
|
|
3098
|
+
]), h = m.status === "fulfilled" && Array.isArray(m.value.data) ? m.value.data[0] : null;
|
|
3099
|
+
return {
|
|
3100
|
+
account: l.status === "fulfilled" ? { state: "available", reason: null, data: {
|
|
3101
|
+
id: d.wabaId,
|
|
3102
|
+
name: S(l.value.name, 256),
|
|
3103
|
+
status: S(l.value.status, 64),
|
|
3104
|
+
currency: S(l.value.currency, 10),
|
|
3105
|
+
verificationStatus: S(l.value.business_verification_status, 64),
|
|
3106
|
+
reviewStatus: S(l.value.account_review_status, 64)
|
|
3107
|
+
} } : Ce(l.reason),
|
|
3108
|
+
phone: f.status === "fulfilled" ? { state: "available", reason: null, data: ss(f.value) } : Ce(f.reason),
|
|
3109
|
+
health: p.status === "fulfilled" && p.value.health_status ? { state: "available", reason: null, data: {
|
|
3110
|
+
canSendMessage: S(p.value.health_status.can_send_message, 64),
|
|
3111
|
+
entities: (Array.isArray(p.value.health_status.entities) ? p.value.health_status.entities : []).slice(0, 100).map((y) => ({
|
|
3112
|
+
id: S(y.id, 30),
|
|
3113
|
+
type: S(y.entity_type, 64),
|
|
3114
|
+
canSendMessage: S(y.can_send_message, 64)
|
|
3115
|
+
}))
|
|
3116
|
+
} } : Ce(p.status === "rejected" ? p.reason : null),
|
|
3117
|
+
profile: h ? { state: "available", reason: null, data: {
|
|
3118
|
+
about: S(h.about),
|
|
3119
|
+
address: S(h.address),
|
|
3120
|
+
description: S(h.description),
|
|
3121
|
+
email: S(h.email, 254),
|
|
3122
|
+
vertical: S(h.vertical, 64),
|
|
3123
|
+
websites: (Array.isArray(h.websites) ? h.websites : []).filter((y) => typeof y == "string" && /^https?:\/\//i.test(y)).slice(0, 10).map((y) => y.slice(0, 2048))
|
|
3124
|
+
} } : Ce(m.status === "rejected" ? m.reason : null)
|
|
3125
|
+
};
|
|
3126
|
+
})), s.get("/insights", b(async (i) => {
|
|
3127
|
+
const o = Je(t, 18, 2e4), d = await r(i), { row: u, token: l } = await a(d), f = be(O({ days: Ke(["7", "30", "90"]).optional() }).strict(), v(i)), p = Number(f.days || 30), m = e.now?.() || /* @__PURE__ */ new Date(), h = Math.floor(Date.UTC(m.getUTCFullYear(), m.getUTCMonth(), m.getUTCDate()) / 1e3), y = h - p * 86400, w = u.displayPhoneNumber.replace(/\D/g, ""), [C, A] = await Promise.allSettled([
|
|
3128
|
+
o(l, u.wabaId, { query: { fields: "id,is_enabled_for_insights,currency" } }),
|
|
3129
|
+
/^\d{6,15}$/.test(w) ? o(l, u.wabaId, { query: { fields: `analytics.start(${y}).end(${h}).granularity(DAY).phone_numbers(${JSON.stringify([w])})` } }) : Promise.reject(new F(424, "The connected phone number is unavailable. Reconnect it to load message analytics."))
|
|
3130
|
+
]), I = C.status === "fulfilled" && typeof C.value.is_enabled_for_insights == "boolean" ? C.value.is_enabled_for_insights : null, M = C.status === "fulfilled" ? S(C.value.currency, 10) : null;
|
|
3131
|
+
let D;
|
|
3132
|
+
if (I === !1) D = { state: "unavailable", reason: "Template insights have not been enabled for this WhatsApp account.", data: null };
|
|
3133
|
+
else
|
|
3134
|
+
try {
|
|
3135
|
+
const _ = await o(l, `${u.wabaId}/message_templates`, { query: { fields: "id,name,language", limit: 50 } }), T = ve(_).filter((N) => Ie.safeParse(N.id).success).slice(0, 50), E = [];
|
|
3136
|
+
let V = Te(_) !== null;
|
|
3137
|
+
const P = [], K = Array.from({ length: Math.ceil(T.length / 10) }, (N, de) => T.slice(de * 10, de * 10 + 10)), Q = await Promise.allSettled(K.map(async (N) => {
|
|
3138
|
+
const de = [];
|
|
3139
|
+
let Z = null;
|
|
3140
|
+
const ot = /* @__PURE__ */ new Set();
|
|
3141
|
+
for (let nt = 0; nt < 3; nt++) {
|
|
3142
|
+
const rt = await o(l, `${u.wabaId}/template_analytics`, { query: {
|
|
3143
|
+
start: y,
|
|
3144
|
+
end: h,
|
|
3145
|
+
granularity: "DAILY",
|
|
3146
|
+
metric_types: JSON.stringify(["sent", "delivered", "read", "clicked"]),
|
|
3147
|
+
template_ids: JSON.stringify(N.map((as) => as.id)),
|
|
3148
|
+
use_waba_timezone: !1,
|
|
3149
|
+
limit: 1e3,
|
|
3150
|
+
after: Z || void 0
|
|
3151
|
+
} });
|
|
3152
|
+
if (de.push(...ve(rt)), Z = Te(rt), !Z) break;
|
|
3153
|
+
if (ot.has(Z)) throw new F(502, "Meta returned repeated analytics pages. Try again later.");
|
|
3154
|
+
ot.add(Z);
|
|
3155
|
+
}
|
|
3156
|
+
if (Z) throw new F(504, "Meta returned more analytics pages than this dashboard can load at once.");
|
|
3157
|
+
return { batch: N, batchPoints: de };
|
|
3158
|
+
}));
|
|
3159
|
+
for (const N of Q) {
|
|
3160
|
+
if (N.status === "rejected") {
|
|
3161
|
+
V = !0;
|
|
3162
|
+
continue;
|
|
3163
|
+
}
|
|
3164
|
+
E.push(...N.value.batchPoints), P.push(...N.value.batch);
|
|
3165
|
+
}
|
|
3166
|
+
if (Q.length && !P.length) throw Q.find((N) => N.status === "rejected").reason;
|
|
3167
|
+
D = ma(E, P, M, V);
|
|
3168
|
+
} catch (_) {
|
|
3169
|
+
D = Ce(_);
|
|
3170
|
+
}
|
|
3171
|
+
return {
|
|
3172
|
+
days: p,
|
|
3173
|
+
start: y,
|
|
3174
|
+
end: h,
|
|
3175
|
+
timezone: "UTC",
|
|
3176
|
+
phoneNumber: u.displayPhoneNumber,
|
|
3177
|
+
enabled: I,
|
|
3178
|
+
canEnable: Oe(d).manageInsights && I !== !0,
|
|
3179
|
+
messages: A.status === "fulfilled" ? la(A.value) : Ce(A.reason),
|
|
3180
|
+
templates: D
|
|
3181
|
+
};
|
|
3182
|
+
})), s.post("/insights/enable", b(async (i) => {
|
|
3183
|
+
const o = await r(i, "manageInsights");
|
|
3184
|
+
be(O({ confirm: ke(!0) }).strict(), await j(i));
|
|
3185
|
+
const { row: d, token: u } = await a(o);
|
|
3186
|
+
try {
|
|
3187
|
+
const l = await t(u, d.wabaId, { method: "POST", body: { is_enabled_for_insights: !0 } });
|
|
3188
|
+
if (l.id !== d.wabaId && l.success !== !0) throw new F(502, "Meta did not confirm insights activation. Refresh before trying again.");
|
|
3189
|
+
return { enabled: !0 };
|
|
3190
|
+
} catch (l) {
|
|
3191
|
+
fe(l);
|
|
2264
3192
|
}
|
|
2265
|
-
})),
|
|
3193
|
+
})), s.handler;
|
|
2266
3194
|
}
|
|
2267
3195
|
export {
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
3196
|
+
Sa as appointments,
|
|
3197
|
+
Ta as contentManager,
|
|
3198
|
+
xa as filezone,
|
|
3199
|
+
va as firebaseAuthManager,
|
|
3200
|
+
_a as formBuilder,
|
|
3201
|
+
Da as googleAnalytics,
|
|
3202
|
+
Ra as mailTemplates,
|
|
3203
|
+
Ma as products,
|
|
3204
|
+
Na as whatsappBusiness,
|
|
3205
|
+
Fa as whatsappBusinessConnection
|
|
2274
3206
|
};
|