@dev.smartpricing/message-composer-utils 4.3.2-fix.1 → 4.5.1-next.0
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/schemas/brand.js +2 -0
- package/dist/schemas/brand.js.map +1 -1
- package/dist/schemas/dynamic-values.js +6 -1
- package/dist/schemas/dynamic-values.js.map +1 -1
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/message-compilation.js +2 -0
- package/dist/schemas/message-compilation.js.map +1 -1
- package/dist/schemas/message-group.js +33 -6
- package/dist/schemas/message-group.js.map +1 -1
- package/dist/schemas/message-group.test.js +14 -0
- package/dist/schemas/message-group.test.js.map +1 -0
- package/dist/schemas/meta-integration.js +20 -0
- package/dist/schemas/meta-integration.js.map +1 -1
- package/dist/schemas/meta-integration.test.js +18 -0
- package/dist/schemas/meta-integration.test.js.map +1 -0
- package/dist/schemas/onboarding.js +3 -1
- package/dist/schemas/onboarding.js.map +1 -1
- package/dist/schemas/organization.js +31 -0
- package/dist/schemas/organization.js.map +1 -1
- package/dist/schemas/organization.test.js +86 -0
- package/dist/schemas/organization.test.js.map +1 -0
- package/dist/schemas/pagination.js +40 -0
- package/dist/schemas/pagination.js.map +1 -0
- package/dist/schemas/pagination.test.js +82 -0
- package/dist/schemas/pagination.test.js.map +1 -0
- package/dist/schemas/tracking.js +2 -0
- package/dist/schemas/tracking.js.map +1 -1
- package/dist/types/app.js.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/message.js +24 -0
- package/dist/types/message.js.map +1 -1
- package/dist/types/organization.js +2 -0
- package/dist/types/organization.js.map +1 -0
- package/dist/types/pagination.js +2 -0
- package/dist/types/pagination.js.map +1 -0
- package/dist/utils/templates/whatsapp-compiler.test.js +241 -0
- package/dist/utils/templates/whatsapp-compiler.test.js.map +1 -0
- package/package.json +1 -1
- package/src/schemas/brand.ts +2 -0
- package/src/schemas/dynamic-values.ts +9 -2
- package/src/schemas/index.ts +1 -0
- package/src/schemas/message-compilation.ts +2 -0
- package/src/schemas/message-group.test.ts +20 -0
- package/src/schemas/message-group.ts +45 -6
- package/src/schemas/meta-integration.test.ts +20 -0
- package/src/schemas/meta-integration.ts +30 -1
- package/src/schemas/onboarding.ts +3 -0
- package/src/schemas/organization.test.ts +143 -0
- package/src/schemas/organization.ts +39 -0
- package/src/schemas/pagination.test.ts +101 -0
- package/src/schemas/pagination.ts +49 -0
- package/src/schemas/tracking.ts +2 -0
- package/src/types/api/apiMessages.ts +5 -0
- package/src/types/app.ts +9 -23
- package/src/types/index.ts +2 -0
- package/src/types/kafka-events.ts +7 -1
- package/src/types/message.ts +98 -0
- package/src/types/organization.ts +19 -0
- package/src/types/pagination.ts +36 -0
- package/src/types/submission-job.ts +5 -0
- package/src/utils/templates/whatsapp-compiler.test.ts +282 -0
package/dist/types/message.js
CHANGED
|
@@ -9,6 +9,28 @@ export const MESSAGE_STATUS = [
|
|
|
9
9
|
];
|
|
10
10
|
export const MESSAGE_GROUP_CATEGORIES = ["marketing", "utility"];
|
|
11
11
|
export const MESSAGE_GROUP_VISIBILITY = ["public", "internal"];
|
|
12
|
+
/** `visibility` filter of the list route; `internal` and `all` are admin-only. */
|
|
13
|
+
export const MESSAGE_GROUP_LIST_VISIBILITY = [
|
|
14
|
+
...MESSAGE_GROUP_VISIBILITY,
|
|
15
|
+
"all",
|
|
16
|
+
];
|
|
17
|
+
/** Sortable columns of the list route (ADR 0012); aggregates are not sortable. */
|
|
18
|
+
export const MESSAGE_GROUP_LIST_SORT = [
|
|
19
|
+
"name",
|
|
20
|
+
"category",
|
|
21
|
+
"created_at",
|
|
22
|
+
"updated_at",
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* The Smartness application a Message Group is authored for (ADR 0011).
|
|
26
|
+
* Anything outside this list is not a Product and is rejected on write.
|
|
27
|
+
*/
|
|
28
|
+
export const MESSAGE_GROUP_PRODUCTS = [
|
|
29
|
+
"connect",
|
|
30
|
+
"chat",
|
|
31
|
+
"pms",
|
|
32
|
+
"pricing",
|
|
33
|
+
];
|
|
12
34
|
/**
|
|
13
35
|
* Brand cascade data schema for preview validation.
|
|
14
36
|
* Subset of BrandFromDb used at compile time.
|
|
@@ -24,6 +46,8 @@ export const brandCascadeDataSchema = z.object({
|
|
|
24
46
|
* Request body for previewing a message with inline data (no DB lookups).
|
|
25
47
|
*/
|
|
26
48
|
export const previewMessageRequestSchema = z.object({
|
|
49
|
+
/** Organization the preview is compiled for (ADR 0014). */
|
|
50
|
+
organization_id: z.uuid().optional(),
|
|
27
51
|
message: z
|
|
28
52
|
.object({
|
|
29
53
|
render_type: z.literal("email_v1"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/types/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/types/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,OAAO,EACL,+BAA+B,EAC/B,gCAAgC,EAChC,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,aAAa,EAAE,UAAU,CAAU,CAAC;AAGzE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,SAAS;IACT,OAAO;IACP,UAAU;IACV,UAAU;CACF,CAAC;AAmDX,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,WAAW,EAAE,SAAS,CAAU,CAAC;AAG1E,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAU,CAAC;AAGxE,kFAAkF;AAClF,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,GAAG,wBAAwB;IAC3B,KAAK;CACG,CAAC;AAIX,kFAAkF;AAClF,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,MAAM;IACN,UAAU;IACV,YAAY;IACZ,YAAY;CACJ,CAAC;AAsBX;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,SAAS;IACT,MAAM;IACN,KAAK;IACL,SAAS;CACD,CAAC;AAoEX;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,sBAAsB,EAAE,+BAA+B;IACvD,uBAAuB,EAAE,gCAAgC;IACzD,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC5D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,IAAI,EAAE,eAAe,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;CAC7C,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,2DAA2D;IAC3D,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IACpC,OAAO,EAAE,CAAC;SACP,MAAM,CAAC;QACN,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAClC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YACb,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAClD,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC5C,CAAC;QACF,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;KACvD,CAAC;SACD,WAAW,EAAE;IAChB,KAAK,EAAE,sBAAsB,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACpD,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization.js","sourceRoot":"","sources":["../../src/types/organization.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/types/pagination.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,MAAM,CAAU,CAAC"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { compileWhatsappTemplate, extractRequiredParameters, validateParameters, } from "./whatsapp-compiler.js";
|
|
3
|
+
import { mergeBrandDataParams, } from "./brand-cascade.js";
|
|
4
|
+
const mention = (label) => ({
|
|
5
|
+
type: "mention",
|
|
6
|
+
attrs: { id: label, label },
|
|
7
|
+
});
|
|
8
|
+
const text = (value) => ({ type: "text", text: value });
|
|
9
|
+
const doc = (...content) => ({
|
|
10
|
+
type: "doc",
|
|
11
|
+
content: [{ type: "paragraph", content }],
|
|
12
|
+
});
|
|
13
|
+
/** An approved, submitted WhatsApp Message with every block empty by default. */
|
|
14
|
+
function whatsappMessage(body = {}) {
|
|
15
|
+
return {
|
|
16
|
+
id: "msg-1",
|
|
17
|
+
created_at: new Date("2026-09-01T00:00:00.000Z"),
|
|
18
|
+
updated_at: new Date("2026-09-01T00:00:00.000Z"),
|
|
19
|
+
message_group_id: "group-1",
|
|
20
|
+
language_id: "en",
|
|
21
|
+
status: "approved",
|
|
22
|
+
render_type: "whatsapp_v1",
|
|
23
|
+
user_id: "user-1",
|
|
24
|
+
body: {
|
|
25
|
+
header: { type: "none" },
|
|
26
|
+
body: { enabled: true, content: undefined },
|
|
27
|
+
footer: { enabled: false, content: "" },
|
|
28
|
+
buttons: { enabled: false, buttons: [] },
|
|
29
|
+
...body,
|
|
30
|
+
},
|
|
31
|
+
metadata: {
|
|
32
|
+
meta: {
|
|
33
|
+
template_id: "1234567890",
|
|
34
|
+
template_name: "welcome_en",
|
|
35
|
+
submission_date: "2026-09-01T00:00:00.000Z",
|
|
36
|
+
translated_template: {
|
|
37
|
+
name: "welcome_en",
|
|
38
|
+
language: "en",
|
|
39
|
+
category: "UTILITY",
|
|
40
|
+
parameter_format: "NAMED",
|
|
41
|
+
allow_category_change: false,
|
|
42
|
+
components: [],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
describe("extractRequiredParameters", () => {
|
|
49
|
+
it("lists header, body and URL button keys once each, in template order", () => {
|
|
50
|
+
const message = whatsappMessage({
|
|
51
|
+
header: {
|
|
52
|
+
type: "text",
|
|
53
|
+
content: doc(text("Hi "), mention("guest_name")),
|
|
54
|
+
},
|
|
55
|
+
body: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
content: doc(mention("guest_name"), text(" your stay at "), mention("brand_name"), text(" starts "), mention("check_in")),
|
|
58
|
+
},
|
|
59
|
+
buttons: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
buttons: [
|
|
62
|
+
{
|
|
63
|
+
id: "b1",
|
|
64
|
+
type: "link",
|
|
65
|
+
text: "Open",
|
|
66
|
+
url: "https://example.com/{{booking_id}}?guest={{guest_name}}",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
expect(extractRequiredParameters(message)).toEqual([
|
|
72
|
+
"guest_name",
|
|
73
|
+
"brand_name",
|
|
74
|
+
"check_in",
|
|
75
|
+
"booking_id",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
it("returns no keys for a template without variables", () => {
|
|
79
|
+
const message = whatsappMessage({
|
|
80
|
+
body: { enabled: true, content: doc(text("Plain text")) },
|
|
81
|
+
});
|
|
82
|
+
expect(extractRequiredParameters(message)).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
it("ignores URL buttons when the button block is disabled", () => {
|
|
85
|
+
const message = whatsappMessage({
|
|
86
|
+
buttons: {
|
|
87
|
+
enabled: false,
|
|
88
|
+
buttons: [
|
|
89
|
+
{ id: "b1", type: "link", text: "Open", url: "https://x/{{id}}" },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
expect(extractRequiredParameters(message)).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe("validateParameters with brand_* keys", () => {
|
|
97
|
+
const message = whatsappMessage({
|
|
98
|
+
body: {
|
|
99
|
+
enabled: true,
|
|
100
|
+
content: doc(mention("guest_name"), text(" @ "), mention("brand_name")),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const brand = {
|
|
104
|
+
default_email_settings: {},
|
|
105
|
+
default_button_settings: {},
|
|
106
|
+
default_socials: null,
|
|
107
|
+
logo_url: null,
|
|
108
|
+
data: { name: "Hotel Sole" },
|
|
109
|
+
};
|
|
110
|
+
it("reports brand_name missing when validated on the raw parameters", () => {
|
|
111
|
+
expect(validateParameters(message, { guest_name: "Mario" })).toEqual({
|
|
112
|
+
valid: false,
|
|
113
|
+
required: ["guest_name", "brand_name"],
|
|
114
|
+
missing: ["brand_name"],
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
it("counts brand_name as present once the Brand data is merged in", () => {
|
|
118
|
+
const merged = mergeBrandDataParams({ guest_name: "Mario" }, brand);
|
|
119
|
+
expect(validateParameters(message, merged)).toEqual({
|
|
120
|
+
valid: true,
|
|
121
|
+
required: ["guest_name", "brand_name"],
|
|
122
|
+
missing: [],
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it("still reports a brand_* key the Brand cannot supply", () => {
|
|
126
|
+
const withSlogan = whatsappMessage({
|
|
127
|
+
body: { enabled: true, content: doc(mention("brand_slogan")) },
|
|
128
|
+
});
|
|
129
|
+
const merged = mergeBrandDataParams({}, brand);
|
|
130
|
+
expect(validateParameters(withSlogan, merged).missing).toEqual([
|
|
131
|
+
"brand_slogan",
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("compileWhatsappTemplate", () => {
|
|
136
|
+
it("builds the template envelope from the Meta metadata and language", () => {
|
|
137
|
+
const payload = compileWhatsappTemplate(whatsappMessage(), {});
|
|
138
|
+
expect(payload).toEqual({
|
|
139
|
+
messaging_product: "whatsapp",
|
|
140
|
+
type: "template",
|
|
141
|
+
template: {
|
|
142
|
+
name: "welcome_en",
|
|
143
|
+
language: { code: "en" },
|
|
144
|
+
components: undefined,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
it("throws when the Message has no Meta template name", () => {
|
|
149
|
+
const message = whatsappMessage();
|
|
150
|
+
message.metadata = {};
|
|
151
|
+
expect(() => compileWhatsappTemplate(message, {})).toThrow("Message does not have Meta template name in metadata");
|
|
152
|
+
});
|
|
153
|
+
it("emits a HEADER component with the header text variable", () => {
|
|
154
|
+
const message = whatsappMessage({
|
|
155
|
+
header: {
|
|
156
|
+
type: "text",
|
|
157
|
+
content: doc(text("Hi "), mention("guest_name")),
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
const payload = compileWhatsappTemplate(message, { guest_name: "Mario" });
|
|
161
|
+
expect(payload.template.components).toEqual([
|
|
162
|
+
{
|
|
163
|
+
type: "HEADER",
|
|
164
|
+
parameters: [
|
|
165
|
+
{ type: "text", text: "Mario", parameter_name: "guest_name" },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
it("emits BODY named parameters in order, coercing values to strings", () => {
|
|
171
|
+
const message = whatsappMessage({
|
|
172
|
+
body: {
|
|
173
|
+
enabled: true,
|
|
174
|
+
content: doc(mention("guest_name"), text(", "), mention("nights"), text(" nights, "), mention("missing_key")),
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const payload = compileWhatsappTemplate(message, {
|
|
178
|
+
guest_name: "Mario",
|
|
179
|
+
nights: 3,
|
|
180
|
+
});
|
|
181
|
+
expect(payload.template.components).toEqual([
|
|
182
|
+
{
|
|
183
|
+
type: "BODY",
|
|
184
|
+
parameters: [
|
|
185
|
+
{ type: "text", text: "Mario", parameter_name: "guest_name" },
|
|
186
|
+
{ type: "text", text: "3", parameter_name: "nights" },
|
|
187
|
+
{ type: "text", text: "", parameter_name: "missing_key" },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
]);
|
|
191
|
+
});
|
|
192
|
+
it("emits a media HEADER by smartchat media id", () => {
|
|
193
|
+
const message = whatsappMessage({
|
|
194
|
+
header: {
|
|
195
|
+
type: "image",
|
|
196
|
+
url: "https://cdn.example.com/hero.png",
|
|
197
|
+
smartchatMediaId: "media-42",
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const payload = compileWhatsappTemplate(message, {});
|
|
201
|
+
expect(payload.template.components).toEqual([
|
|
202
|
+
{
|
|
203
|
+
type: "HEADER",
|
|
204
|
+
parameters: [{ type: "image", image: { id: "media-42" } }],
|
|
205
|
+
},
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
208
|
+
it("omits the media HEADER when the media id is not resolved", () => {
|
|
209
|
+
const message = whatsappMessage({
|
|
210
|
+
header: { type: "image", url: "https://cdn.example.com/hero.png" },
|
|
211
|
+
});
|
|
212
|
+
const payload = compileWhatsappTemplate(message, {});
|
|
213
|
+
expect(payload.template.components).toBeUndefined();
|
|
214
|
+
});
|
|
215
|
+
it("emits a URL BUTTON component named by button position, as submitted to Meta", () => {
|
|
216
|
+
const message = whatsappMessage({
|
|
217
|
+
buttons: {
|
|
218
|
+
enabled: true,
|
|
219
|
+
buttons: [
|
|
220
|
+
{ id: "b0", type: "quick_reply", text: "Yes" },
|
|
221
|
+
{
|
|
222
|
+
id: "b1",
|
|
223
|
+
type: "link",
|
|
224
|
+
text: "Open",
|
|
225
|
+
url: "https://example.com/{{booking_id}}",
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
const payload = compileWhatsappTemplate(message, { booking_id: "abc" });
|
|
231
|
+
expect(payload.template.components).toEqual([
|
|
232
|
+
{
|
|
233
|
+
type: "BUTTON",
|
|
234
|
+
sub_type: "url",
|
|
235
|
+
index: "1",
|
|
236
|
+
parameters: [{ type: "text", text: "abc", parameter_name: "{{2}}" }],
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
//# sourceMappingURL=whatsapp-compiler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-compiler.test.js","sourceRoot":"","sources":["../../../src/utils/templates/whatsapp-compiler.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/schemas/brand.ts
CHANGED
|
@@ -7,10 +7,12 @@ import {
|
|
|
7
7
|
BrandProperty,
|
|
8
8
|
} from "../types/index.js";
|
|
9
9
|
import { dbTimestampSchema } from "./common.js";
|
|
10
|
+
import { organizationFilterQuerySchema } from "./organization.js";
|
|
10
11
|
|
|
11
12
|
export const listBrandsQuerySchema = z.object({
|
|
12
13
|
sort_by: z.enum(["created_at", "name"]).catch("created_at"),
|
|
13
14
|
direction: z.enum(["asc", "desc"]).catch("asc"),
|
|
15
|
+
...organizationFilterQuerySchema.shape,
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
export const byPropertyParamsSchema = z.object({
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DynamicValuesResponse,
|
|
4
|
+
MESSAGE_GROUP_PRODUCTS,
|
|
5
|
+
} from "../types/index.js";
|
|
6
|
+
import { organizationLookupQuerySchema } from "./organization.js";
|
|
3
7
|
|
|
4
8
|
export const dynamicValuesQuerySchema = z.object({
|
|
5
|
-
product: z.
|
|
9
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS, {
|
|
10
|
+
error: "product query parameter is required and must be a Product",
|
|
11
|
+
}),
|
|
6
12
|
context: z.string().optional(),
|
|
7
13
|
language: z.string().default("en"),
|
|
14
|
+
...organizationLookupQuerySchema.shape,
|
|
8
15
|
});
|
|
9
16
|
|
|
10
17
|
export const dynamicValuesResponseSchema = z.record(
|
package/src/schemas/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CompileAndSendRequest,
|
|
5
5
|
previewMessageRequestSchema,
|
|
6
6
|
} from "../types/index.js";
|
|
7
|
+
import { organizationBodySchema } from "./organization.js";
|
|
7
8
|
|
|
8
9
|
export const messageLanguageQuerySchema = z.object({
|
|
9
10
|
language: z.string(),
|
|
@@ -30,6 +31,7 @@ export const compileAndSendBodySchema = compileMessageBodySchema.extend({
|
|
|
30
31
|
}) satisfies z.ZodType<CompileAndSendRequest>;
|
|
31
32
|
|
|
32
33
|
export const testEmailBodySchema = z.object({
|
|
34
|
+
...organizationBodySchema.shape,
|
|
33
35
|
message: previewMessageRequestSchema.shape.message,
|
|
34
36
|
to: z.string().min(1, "Missing required parameters: message, to"),
|
|
35
37
|
brand_id: z.string().nullable().optional(),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { listMessageGroupsQuerySchema } from "./message-group.js";
|
|
3
|
+
|
|
4
|
+
describe("listMessageGroupsQuerySchema: the WABA filter", () => {
|
|
5
|
+
it("keeps the WABA a list is narrowed to", () => {
|
|
6
|
+
expect(
|
|
7
|
+
listMessageGroupsQuerySchema.parse({ waba_id: "waba-1" }).waba_id,
|
|
8
|
+
).toBe("waba-1");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("is optional: a list without it spans every WABA", () => {
|
|
12
|
+
expect(listMessageGroupsQuerySchema.parse({}).waba_id).toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("rejects an empty WABA", () => {
|
|
16
|
+
expect(
|
|
17
|
+
listMessageGroupsQuerySchema.safeParse({ waba_id: "" }).success,
|
|
18
|
+
).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
3
|
DuplicateMessageGroupBody,
|
|
4
|
+
ListMessageGroupsQuery,
|
|
4
5
|
MessageGroupFromDb,
|
|
5
6
|
MessageGroupWithMessageSummary,
|
|
6
7
|
MESSAGE_GROUP_CATEGORIES,
|
|
8
|
+
MESSAGE_GROUP_LIST_SORT,
|
|
9
|
+
MESSAGE_GROUP_LIST_VISIBILITY,
|
|
10
|
+
MESSAGE_GROUP_PRODUCTS,
|
|
7
11
|
MESSAGE_GROUP_VISIBILITY,
|
|
8
12
|
MESSAGE_STATUS,
|
|
9
13
|
MESSAGE_RENDER_TYPES,
|
|
14
|
+
SORT_ORDER,
|
|
10
15
|
} from "../types/index.js";
|
|
11
16
|
import { dbTimestampSchema } from "./common.js";
|
|
17
|
+
import {
|
|
18
|
+
paginatedResponseSchema,
|
|
19
|
+
paginationQuerySchema,
|
|
20
|
+
} from "./pagination.js";
|
|
12
21
|
import { messageResponseSchema } from "./message.js";
|
|
22
|
+
import {
|
|
23
|
+
organizationBodySchema,
|
|
24
|
+
organizationFilterQuerySchema,
|
|
25
|
+
} from "./organization.js";
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
/**
|
|
28
|
+
* GET /message-groups: server-side filter, sort and pagination (ADR 0012).
|
|
29
|
+
* `visibility` other than `public` is admin-only (403 otherwise, enforced by
|
|
30
|
+
* the route). An empty or whitespace `search` is treated as absent.
|
|
31
|
+
*/
|
|
32
|
+
export const listMessageGroupsQuerySchema = paginationQuerySchema.extend({
|
|
33
|
+
...organizationFilterQuerySchema.shape,
|
|
34
|
+
search: z
|
|
35
|
+
.string()
|
|
36
|
+
.trim()
|
|
37
|
+
.max(200)
|
|
38
|
+
.optional()
|
|
39
|
+
.transform((value) => (value === "" ? undefined : value)),
|
|
40
|
+
category: z.enum(MESSAGE_GROUP_CATEGORIES).optional(),
|
|
41
|
+
status: z.enum(MESSAGE_STATUS).optional(),
|
|
42
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS).optional(),
|
|
43
|
+
visibility: z.enum(MESSAGE_GROUP_LIST_VISIBILITY).default("public"),
|
|
44
|
+
render_type: z.enum(MESSAGE_RENDER_TYPES).optional(),
|
|
45
|
+
waba_id: z.string().min(1).optional(),
|
|
46
|
+
sort: z.enum(MESSAGE_GROUP_LIST_SORT).default("updated_at"),
|
|
47
|
+
order: z.enum(SORT_ORDER).default("desc"),
|
|
48
|
+
}) satisfies z.ZodType<ListMessageGroupsQuery, unknown>;
|
|
49
|
+
export type { ListMessageGroupsQuery };
|
|
19
50
|
|
|
20
51
|
export const updateMessageGroupBodySchema = z.object({
|
|
21
52
|
name: z
|
|
@@ -24,20 +55,25 @@ export const updateMessageGroupBodySchema = z.object({
|
|
|
24
55
|
(name) => name.trim() !== "",
|
|
25
56
|
"Name is required and cannot be empty",
|
|
26
57
|
),
|
|
58
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS).optional(),
|
|
27
59
|
brand_id: z.string().nullable().optional(),
|
|
28
60
|
});
|
|
29
61
|
|
|
30
62
|
export const createMessageGroupBodySchema = z.object({
|
|
63
|
+
...organizationBodySchema.shape,
|
|
31
64
|
name: z.string(),
|
|
32
65
|
category: z.enum(MESSAGE_GROUP_CATEGORIES),
|
|
66
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
33
67
|
visibility: z.enum(MESSAGE_GROUP_VISIBILITY).optional(),
|
|
34
68
|
brand_id: z.string().nullable().optional(),
|
|
35
69
|
});
|
|
36
70
|
|
|
37
71
|
export const duplicateMessageGroupBodySchema = z
|
|
38
72
|
.object({
|
|
73
|
+
...organizationBodySchema.shape,
|
|
39
74
|
name: z.string().optional(),
|
|
40
75
|
visibility: z.enum(MESSAGE_GROUP_VISIBILITY).optional(),
|
|
76
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS).optional(),
|
|
41
77
|
brand_id: z.string().nullable().optional(),
|
|
42
78
|
})
|
|
43
79
|
.optional() satisfies z.ZodType<DuplicateMessageGroupBody | undefined>;
|
|
@@ -47,9 +83,11 @@ export const messageGroupResponseSchema = z.object({
|
|
|
47
83
|
id: z.uuid(),
|
|
48
84
|
name: z.string(),
|
|
49
85
|
category: z.enum(MESSAGE_GROUP_CATEGORIES),
|
|
86
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
50
87
|
visibility: z.enum(MESSAGE_GROUP_VISIBILITY).nullable().optional(),
|
|
51
88
|
brand_id: z.string().nullable().optional(),
|
|
52
89
|
organization_id: z.string().nullable().optional(),
|
|
90
|
+
waba_id: z.string().nullable().optional(),
|
|
53
91
|
user_id: z.string(),
|
|
54
92
|
created_at: dbTimestampSchema,
|
|
55
93
|
updated_at: dbTimestampSchema,
|
|
@@ -80,7 +118,8 @@ export const messageGroupWithMessageSummaryResponseSchema =
|
|
|
80
118
|
),
|
|
81
119
|
}) satisfies z.ZodType<unknown, MessageGroupWithMessageSummary>;
|
|
82
120
|
|
|
83
|
-
|
|
121
|
+
/** Paginated envelope of list items (ADR 0012). */
|
|
122
|
+
export const messageGroupSummaryListResponseSchema = paginatedResponseSchema(
|
|
84
123
|
messageGroupWithMessageSummaryResponseSchema,
|
|
85
124
|
);
|
|
86
125
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { submitToWabaBodySchema } from "./meta-integration.js";
|
|
3
|
+
|
|
4
|
+
describe("submitToWabaBodySchema", () => {
|
|
5
|
+
it("accepts a submission that names its WABA", () => {
|
|
6
|
+
expect(submitToWabaBodySchema.parse({ waba_id: "waba-1" })).toEqual({
|
|
7
|
+
waba_id: "waba-1",
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("is optional: no body and an empty body both name no WABA", () => {
|
|
12
|
+
expect(submitToWabaBodySchema.parse(undefined)).toBeUndefined();
|
|
13
|
+
expect(submitToWabaBodySchema.parse(null)).toBeNull();
|
|
14
|
+
expect(submitToWabaBodySchema.parse({})).toEqual({});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it.each(["", 42, null])("rejects the WABA %j", (waba_id) => {
|
|
18
|
+
expect(submitToWabaBodySchema.safeParse({ waba_id }).success).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
OrganizationWaba,
|
|
4
|
+
SubmissionJob,
|
|
5
|
+
SubmitToWabaBody,
|
|
6
|
+
SubmissionJobResponse,
|
|
7
|
+
} from "../types/index.js";
|
|
3
8
|
import { dbTimestampSchema } from "./common.js";
|
|
4
9
|
|
|
5
10
|
export const messageIdParamsSchema = z.object({
|
|
@@ -45,3 +50,27 @@ export const submitGroupAcceptedResponseSchema = z.object({
|
|
|
45
50
|
status: z.literal("queued"),
|
|
46
51
|
message: z.string(),
|
|
47
52
|
}) satisfies z.ZodType<SubmissionJobResponse>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* GET /integrations/meta/wabas: the WABAs of the Organization named in the
|
|
56
|
+
* query (`organizationLookupQuerySchema`), empty when it has none (ADR 0015).
|
|
57
|
+
*/
|
|
58
|
+
export const organizationWabasResponseSchema = z.array(
|
|
59
|
+
z.object({
|
|
60
|
+
id: z.string(),
|
|
61
|
+
name: z.string(),
|
|
62
|
+
phone_numbers: z.array(z.string()),
|
|
63
|
+
}),
|
|
64
|
+
) satisfies z.ZodType<OrganizationWaba[]>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Body of the public submit routes (ADR 0015). `waba_id` names the WABA a
|
|
68
|
+
* Message Group is submitted to at its first submission; it is required only
|
|
69
|
+
* when the Organization has several and the Message Group has none yet.
|
|
70
|
+
*/
|
|
71
|
+
export const submitToWabaBodySchema = z
|
|
72
|
+
.object({
|
|
73
|
+
waba_id: z.string().min(1).optional(),
|
|
74
|
+
})
|
|
75
|
+
// A POST with no body reaches the route as null.
|
|
76
|
+
.nullish() satisfies z.ZodType<SubmitToWabaBody | null | undefined>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
|
+
MESSAGE_GROUP_PRODUCTS,
|
|
3
4
|
brandDefaultEmailSettingsSchema,
|
|
4
5
|
brandDefaultButtonSettingsSchema,
|
|
5
6
|
brandSocialsSchema,
|
|
@@ -21,6 +22,8 @@ export const onboardingBodySchema = z.object({
|
|
|
21
22
|
userId: z
|
|
22
23
|
.string({ error: "Both organizationId and userId are required" })
|
|
23
24
|
.min(1, "Both organizationId and userId are required"),
|
|
25
|
+
// Required, no fallback to the sm-product header (ADR 0011)
|
|
26
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
24
27
|
tags: z.array(z.string()).optional(),
|
|
25
28
|
});
|
|
26
29
|
|