@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
organizationBodySchema,
|
|
4
|
+
organizationFilterQuerySchema,
|
|
5
|
+
organizationLookupQuerySchema,
|
|
6
|
+
} from "./organization.js";
|
|
7
|
+
import { listBrandsQuerySchema } from "./brand.js";
|
|
8
|
+
import { dynamicValuesQuerySchema } from "./dynamic-values.js";
|
|
9
|
+
import { trackingBodySchema } from "./tracking.js";
|
|
10
|
+
import {
|
|
11
|
+
createMessageGroupBodySchema,
|
|
12
|
+
duplicateMessageGroupBodySchema,
|
|
13
|
+
listMessageGroupsQuerySchema,
|
|
14
|
+
} from "./message-group.js";
|
|
15
|
+
|
|
16
|
+
const ORGANIZATION = "3f0c1a52-8f0e-4c53-9d58-0f6f6f1f2a77";
|
|
17
|
+
const NOT_UUIDS = ["", "42", "hotel-uno", `${ORGANIZATION}x`, 42, null];
|
|
18
|
+
|
|
19
|
+
describe("organizationFilterQuerySchema", () => {
|
|
20
|
+
it("accepts a UUID", () => {
|
|
21
|
+
expect(
|
|
22
|
+
organizationFilterQuerySchema.parse({ organization_id: ORGANIZATION }),
|
|
23
|
+
).toEqual({ organization_id: ORGANIZATION });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("is optional: a list without it spans the Reachable Organizations", () => {
|
|
27
|
+
expect(organizationFilterQuerySchema.parse({})).toEqual({});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it.each(NOT_UUIDS)("rejects %j", (organization_id) => {
|
|
31
|
+
expect(
|
|
32
|
+
organizationFilterQuerySchema.safeParse({ organization_id }).success,
|
|
33
|
+
).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("organizationBodySchema", () => {
|
|
38
|
+
it("accepts a UUID", () => {
|
|
39
|
+
expect(
|
|
40
|
+
organizationBodySchema.parse({ organization_id: ORGANIZATION }),
|
|
41
|
+
).toEqual({ organization_id: ORGANIZATION });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it.each(NOT_UUIDS)("rejects %j", (organization_id) => {
|
|
45
|
+
expect(organizationBodySchema.safeParse({ organization_id }).success).toBe(
|
|
46
|
+
false,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("Message Group schemas carry the Organization", () => {
|
|
52
|
+
it("the list query keeps a UUID filter and rejects anything else", () => {
|
|
53
|
+
expect(
|
|
54
|
+
listMessageGroupsQuerySchema.parse({ organization_id: ORGANIZATION })
|
|
55
|
+
.organization_id,
|
|
56
|
+
).toBe(ORGANIZATION);
|
|
57
|
+
expect(
|
|
58
|
+
listMessageGroupsQuerySchema.safeParse({ organization_id: "hotel-uno" })
|
|
59
|
+
.success,
|
|
60
|
+
).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("the create body keeps a UUID Organization and rejects anything else", () => {
|
|
64
|
+
const body = { name: "Welcome", category: "utility", product: "chat" };
|
|
65
|
+
expect(
|
|
66
|
+
createMessageGroupBodySchema.parse({
|
|
67
|
+
...body,
|
|
68
|
+
organization_id: ORGANIZATION,
|
|
69
|
+
}).organization_id,
|
|
70
|
+
).toBe(ORGANIZATION);
|
|
71
|
+
expect(
|
|
72
|
+
createMessageGroupBodySchema.safeParse({
|
|
73
|
+
...body,
|
|
74
|
+
organization_id: "hotel-uno",
|
|
75
|
+
}).success,
|
|
76
|
+
).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("the duplicate body keeps a UUID Organization and rejects anything else", () => {
|
|
80
|
+
expect(
|
|
81
|
+
duplicateMessageGroupBodySchema.parse({ organization_id: ORGANIZATION }),
|
|
82
|
+
).toEqual({ organization_id: ORGANIZATION });
|
|
83
|
+
expect(
|
|
84
|
+
duplicateMessageGroupBodySchema.safeParse({
|
|
85
|
+
organization_id: "hotel-uno",
|
|
86
|
+
}).success,
|
|
87
|
+
).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("organizationLookupQuerySchema", () => {
|
|
92
|
+
it("accepts a UUID", () => {
|
|
93
|
+
expect(
|
|
94
|
+
organizationLookupQuerySchema.parse({ organization_id: ORGANIZATION }),
|
|
95
|
+
).toEqual({ organization_id: ORGANIZATION });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it.each(NOT_UUIDS)("rejects %j", (organization_id) => {
|
|
99
|
+
expect(
|
|
100
|
+
organizationLookupQuerySchema.safeParse({ organization_id }).success,
|
|
101
|
+
).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("Brand, dynamic values and tracking schemas carry the Organization", () => {
|
|
106
|
+
it("the Brand list query keeps a UUID filter and rejects anything else", () => {
|
|
107
|
+
expect(
|
|
108
|
+
listBrandsQuerySchema.parse({ organization_id: ORGANIZATION })
|
|
109
|
+
.organization_id,
|
|
110
|
+
).toBe(ORGANIZATION);
|
|
111
|
+
expect(
|
|
112
|
+
listBrandsQuerySchema.safeParse({ organization_id: "hotel-uno" }).success,
|
|
113
|
+
).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("the dynamic values query keeps a UUID Organization and rejects anything else", () => {
|
|
117
|
+
const query = { product: "connect" };
|
|
118
|
+
expect(
|
|
119
|
+
dynamicValuesQuerySchema.parse({
|
|
120
|
+
...query,
|
|
121
|
+
organization_id: ORGANIZATION,
|
|
122
|
+
}).organization_id,
|
|
123
|
+
).toBe(ORGANIZATION);
|
|
124
|
+
expect(
|
|
125
|
+
dynamicValuesQuerySchema.safeParse({
|
|
126
|
+
...query,
|
|
127
|
+
organization_id: "hotel-uno",
|
|
128
|
+
}).success,
|
|
129
|
+
).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("the tracking body keeps a UUID Organization and rejects anything else", () => {
|
|
133
|
+
const body = { eventName: "brand_created" };
|
|
134
|
+
expect(
|
|
135
|
+
trackingBodySchema.parse({ ...body, organization_id: ORGANIZATION })
|
|
136
|
+
.organization_id,
|
|
137
|
+
).toBe(ORGANIZATION);
|
|
138
|
+
expect(
|
|
139
|
+
trackingBodySchema.safeParse({ ...body, organization_id: "hotel-uno" })
|
|
140
|
+
.success,
|
|
141
|
+
).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -1,4 +1,43 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ReachableOrganization } from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
export const reachableOrganizationSchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
name: z.string(),
|
|
7
|
+
}) satisfies z.ZodType<ReachableOrganization>;
|
|
8
|
+
|
|
9
|
+
/** GET /organizations: the user's Reachable Organizations (ADR 0014). */
|
|
10
|
+
export const reachableOrganizationListResponseSchema = z.array(
|
|
11
|
+
reachableOrganizationSchema,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional Organization filter of a list route (ADR 0014). Without it the
|
|
16
|
+
* list spans the user's Reachable Organizations; a value that is not
|
|
17
|
+
* reachable yields no data.
|
|
18
|
+
*/
|
|
19
|
+
export const organizationFilterQuerySchema = z.object({
|
|
20
|
+
organization_id: z.uuid().optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Organization a resource is created in (ADR 0014). Optional only while the
|
|
25
|
+
* deprecated `sm-organization-id` header is accepted in its place: a request
|
|
26
|
+
* with neither is rejected by the route.
|
|
27
|
+
*/
|
|
28
|
+
export const organizationBodySchema = z.object({
|
|
29
|
+
organization_id: z.uuid().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Organization of a lookup route: one that reads Organization-scoped data
|
|
34
|
+
* without listing or creating a resource (ADR 0014). Optional only while the
|
|
35
|
+
* deprecated `sm-organization-id` header is accepted in its place: a request
|
|
36
|
+
* with neither is rejected by the route.
|
|
37
|
+
*/
|
|
38
|
+
export const organizationLookupQuerySchema = z.object({
|
|
39
|
+
organization_id: z.uuid().optional(),
|
|
40
|
+
});
|
|
2
41
|
|
|
3
42
|
/**
|
|
4
43
|
* GET /organization/data passes through OrganizationData from Smartconnect —
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
paginationQuerySchema,
|
|
5
|
+
paginatedResponseSchema,
|
|
6
|
+
} from "./pagination.js";
|
|
7
|
+
|
|
8
|
+
describe("paginationQuerySchema", () => {
|
|
9
|
+
it("defaults to page 1 and 25 per page", () => {
|
|
10
|
+
expect(paginationQuerySchema.parse({})).toEqual({ page: 1, per_page: 25 });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("coerces query-string numbers", () => {
|
|
14
|
+
expect(paginationQuerySchema.parse({ page: "3", per_page: "50" })).toEqual({
|
|
15
|
+
page: 3,
|
|
16
|
+
per_page: 50,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("accepts the bounds 1 and 100 for per_page", () => {
|
|
21
|
+
expect(paginationQuerySchema.parse({ per_page: "1" }).per_page).toBe(1);
|
|
22
|
+
expect(paginationQuerySchema.parse({ per_page: "100" }).per_page).toBe(100);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("rejects page 0, per_page 0 and per_page 101", () => {
|
|
26
|
+
expect(paginationQuerySchema.safeParse({ page: "0" }).success).toBe(false);
|
|
27
|
+
expect(paginationQuerySchema.safeParse({ per_page: "0" }).success).toBe(
|
|
28
|
+
false,
|
|
29
|
+
);
|
|
30
|
+
expect(paginationQuerySchema.safeParse({ per_page: "101" }).success).toBe(
|
|
31
|
+
false,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("rejects non-integer values", () => {
|
|
36
|
+
expect(paginationQuerySchema.safeParse({ page: "1.5" }).success).toBe(
|
|
37
|
+
false,
|
|
38
|
+
);
|
|
39
|
+
expect(paginationQuerySchema.safeParse({ page: "abc" }).success).toBe(
|
|
40
|
+
false,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("paginatedResponseSchema", () => {
|
|
46
|
+
const schema = paginatedResponseSchema(z.object({ id: z.string() }));
|
|
47
|
+
|
|
48
|
+
it("accepts the Laravel resource-collection envelope", () => {
|
|
49
|
+
const envelope = {
|
|
50
|
+
data: [{ id: "a" }],
|
|
51
|
+
links: { first: "/x?page=1", last: "/x?page=1", prev: null, next: null },
|
|
52
|
+
meta: {
|
|
53
|
+
current_page: 1,
|
|
54
|
+
from: 1,
|
|
55
|
+
last_page: 1,
|
|
56
|
+
path: "/x",
|
|
57
|
+
per_page: 25,
|
|
58
|
+
to: 1,
|
|
59
|
+
total: 1,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
expect(schema.parse(envelope)).toEqual(envelope);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("allows null from/to on an empty page and has no meta.links", () => {
|
|
66
|
+
const parsed = schema.parse({
|
|
67
|
+
data: [],
|
|
68
|
+
links: { first: "/x?page=1", last: "/x?page=1", prev: null, next: null },
|
|
69
|
+
meta: {
|
|
70
|
+
current_page: 1,
|
|
71
|
+
from: null,
|
|
72
|
+
last_page: 1,
|
|
73
|
+
path: "/x",
|
|
74
|
+
per_page: 25,
|
|
75
|
+
to: null,
|
|
76
|
+
total: 0,
|
|
77
|
+
links: [{ url: null, label: "x", active: false }],
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(parsed.meta.from).toBeNull();
|
|
81
|
+
expect(parsed.meta.to).toBeNull();
|
|
82
|
+
expect("links" in parsed.meta).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("validates items with the given schema", () => {
|
|
86
|
+
const result = schema.safeParse({
|
|
87
|
+
data: [{ id: 1 }],
|
|
88
|
+
links: { first: "/x?page=1", last: "/x?page=1", prev: null, next: null },
|
|
89
|
+
meta: {
|
|
90
|
+
current_page: 1,
|
|
91
|
+
from: 1,
|
|
92
|
+
last_page: 1,
|
|
93
|
+
path: "/x",
|
|
94
|
+
per_page: 25,
|
|
95
|
+
to: 1,
|
|
96
|
+
total: 1,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
expect(result.success).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type {
|
|
3
|
+
PaginationLinks,
|
|
4
|
+
PaginationMeta,
|
|
5
|
+
PaginationQuery,
|
|
6
|
+
} from "../types/index.js";
|
|
7
|
+
|
|
8
|
+
export const PAGINATION_DEFAULT_PER_PAGE = 25;
|
|
9
|
+
export const PAGINATION_MAX_PER_PAGE = 100;
|
|
10
|
+
|
|
11
|
+
/** Laravel-style offset pagination query params (ADR 0012). */
|
|
12
|
+
export const paginationQuerySchema = z.object({
|
|
13
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
14
|
+
per_page: z.coerce
|
|
15
|
+
.number()
|
|
16
|
+
.int()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(PAGINATION_MAX_PER_PAGE)
|
|
19
|
+
.default(PAGINATION_DEFAULT_PER_PAGE),
|
|
20
|
+
}) satisfies z.ZodType<PaginationQuery, unknown>;
|
|
21
|
+
|
|
22
|
+
export const paginationLinksSchema = z.object({
|
|
23
|
+
first: z.string(),
|
|
24
|
+
last: z.string(),
|
|
25
|
+
prev: z.string().nullable(),
|
|
26
|
+
next: z.string().nullable(),
|
|
27
|
+
}) satisfies z.ZodType<unknown, PaginationLinks>;
|
|
28
|
+
|
|
29
|
+
export const paginationMetaSchema = z.object({
|
|
30
|
+
current_page: z.number().int(),
|
|
31
|
+
from: z.number().int().nullable(),
|
|
32
|
+
last_page: z.number().int(),
|
|
33
|
+
path: z.string(),
|
|
34
|
+
per_page: z.number().int(),
|
|
35
|
+
to: z.number().int().nullable(),
|
|
36
|
+
total: z.number().int(),
|
|
37
|
+
}) satisfies z.ZodType<unknown, PaginationMeta>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Laravel API resource-collection envelope around `item`:
|
|
41
|
+
* `{ data, links: { first, last, prev, next }, meta: { … } }`, no `meta.links`.
|
|
42
|
+
*/
|
|
43
|
+
export function paginatedResponseSchema<T extends z.ZodType>(item: T) {
|
|
44
|
+
return z.object({
|
|
45
|
+
data: z.array(item),
|
|
46
|
+
links: paginationLinksSchema,
|
|
47
|
+
meta: paginationMetaSchema,
|
|
48
|
+
});
|
|
49
|
+
}
|
package/src/schemas/tracking.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { organizationBodySchema } from "./organization.js";
|
|
2
3
|
|
|
3
4
|
export const trackingBodySchema = z.object({
|
|
4
5
|
eventName: z.string().min(1, "eventName is required"),
|
|
5
6
|
properties: z.record(z.string(), z.unknown()).optional(),
|
|
7
|
+
...organizationBodySchema.shape,
|
|
6
8
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
MessageFromDb,
|
|
3
3
|
MessageGroupFromDb,
|
|
4
|
+
MessageGroupProduct,
|
|
4
5
|
MessageGroupVisibility,
|
|
5
6
|
MessageStatus,
|
|
6
7
|
RenderType,
|
|
@@ -12,8 +13,12 @@ export type MessageGroupWithMessages = MessageGroupFromDb & {
|
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export interface DuplicateMessageGroupBody {
|
|
16
|
+
/** Organization the copy is created in (ADR 0014). */
|
|
17
|
+
organization_id?: string;
|
|
15
18
|
name?: string;
|
|
16
19
|
visibility?: MessageGroupVisibility;
|
|
20
|
+
/** Defaults to the source group's Product. */
|
|
21
|
+
product?: MessageGroupProduct;
|
|
17
22
|
brand_id?: string | null;
|
|
18
23
|
}
|
|
19
24
|
|
package/src/types/app.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { MessageGroupProduct } from "./message.js";
|
|
2
|
+
|
|
1
3
|
export const errorCodes = {
|
|
2
4
|
INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
|
|
3
5
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
@@ -32,37 +34,21 @@ export interface AppError {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface AppContextConfig {
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
product?: "connect" | "chat" | "pms" | "pricing" | "browser";
|
|
38
|
-
strategy?: "embed" | "native";
|
|
37
|
+
/** The Product the host app embeds the composer in (ADR 0011). Required. */
|
|
38
|
+
product: MessageGroupProduct;
|
|
39
39
|
channels?: Array<"email" | "whatsapp">;
|
|
40
40
|
languages?: string[];
|
|
41
41
|
mainLanguage?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The WABA the Host works on. Only a preselection when the user picks the
|
|
44
|
+
* WABA of a Message Group, valid if it is one of the Organization's
|
|
45
|
+
* (ADR 0015).
|
|
46
|
+
*/
|
|
42
47
|
metaWabaId?: string;
|
|
43
|
-
metaAccessToken?: string;
|
|
44
48
|
organization_id?: string;
|
|
45
49
|
default_tags?: string[];
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
export interface AppContextState extends AppContextConfig {
|
|
49
|
-
initializationSource:
|
|
50
|
-
| "query"
|
|
51
|
-
| "postMessage"
|
|
52
|
-
| "cookies"
|
|
53
|
-
| "injection"
|
|
54
|
-
| null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface ContextSource {
|
|
58
|
-
name: string;
|
|
59
|
-
priority: number;
|
|
60
|
-
extract: () => Promise<Partial<AppContextConfig>>;
|
|
61
|
-
watch?: (
|
|
62
|
-
callback: (context: Partial<AppContextConfig>) => void,
|
|
63
|
-
) => () => void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
52
|
export type ParentNotificationType =
|
|
67
53
|
| "TEMPLATE_SAVED_DRAFT"
|
|
68
54
|
| "TEMPLATE_SAVED"
|
package/src/types/index.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
MessageStatus,
|
|
3
|
+
MessageGroupCategory,
|
|
4
|
+
MessageGroupProduct,
|
|
5
|
+
} from "./message.js";
|
|
2
6
|
|
|
3
7
|
export interface KafkaEventBase {
|
|
4
8
|
event_id: string;
|
|
5
9
|
event_type: string;
|
|
6
10
|
timestamp: string;
|
|
7
11
|
message_group_id: string;
|
|
12
|
+
/** Product of the Message Group the event is about (ADR 0011). */
|
|
13
|
+
product: MessageGroupProduct;
|
|
8
14
|
organization_id?: string | null;
|
|
9
15
|
user_id: string;
|
|
10
16
|
}
|
package/src/types/message.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ButtonBlockV1,
|
|
9
9
|
} from "./whatsappblocks.v1.js";
|
|
10
10
|
import type { EmailBlock, EmailGlobalSettings } from "./emailblocks.v1.js";
|
|
11
|
+
import type { PaginationQuery, SortOrder } from "./pagination.js";
|
|
11
12
|
import {
|
|
12
13
|
brandDefaultEmailSettingsSchema,
|
|
13
14
|
brandDefaultButtonSettingsSchema,
|
|
@@ -79,13 +80,67 @@ export type MessageGroupCategory = (typeof MESSAGE_GROUP_CATEGORIES)[number];
|
|
|
79
80
|
export const MESSAGE_GROUP_VISIBILITY = ["public", "internal"] as const;
|
|
80
81
|
export type MessageGroupVisibility = (typeof MESSAGE_GROUP_VISIBILITY)[number];
|
|
81
82
|
|
|
83
|
+
/** `visibility` filter of the list route; `internal` and `all` are admin-only. */
|
|
84
|
+
export const MESSAGE_GROUP_LIST_VISIBILITY = [
|
|
85
|
+
...MESSAGE_GROUP_VISIBILITY,
|
|
86
|
+
"all",
|
|
87
|
+
] as const;
|
|
88
|
+
export type MessageGroupListVisibility =
|
|
89
|
+
(typeof MESSAGE_GROUP_LIST_VISIBILITY)[number];
|
|
90
|
+
|
|
91
|
+
/** Sortable columns of the list route (ADR 0012); aggregates are not sortable. */
|
|
92
|
+
export const MESSAGE_GROUP_LIST_SORT = [
|
|
93
|
+
"name",
|
|
94
|
+
"category",
|
|
95
|
+
"created_at",
|
|
96
|
+
"updated_at",
|
|
97
|
+
] as const;
|
|
98
|
+
export type MessageGroupListSort = (typeof MESSAGE_GROUP_LIST_SORT)[number];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parsed query of `GET /message-groups` (ADR 0012): what the route handler
|
|
102
|
+
* receives after defaults. The layer sends any subset of these as query params.
|
|
103
|
+
*/
|
|
104
|
+
export interface ListMessageGroupsQuery extends PaginationQuery {
|
|
105
|
+
/** Narrows the list to one Reachable Organization (ADR 0014). */
|
|
106
|
+
organization_id?: string;
|
|
107
|
+
search?: string;
|
|
108
|
+
category?: MessageGroupCategory;
|
|
109
|
+
status?: MessageStatus;
|
|
110
|
+
product?: MessageGroupProduct;
|
|
111
|
+
visibility: MessageGroupListVisibility;
|
|
112
|
+
render_type?: RenderType;
|
|
113
|
+
/** Narrows the list to the Message Groups on one WABA (ADR 0015). */
|
|
114
|
+
waba_id?: string;
|
|
115
|
+
sort: MessageGroupListSort;
|
|
116
|
+
order: SortOrder;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The Smartness application a Message Group is authored for (ADR 0011).
|
|
121
|
+
* Anything outside this list is not a Product and is rejected on write.
|
|
122
|
+
*/
|
|
123
|
+
export const MESSAGE_GROUP_PRODUCTS = [
|
|
124
|
+
"connect",
|
|
125
|
+
"chat",
|
|
126
|
+
"pms",
|
|
127
|
+
"pricing",
|
|
128
|
+
] as const;
|
|
129
|
+
export type MessageGroupProduct = (typeof MESSAGE_GROUP_PRODUCTS)[number];
|
|
130
|
+
|
|
82
131
|
export interface MessageGroup extends DbEntity {
|
|
83
132
|
id: string;
|
|
84
133
|
name: string;
|
|
85
134
|
category: MessageGroupCategory;
|
|
135
|
+
product: MessageGroupProduct;
|
|
86
136
|
visibility?: MessageGroupVisibility | null;
|
|
87
137
|
brand_id?: string | null;
|
|
88
138
|
organization_id?: string | null;
|
|
139
|
+
/**
|
|
140
|
+
* The WABA the Message Group is submitted to: set at its first submission
|
|
141
|
+
* and fixed from then on, absent until then (ADR 0015).
|
|
142
|
+
*/
|
|
143
|
+
waba_id?: string | null;
|
|
89
144
|
user_id: string;
|
|
90
145
|
}
|
|
91
146
|
|
|
@@ -154,6 +209,8 @@ export const brandCascadeDataSchema = z.object({
|
|
|
154
209
|
* Request body for previewing a message with inline data (no DB lookups).
|
|
155
210
|
*/
|
|
156
211
|
export const previewMessageRequestSchema = z.object({
|
|
212
|
+
/** Organization the preview is compiled for (ADR 0014). */
|
|
213
|
+
organization_id: z.uuid().optional(),
|
|
157
214
|
message: z
|
|
158
215
|
.object({
|
|
159
216
|
render_type: z.literal("email_v1"),
|
|
@@ -203,6 +260,9 @@ export interface CompileAndSendRequest extends CompileMessageRequest {
|
|
|
203
260
|
* Request body for testing email rendering
|
|
204
261
|
*/
|
|
205
262
|
export interface TestEmailRequest {
|
|
263
|
+
/** Organization the test email is sent for (ADR 0014) */
|
|
264
|
+
organization_id?: string;
|
|
265
|
+
|
|
206
266
|
/** Email message configuration */
|
|
207
267
|
message: PostEntity<EmailMessage>;
|
|
208
268
|
|
|
@@ -311,6 +371,40 @@ interface WhatsAppTemplateMessage extends WhatsAppMessageBase {
|
|
|
311
371
|
*/
|
|
312
372
|
export type WhatsAppSendPayload = WhatsAppTemplateMessage;
|
|
313
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Sendable Message (WhatsApp): the content object a host hands to its own
|
|
376
|
+
* send pipeline. `meta.data` is the compiled Meta template object (name,
|
|
377
|
+
* language, components) and `meta.template` the translated Meta template the
|
|
378
|
+
* bubble is rendered from.
|
|
379
|
+
*/
|
|
380
|
+
export interface WhatsappSendableMessage {
|
|
381
|
+
type: "WhatsappTemplateMessage";
|
|
382
|
+
text: string;
|
|
383
|
+
meta: {
|
|
384
|
+
data: WhatsAppTemplateMessage["template"];
|
|
385
|
+
template: MetaTemplate;
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Email Sendable Message: the content object the email compile-and-send path
|
|
391
|
+
* publishes (Smartchat `HtmlMessage`), returned by `/compile` as `message`.
|
|
392
|
+
*/
|
|
393
|
+
export interface EmailSendableMessage {
|
|
394
|
+
type: "HtmlMessage";
|
|
395
|
+
skipTranslation: true;
|
|
396
|
+
/** The compiled HTML, as the message API expects it in both fields. */
|
|
397
|
+
text: string;
|
|
398
|
+
rawHtml: string;
|
|
399
|
+
subject: string;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Sendable Message: what `/compile` returns as `message` and what
|
|
404
|
+
* compile-and-send publishes, one member per channel.
|
|
405
|
+
*/
|
|
406
|
+
export type SendableMessage = WhatsappSendableMessage | EmailSendableMessage;
|
|
407
|
+
|
|
314
408
|
/**
|
|
315
409
|
* Response for compile endpoint (WhatsApp)
|
|
316
410
|
*/
|
|
@@ -319,6 +413,8 @@ export interface CompileWhatsAppResponse {
|
|
|
319
413
|
language: string;
|
|
320
414
|
template_name: string;
|
|
321
415
|
payload: Omit<WhatsAppTemplateMessage, "to">;
|
|
416
|
+
/** The Sendable Message, ready for the host's send pipeline. */
|
|
417
|
+
message: SendableMessage;
|
|
322
418
|
parameters_used: string[];
|
|
323
419
|
missingParameters?: string[];
|
|
324
420
|
}
|
|
@@ -332,6 +428,8 @@ export interface CompileEmailResponse {
|
|
|
332
428
|
subject: string;
|
|
333
429
|
html: string;
|
|
334
430
|
plaintext?: string;
|
|
431
|
+
/** The Sendable Message, ready for the host's send pipeline. */
|
|
432
|
+
message: EmailSendableMessage;
|
|
335
433
|
parameters_used: string[];
|
|
336
434
|
missingParameters?: string[];
|
|
337
435
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Organization the logged-in user has a role on, directly or through one of
|
|
3
|
+
* its properties (Reachable Organization, ADR 0014).
|
|
4
|
+
*/
|
|
5
|
+
export interface ReachableOrganization {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A WABA of an Organization. An Organization has none, one or several
|
|
12
|
+
* (ADR 0015). `phone_numbers` are the numbers connected to it, possibly none:
|
|
13
|
+
* a WABA is a valid submission target before its first number.
|
|
14
|
+
*/
|
|
15
|
+
export interface OrganizationWaba {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
phone_numbers: string[];
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laravel API resource-collection envelope for paginated lists (ADR 0012).
|
|
3
|
+
* Links are relative (path + query, no host); `meta.links` is omitted.
|
|
4
|
+
*/
|
|
5
|
+
export interface PaginationLinks {
|
|
6
|
+
first: string;
|
|
7
|
+
last: string;
|
|
8
|
+
prev: string | null;
|
|
9
|
+
next: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PaginationMeta {
|
|
13
|
+
current_page: number;
|
|
14
|
+
/** 1-based index of the first item on this page; null on an empty page. */
|
|
15
|
+
from: number | null;
|
|
16
|
+
last_page: number;
|
|
17
|
+
path: string;
|
|
18
|
+
per_page: number;
|
|
19
|
+
/** 1-based index of the last item on this page; null on an empty page. */
|
|
20
|
+
to: number | null;
|
|
21
|
+
total: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PaginatedResponse<T> {
|
|
25
|
+
data: T[];
|
|
26
|
+
links: PaginationLinks;
|
|
27
|
+
meta: PaginationMeta;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PaginationQuery {
|
|
31
|
+
page: number;
|
|
32
|
+
per_page: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const SORT_ORDER = ["asc", "desc"] as const;
|
|
36
|
+
export type SortOrder = (typeof SORT_ORDER)[number];
|