@tbs-fitness/contract 0.3.0 → 0.5.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/README.md +21 -0
- package/dist/cjs/app-settings/app-settings.dto.d.ts +124 -0
- package/dist/cjs/app-settings/app-settings.dto.js +94 -0
- package/dist/cjs/app-settings/index.d.ts +1 -0
- package/dist/cjs/app-settings/index.js +17 -0
- package/dist/cjs/auth/auth.dto.d.ts +4 -4
- package/dist/cjs/auth/index.d.ts +1 -0
- package/dist/cjs/auth/index.js +1 -0
- package/dist/cjs/auth/otp.dto.d.ts +45 -0
- package/dist/cjs/auth/otp.dto.js +39 -0
- package/dist/cjs/client/client-auth.dto.d.ts +81 -0
- package/dist/cjs/client/client-auth.dto.js +49 -0
- package/dist/cjs/client/index.d.ts +2 -0
- package/dist/cjs/client/index.js +18 -0
- package/dist/cjs/coach/coach-auth.dto.d.ts +0 -40
- package/dist/cjs/coach/coach-auth.dto.js +1 -30
- package/dist/cjs/coach/index.d.ts +1 -0
- package/dist/cjs/coach/index.js +1 -0
- package/dist/cjs/communication/index.d.ts +2 -0
- package/dist/cjs/communication/index.js +18 -0
- package/dist/cjs/communication/message-catalogue.d.ts +26 -0
- package/dist/cjs/communication/message-catalogue.js +53 -0
- package/dist/cjs/communication/template.dto.d.ts +240 -0
- package/dist/cjs/communication/template.dto.js +206 -0
- package/dist/cjs/enums/index.d.ts +21 -2
- package/dist/cjs/enums/index.js +28 -3
- package/dist/cjs/feature-flags/feature-flag.dto.d.ts +4 -4
- package/dist/cjs/feature-flags/feature-flag.dto.js +1 -1
- package/dist/esm/app-settings/app-settings.dto.d.ts +124 -0
- package/dist/esm/app-settings/app-settings.dto.js +90 -0
- package/dist/esm/app-settings/index.d.ts +1 -0
- package/dist/esm/app-settings/index.js +1 -0
- package/dist/esm/auth/auth.dto.d.ts +4 -4
- package/dist/esm/auth/index.d.ts +1 -0
- package/dist/esm/auth/index.js +1 -0
- package/dist/esm/auth/otp.dto.d.ts +45 -0
- package/dist/esm/auth/otp.dto.js +36 -0
- package/dist/esm/client/client-auth.dto.d.ts +81 -0
- package/dist/esm/client/client-auth.dto.js +46 -0
- package/dist/esm/client/index.d.ts +2 -0
- package/dist/esm/client/index.js +2 -0
- package/dist/esm/coach/coach-auth.dto.d.ts +0 -40
- package/dist/esm/coach/coach-auth.dto.js +1 -30
- package/dist/esm/coach/index.d.ts +1 -0
- package/dist/esm/coach/index.js +1 -0
- package/dist/esm/communication/index.d.ts +2 -0
- package/dist/esm/communication/index.js +2 -0
- package/dist/esm/communication/message-catalogue.d.ts +26 -0
- package/dist/esm/communication/message-catalogue.js +50 -0
- package/dist/esm/communication/template.dto.d.ts +240 -0
- package/dist/esm/communication/template.dto.js +200 -0
- package/dist/esm/enums/index.d.ts +21 -2
- package/dist/esm/enums/index.js +27 -2
- package/dist/esm/feature-flags/feature-flag.dto.d.ts +4 -4
- package/dist/esm/feature-flags/feature-flag.dto.js +1 -1
- package/package.json +31 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.coachLoginResponseSchema = exports.coachProfileSchema = exports.
|
|
3
|
+
exports.coachLoginResponseSchema = exports.coachProfileSchema = exports.coachLoginSchema = exports.coachSignupSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const auth_dto_js_1 = require("../auth/auth.dto.js");
|
|
6
6
|
const contact_schema_js_1 = require("../common/contact.schema.js");
|
|
@@ -15,12 +15,6 @@ exports.coachSignupSchema = zod_1.z.strictObject({
|
|
|
15
15
|
/** The browser's time zone. Missing or unusable becomes UTC on the server. */
|
|
16
16
|
timezone: date_time_schema_js_1.detectedTimezoneSchema,
|
|
17
17
|
});
|
|
18
|
-
/** Used by both resend endpoints and by forgot-password. */
|
|
19
|
-
exports.otpEmailSchema = zod_1.z.strictObject({ email: contact_schema_js_1.emailSchema });
|
|
20
|
-
exports.verifyOtpSchema = zod_1.z.strictObject({
|
|
21
|
-
email: contact_schema_js_1.emailSchema,
|
|
22
|
-
code: auth_dto_js_1.otpCodeSchema,
|
|
23
|
-
});
|
|
24
18
|
/**
|
|
25
19
|
* Coaches sign in from the browser, so the refresh token always travels in an
|
|
26
20
|
* HttpOnly cookie. `rememberMe: false` makes that cookie end with the browser
|
|
@@ -36,29 +30,6 @@ exports.coachLoginSchema = auth_dto_js_1.loginSchema
|
|
|
36
30
|
*/
|
|
37
31
|
timezone: date_time_schema_js_1.detectedTimezoneSchema,
|
|
38
32
|
});
|
|
39
|
-
exports.resetPasswordSchema = zod_1.z.strictObject({
|
|
40
|
-
resetToken: auth_dto_js_1.opaqueTokenSchema,
|
|
41
|
-
newPassword: auth_dto_js_1.newPasswordSchema,
|
|
42
|
-
});
|
|
43
|
-
/**
|
|
44
|
-
* Says what the client needs to drive the code screen, and nothing about
|
|
45
|
-
* whether the email belongs to an account: the same values are returned either
|
|
46
|
-
* way.
|
|
47
|
-
*/
|
|
48
|
-
exports.otpDispatchResponseSchema = zod_1.z.object({
|
|
49
|
-
message: zod_1.z.string(),
|
|
50
|
-
otpLength: zod_1.z.number().int(),
|
|
51
|
-
expiresInSeconds: zod_1.z.number().int(),
|
|
52
|
-
resendAvailableInSeconds: zod_1.z.number().int(),
|
|
53
|
-
});
|
|
54
|
-
exports.signupVerifiedResponseSchema = zod_1.z.object({
|
|
55
|
-
message: zod_1.z.string(),
|
|
56
|
-
});
|
|
57
|
-
/** Issued only after a recovery code is verified. It can reset a password once. */
|
|
58
|
-
exports.resetTokenResponseSchema = zod_1.z.object({
|
|
59
|
-
resetToken: zod_1.z.string(),
|
|
60
|
-
expiresInSeconds: zod_1.z.number().int(),
|
|
61
|
-
});
|
|
62
33
|
exports.coachProfileSchema = zod_1.z
|
|
63
34
|
.object({
|
|
64
35
|
id: zod_1.z.uuid(),
|
package/dist/cjs/coach/index.js
CHANGED
|
@@ -14,4 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("../auth/otp.dto.js"), exports);
|
|
17
18
|
__exportStar(require("./coach-auth.dto.js"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./message-catalogue.js"), exports);
|
|
18
|
+
__exportStar(require("./template.dto.js"), exports);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommunicationChannel, MessageType } from '../enums/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* What the platform knows about each message type: the channels that may carry
|
|
4
|
+
* it, the channels switched on by default, and the variables its sender
|
|
5
|
+
* supplies. A template may only use supplied variables, and must use the
|
|
6
|
+
* required ones, so an admin can never activate a template the system cannot
|
|
7
|
+
* fill.
|
|
8
|
+
*/
|
|
9
|
+
export interface MessageTypeDefinition {
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
channels: readonly CommunicationChannel[];
|
|
13
|
+
defaultChannels: Readonly<Record<CommunicationChannel, boolean>>;
|
|
14
|
+
/** Every variable the sender passes, with a sample value for previews. */
|
|
15
|
+
variables: Readonly<Record<string, {
|
|
16
|
+
description: string;
|
|
17
|
+
example: string;
|
|
18
|
+
}>>;
|
|
19
|
+
/** Variables every template for this type must use. */
|
|
20
|
+
requiredVariables: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
export declare const MESSAGE_CATALOGUE: Readonly<Record<MessageType, MessageTypeDefinition>>;
|
|
23
|
+
/** The longest SMS body a template may have: four concatenated GSM-7 segments. */
|
|
24
|
+
export declare const MAX_SMS_LENGTH = 612;
|
|
25
|
+
/** One GSM-7 segment. Longer messages are split and cost more. */
|
|
26
|
+
export declare const SMS_SEGMENT_LENGTH = 160;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SMS_SEGMENT_LENGTH = exports.MAX_SMS_LENGTH = exports.MESSAGE_CATALOGUE = void 0;
|
|
4
|
+
const OTP_VARIABLES = {
|
|
5
|
+
code: { description: 'The one-time code', example: '482915' },
|
|
6
|
+
fullName: { description: "The recipient's name", example: 'Biki Singh' },
|
|
7
|
+
expiresInMinutes: {
|
|
8
|
+
description: 'Minutes until the code expires',
|
|
9
|
+
example: '10',
|
|
10
|
+
},
|
|
11
|
+
appName: { description: 'The product name', example: 'TBS' },
|
|
12
|
+
};
|
|
13
|
+
exports.MESSAGE_CATALOGUE = {
|
|
14
|
+
'auth.otp.signup_verification': {
|
|
15
|
+
label: 'Sign-up verification code',
|
|
16
|
+
description: 'Sent when a coach signs up, to verify their email address.',
|
|
17
|
+
channels: ['EMAIL', 'SMS'],
|
|
18
|
+
defaultChannels: { EMAIL: true, SMS: false },
|
|
19
|
+
variables: OTP_VARIABLES,
|
|
20
|
+
requiredVariables: ['code'],
|
|
21
|
+
},
|
|
22
|
+
'auth.otp.password_reset': {
|
|
23
|
+
label: 'Password recovery code',
|
|
24
|
+
description: 'Sent when a coach asks to reset a forgotten password.',
|
|
25
|
+
channels: ['EMAIL', 'SMS'],
|
|
26
|
+
defaultChannels: { EMAIL: true, SMS: false },
|
|
27
|
+
variables: OTP_VARIABLES,
|
|
28
|
+
requiredVariables: ['code'],
|
|
29
|
+
},
|
|
30
|
+
'auth.password_reset_link': {
|
|
31
|
+
label: 'Password reset link',
|
|
32
|
+
description: 'Sent when an administrator account requests a reset link.',
|
|
33
|
+
channels: ['EMAIL'],
|
|
34
|
+
defaultChannels: { EMAIL: true, SMS: false },
|
|
35
|
+
variables: {
|
|
36
|
+
resetUrl: {
|
|
37
|
+
description: 'The single-use reset link',
|
|
38
|
+
example: 'https://app.example.com/reset-password?token=…',
|
|
39
|
+
},
|
|
40
|
+
fullName: { description: "The recipient's name", example: 'Biki Singh' },
|
|
41
|
+
expiresInMinutes: {
|
|
42
|
+
description: 'Minutes until the link expires',
|
|
43
|
+
example: '30',
|
|
44
|
+
},
|
|
45
|
+
appName: { description: 'The product name', example: 'TBS' },
|
|
46
|
+
},
|
|
47
|
+
requiredVariables: ['resetUrl'],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
/** The longest SMS body a template may have: four concatenated GSM-7 segments. */
|
|
51
|
+
exports.MAX_SMS_LENGTH = 612;
|
|
52
|
+
/** One GSM-7 segment. Longer messages are split and cost more. */
|
|
53
|
+
exports.SMS_SEGMENT_LENGTH = 160;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type CommunicationChannel, type MessageType } from '../enums/index.js';
|
|
3
|
+
/** The variable names used as `{{name}}` in a piece of template text, in order, without repeats. */
|
|
4
|
+
export declare function templatePlaceholders(text: string): string[];
|
|
5
|
+
/** True when the text has a `{{` or `}}` that is not part of a valid placeholder. */
|
|
6
|
+
export declare function hasMalformedPlaceholder(text: string): boolean;
|
|
7
|
+
/** A variable a template declares. Its value is supplied when the message is sent. */
|
|
8
|
+
export declare const templateVariableSchema: z.ZodObject<{
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
description: z.ZodOptional<z.ZodString>;
|
|
11
|
+
example: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strict>;
|
|
13
|
+
export type TemplateVariable = z.output<typeof templateVariableSchema>;
|
|
14
|
+
interface TemplateShape {
|
|
15
|
+
messageType: MessageType;
|
|
16
|
+
channel: CommunicationChannel;
|
|
17
|
+
subject?: string | null | undefined;
|
|
18
|
+
bodyText: string;
|
|
19
|
+
bodyHtml?: string | null | undefined;
|
|
20
|
+
variables: {
|
|
21
|
+
name: string;
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The rules every template must meet before it is saved: the declared
|
|
26
|
+
* variables match the placeholders exactly, every one is supplied by the
|
|
27
|
+
* message type, and the type's required variables are used. Shared by the
|
|
28
|
+
* server and the admin editor, so both report the same problems.
|
|
29
|
+
*/
|
|
30
|
+
export declare function templateProblems(template: TemplateShape): {
|
|
31
|
+
path: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}[];
|
|
34
|
+
/** A new draft. Saved templates are never edited; a change is a new version. */
|
|
35
|
+
export declare const createTemplateSchema: z.ZodObject<{
|
|
36
|
+
messageType: z.ZodEnum<{
|
|
37
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
38
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
39
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
40
|
+
}>;
|
|
41
|
+
channel: z.ZodEnum<{
|
|
42
|
+
EMAIL: "EMAIL";
|
|
43
|
+
SMS: "SMS";
|
|
44
|
+
}>;
|
|
45
|
+
name: z.ZodPipe<z.ZodString, z.ZodString>;
|
|
46
|
+
subject: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
bodyText: z.ZodString;
|
|
48
|
+
bodyHtml: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
49
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
50
|
+
name: z.ZodString;
|
|
51
|
+
description: z.ZodOptional<z.ZodString>;
|
|
52
|
+
example: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, z.core.$strict>>;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
export type CreateTemplateInput = z.input<typeof createTemplateSchema>;
|
|
56
|
+
export type CreateTemplateBody = z.output<typeof createTemplateSchema>;
|
|
57
|
+
export declare const templateListQuerySchema: z.ZodObject<{
|
|
58
|
+
messageType: z.ZodOptional<z.ZodEnum<{
|
|
59
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
60
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
61
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
62
|
+
}>>;
|
|
63
|
+
channel: z.ZodOptional<z.ZodEnum<{
|
|
64
|
+
EMAIL: "EMAIL";
|
|
65
|
+
SMS: "SMS";
|
|
66
|
+
}>>;
|
|
67
|
+
}, z.core.$strict>;
|
|
68
|
+
export type TemplateListQuery = z.output<typeof templateListQuerySchema>;
|
|
69
|
+
export declare const templateResponseSchema: z.ZodObject<{
|
|
70
|
+
id: z.ZodUUID;
|
|
71
|
+
messageType: z.ZodEnum<{
|
|
72
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
73
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
74
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
75
|
+
}>;
|
|
76
|
+
channel: z.ZodEnum<{
|
|
77
|
+
EMAIL: "EMAIL";
|
|
78
|
+
SMS: "SMS";
|
|
79
|
+
}>;
|
|
80
|
+
version: z.ZodNumber;
|
|
81
|
+
name: z.ZodString;
|
|
82
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
83
|
+
bodyText: z.ZodString;
|
|
84
|
+
bodyHtml: z.ZodNullable<z.ZodString>;
|
|
85
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
86
|
+
name: z.ZodString;
|
|
87
|
+
description: z.ZodOptional<z.ZodString>;
|
|
88
|
+
example: z.ZodOptional<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>>;
|
|
90
|
+
status: z.ZodEnum<{
|
|
91
|
+
active: "active";
|
|
92
|
+
draft: "draft";
|
|
93
|
+
archived: "archived";
|
|
94
|
+
}>;
|
|
95
|
+
createdBy: z.ZodNullable<z.ZodUUID>;
|
|
96
|
+
createdAt: z.ZodISODateTime;
|
|
97
|
+
activatedBy: z.ZodNullable<z.ZodUUID>;
|
|
98
|
+
activatedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
99
|
+
}, z.core.$strip>;
|
|
100
|
+
export type TemplateResponse = z.output<typeof templateResponseSchema>;
|
|
101
|
+
export declare const templateListResponseSchema: z.ZodArray<z.ZodObject<{
|
|
102
|
+
id: z.ZodUUID;
|
|
103
|
+
messageType: z.ZodEnum<{
|
|
104
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
105
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
106
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
107
|
+
}>;
|
|
108
|
+
channel: z.ZodEnum<{
|
|
109
|
+
EMAIL: "EMAIL";
|
|
110
|
+
SMS: "SMS";
|
|
111
|
+
}>;
|
|
112
|
+
version: z.ZodNumber;
|
|
113
|
+
name: z.ZodString;
|
|
114
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
115
|
+
bodyText: z.ZodString;
|
|
116
|
+
bodyHtml: z.ZodNullable<z.ZodString>;
|
|
117
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
118
|
+
name: z.ZodString;
|
|
119
|
+
description: z.ZodOptional<z.ZodString>;
|
|
120
|
+
example: z.ZodOptional<z.ZodString>;
|
|
121
|
+
}, z.core.$strip>>;
|
|
122
|
+
status: z.ZodEnum<{
|
|
123
|
+
active: "active";
|
|
124
|
+
draft: "draft";
|
|
125
|
+
archived: "archived";
|
|
126
|
+
}>;
|
|
127
|
+
createdBy: z.ZodNullable<z.ZodUUID>;
|
|
128
|
+
createdAt: z.ZodISODateTime;
|
|
129
|
+
activatedBy: z.ZodNullable<z.ZodUUID>;
|
|
130
|
+
activatedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
131
|
+
}, z.core.$strip>>;
|
|
132
|
+
/** Renders unsaved text, or a saved version, with the catalogue's sample values. */
|
|
133
|
+
export declare const previewTemplateSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
134
|
+
templateId: z.ZodUUID;
|
|
135
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
136
|
+
messageType: z.ZodEnum<{
|
|
137
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
138
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
139
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
140
|
+
}>;
|
|
141
|
+
channel: z.ZodEnum<{
|
|
142
|
+
EMAIL: "EMAIL";
|
|
143
|
+
SMS: "SMS";
|
|
144
|
+
}>;
|
|
145
|
+
name: z.ZodPipe<z.ZodString, z.ZodString>;
|
|
146
|
+
subject: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
147
|
+
bodyText: z.ZodString;
|
|
148
|
+
bodyHtml: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
149
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
150
|
+
name: z.ZodString;
|
|
151
|
+
description: z.ZodOptional<z.ZodString>;
|
|
152
|
+
example: z.ZodOptional<z.ZodString>;
|
|
153
|
+
}, z.core.$strict>>;
|
|
154
|
+
}, z.core.$strict>]>;
|
|
155
|
+
export type PreviewTemplateInput = z.input<typeof previewTemplateSchema>;
|
|
156
|
+
export type PreviewTemplateBody = z.output<typeof previewTemplateSchema>;
|
|
157
|
+
export declare const renderedMessageSchema: z.ZodObject<{
|
|
158
|
+
channel: z.ZodEnum<{
|
|
159
|
+
EMAIL: "EMAIL";
|
|
160
|
+
SMS: "SMS";
|
|
161
|
+
}>;
|
|
162
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
163
|
+
bodyText: z.ZodString;
|
|
164
|
+
bodyHtml: z.ZodNullable<z.ZodString>;
|
|
165
|
+
smsSegments: z.ZodNullable<z.ZodNumber>;
|
|
166
|
+
}, z.core.$strip>;
|
|
167
|
+
export type RenderedMessage = z.output<typeof renderedMessageSchema>;
|
|
168
|
+
export declare const testSendResponseSchema: z.ZodObject<{
|
|
169
|
+
channel: z.ZodEnum<{
|
|
170
|
+
EMAIL: "EMAIL";
|
|
171
|
+
SMS: "SMS";
|
|
172
|
+
}>;
|
|
173
|
+
sentTo: z.ZodString;
|
|
174
|
+
}, z.core.$strip>;
|
|
175
|
+
export type TestSendResponse = z.output<typeof testSendResponseSchema>;
|
|
176
|
+
/** A catalogue entry, with the built-in default template for each channel. */
|
|
177
|
+
export declare const messageTypeResponseSchema: z.ZodObject<{
|
|
178
|
+
type: z.ZodEnum<{
|
|
179
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
180
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
181
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
182
|
+
}>;
|
|
183
|
+
label: z.ZodString;
|
|
184
|
+
description: z.ZodString;
|
|
185
|
+
channels: z.ZodArray<z.ZodEnum<{
|
|
186
|
+
EMAIL: "EMAIL";
|
|
187
|
+
SMS: "SMS";
|
|
188
|
+
}>>;
|
|
189
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
190
|
+
name: z.ZodString;
|
|
191
|
+
description: z.ZodString;
|
|
192
|
+
example: z.ZodString;
|
|
193
|
+
}, z.core.$strip>>;
|
|
194
|
+
requiredVariables: z.ZodArray<z.ZodString>;
|
|
195
|
+
defaults: z.ZodArray<z.ZodObject<{
|
|
196
|
+
channel: z.ZodEnum<{
|
|
197
|
+
EMAIL: "EMAIL";
|
|
198
|
+
SMS: "SMS";
|
|
199
|
+
}>;
|
|
200
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
201
|
+
bodyText: z.ZodString;
|
|
202
|
+
bodyHtml: z.ZodNullable<z.ZodString>;
|
|
203
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
204
|
+
name: z.ZodString;
|
|
205
|
+
}, z.core.$strip>>;
|
|
206
|
+
}, z.core.$strip>>;
|
|
207
|
+
}, z.core.$strip>;
|
|
208
|
+
export type MessageTypeResponse = z.output<typeof messageTypeResponseSchema>;
|
|
209
|
+
export declare const messageTypeListResponseSchema: z.ZodArray<z.ZodObject<{
|
|
210
|
+
type: z.ZodEnum<{
|
|
211
|
+
"auth.otp.signup_verification": "auth.otp.signup_verification";
|
|
212
|
+
"auth.otp.password_reset": "auth.otp.password_reset";
|
|
213
|
+
"auth.password_reset_link": "auth.password_reset_link";
|
|
214
|
+
}>;
|
|
215
|
+
label: z.ZodString;
|
|
216
|
+
description: z.ZodString;
|
|
217
|
+
channels: z.ZodArray<z.ZodEnum<{
|
|
218
|
+
EMAIL: "EMAIL";
|
|
219
|
+
SMS: "SMS";
|
|
220
|
+
}>>;
|
|
221
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
222
|
+
name: z.ZodString;
|
|
223
|
+
description: z.ZodString;
|
|
224
|
+
example: z.ZodString;
|
|
225
|
+
}, z.core.$strip>>;
|
|
226
|
+
requiredVariables: z.ZodArray<z.ZodString>;
|
|
227
|
+
defaults: z.ZodArray<z.ZodObject<{
|
|
228
|
+
channel: z.ZodEnum<{
|
|
229
|
+
EMAIL: "EMAIL";
|
|
230
|
+
SMS: "SMS";
|
|
231
|
+
}>;
|
|
232
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
233
|
+
bodyText: z.ZodString;
|
|
234
|
+
bodyHtml: z.ZodNullable<z.ZodString>;
|
|
235
|
+
variables: z.ZodArray<z.ZodObject<{
|
|
236
|
+
name: z.ZodString;
|
|
237
|
+
}, z.core.$strip>>;
|
|
238
|
+
}, z.core.$strip>>;
|
|
239
|
+
}, z.core.$strip>>;
|
|
240
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.messageTypeListResponseSchema = exports.messageTypeResponseSchema = exports.testSendResponseSchema = exports.renderedMessageSchema = exports.previewTemplateSchema = exports.templateListResponseSchema = exports.templateResponseSchema = exports.templateListQuerySchema = exports.createTemplateSchema = exports.templateVariableSchema = void 0;
|
|
4
|
+
exports.templatePlaceholders = templatePlaceholders;
|
|
5
|
+
exports.hasMalformedPlaceholder = hasMalformedPlaceholder;
|
|
6
|
+
exports.templateProblems = templateProblems;
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const date_time_schema_js_1 = require("../common/date-time.schema.js");
|
|
9
|
+
const safe_text_schema_js_1 = require("../common/safe-text.schema.js");
|
|
10
|
+
const index_js_1 = require("../enums/index.js");
|
|
11
|
+
const message_catalogue_js_1 = require("./message-catalogue.js");
|
|
12
|
+
const PLACEHOLDER = /\{\{\s*([A-Za-z][A-Za-z0-9_]*)\s*\}\}/g;
|
|
13
|
+
/** The variable names used as `{{name}}` in a piece of template text, in order, without repeats. */
|
|
14
|
+
function templatePlaceholders(text) {
|
|
15
|
+
const names = new Set();
|
|
16
|
+
for (const match of text.matchAll(PLACEHOLDER)) {
|
|
17
|
+
names.add(match[1]);
|
|
18
|
+
}
|
|
19
|
+
return [...names];
|
|
20
|
+
}
|
|
21
|
+
/** True when the text has a `{{` or `}}` that is not part of a valid placeholder. */
|
|
22
|
+
function hasMalformedPlaceholder(text) {
|
|
23
|
+
const rest = text.replace(PLACEHOLDER, '');
|
|
24
|
+
return rest.includes('{{') || rest.includes('}}');
|
|
25
|
+
}
|
|
26
|
+
const MAX_SUBJECT_LENGTH = 200;
|
|
27
|
+
const MAX_BODY_LENGTH = 20000;
|
|
28
|
+
/** A variable a template declares. Its value is supplied when the message is sent. */
|
|
29
|
+
exports.templateVariableSchema = zod_1.z.strictObject({
|
|
30
|
+
name: zod_1.z
|
|
31
|
+
.string()
|
|
32
|
+
.regex(/^[A-Za-z][A-Za-z0-9_]*$/, {
|
|
33
|
+
error: 'Use letters, digits and underscores, starting with a letter',
|
|
34
|
+
})
|
|
35
|
+
.max(64),
|
|
36
|
+
description: (0, safe_text_schema_js_1.safeTextSchema)(200).optional(),
|
|
37
|
+
example: zod_1.z.string().max(500).optional(),
|
|
38
|
+
});
|
|
39
|
+
const templateFields = {
|
|
40
|
+
messageType: zod_1.z.enum(index_js_1.MESSAGE_TYPES),
|
|
41
|
+
channel: zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS),
|
|
42
|
+
name: (0, safe_text_schema_js_1.safeTextSchema)(100).pipe(zod_1.z.string().min(1, { error: 'Name it' })),
|
|
43
|
+
subject: zod_1.z.string().max(MAX_SUBJECT_LENGTH).nullish(),
|
|
44
|
+
bodyText: zod_1.z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1, { error: 'Write the message' })
|
|
47
|
+
.max(MAX_BODY_LENGTH),
|
|
48
|
+
bodyHtml: zod_1.z.string().max(MAX_BODY_LENGTH).nullish(),
|
|
49
|
+
variables: zod_1.z.array(exports.templateVariableSchema).max(20),
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The rules every template must meet before it is saved: the declared
|
|
53
|
+
* variables match the placeholders exactly, every one is supplied by the
|
|
54
|
+
* message type, and the type's required variables are used. Shared by the
|
|
55
|
+
* server and the admin editor, so both report the same problems.
|
|
56
|
+
*/
|
|
57
|
+
function templateProblems(template) {
|
|
58
|
+
const problems = [];
|
|
59
|
+
const definition = message_catalogue_js_1.MESSAGE_CATALOGUE[template.messageType];
|
|
60
|
+
const isEmail = template.channel === 'EMAIL';
|
|
61
|
+
if (!definition.channels.includes(template.channel)) {
|
|
62
|
+
problems.push({
|
|
63
|
+
path: 'channel',
|
|
64
|
+
message: `${definition.label} cannot be sent by ${template.channel}`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (isEmail && !template.subject?.trim()) {
|
|
68
|
+
problems.push({ path: 'subject', message: 'An email needs a subject' });
|
|
69
|
+
}
|
|
70
|
+
if (!isEmail && (template.subject || template.bodyHtml)) {
|
|
71
|
+
problems.push({
|
|
72
|
+
path: 'bodyHtml',
|
|
73
|
+
message: 'Only email templates have a subject or HTML',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (template.channel === 'SMS' && template.bodyText.length > message_catalogue_js_1.MAX_SMS_LENGTH) {
|
|
77
|
+
problems.push({
|
|
78
|
+
path: 'bodyText',
|
|
79
|
+
message: `An SMS can be at most ${String(message_catalogue_js_1.MAX_SMS_LENGTH)} characters`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const texts = [
|
|
83
|
+
template.subject ?? '',
|
|
84
|
+
template.bodyText,
|
|
85
|
+
template.bodyHtml ?? '',
|
|
86
|
+
];
|
|
87
|
+
if (texts.some(hasMalformedPlaceholder)) {
|
|
88
|
+
problems.push({
|
|
89
|
+
path: 'bodyText',
|
|
90
|
+
message: 'Write placeholders as {{name}}, with matching braces',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const used = new Set(texts.flatMap(templatePlaceholders));
|
|
94
|
+
const declared = template.variables.map((variable) => variable.name);
|
|
95
|
+
const declaredSet = new Set(declared);
|
|
96
|
+
if (declaredSet.size !== declared.length) {
|
|
97
|
+
problems.push({ path: 'variables', message: 'Declare each variable once' });
|
|
98
|
+
}
|
|
99
|
+
for (const name of used) {
|
|
100
|
+
if (!declaredSet.has(name)) {
|
|
101
|
+
problems.push({
|
|
102
|
+
path: 'variables',
|
|
103
|
+
message: `{{${name}}} is used but not declared`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const name of declaredSet) {
|
|
108
|
+
if (!used.has(name)) {
|
|
109
|
+
problems.push({
|
|
110
|
+
path: 'variables',
|
|
111
|
+
message: `${name} is declared but not used`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (!(name in definition.variables)) {
|
|
115
|
+
problems.push({
|
|
116
|
+
path: 'variables',
|
|
117
|
+
message: `${definition.label} does not supply ${name}`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const name of definition.requiredVariables) {
|
|
122
|
+
if (!declaredSet.has(name)) {
|
|
123
|
+
problems.push({
|
|
124
|
+
path: 'variables',
|
|
125
|
+
message: `${definition.label} must include {{${name}}}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return problems;
|
|
130
|
+
}
|
|
131
|
+
/** A new draft. Saved templates are never edited; a change is a new version. */
|
|
132
|
+
exports.createTemplateSchema = zod_1.z
|
|
133
|
+
.strictObject(templateFields)
|
|
134
|
+
.superRefine((template, ctx) => {
|
|
135
|
+
for (const problem of templateProblems(template)) {
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: 'custom',
|
|
138
|
+
path: [problem.path],
|
|
139
|
+
message: problem.message,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
exports.templateListQuerySchema = zod_1.z.strictObject({
|
|
144
|
+
messageType: zod_1.z.enum(index_js_1.MESSAGE_TYPES).optional(),
|
|
145
|
+
channel: zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS).optional(),
|
|
146
|
+
});
|
|
147
|
+
exports.templateResponseSchema = zod_1.z.object({
|
|
148
|
+
id: zod_1.z.uuid(),
|
|
149
|
+
messageType: zod_1.z.enum(index_js_1.MESSAGE_TYPES),
|
|
150
|
+
channel: zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS),
|
|
151
|
+
version: zod_1.z.number().int(),
|
|
152
|
+
name: zod_1.z.string(),
|
|
153
|
+
subject: zod_1.z.string().nullable(),
|
|
154
|
+
bodyText: zod_1.z.string(),
|
|
155
|
+
bodyHtml: zod_1.z.string().nullable(),
|
|
156
|
+
variables: zod_1.z.array(zod_1.z.object({
|
|
157
|
+
name: zod_1.z.string(),
|
|
158
|
+
description: zod_1.z.string().optional(),
|
|
159
|
+
example: zod_1.z.string().optional(),
|
|
160
|
+
})),
|
|
161
|
+
status: zod_1.z.enum(index_js_1.TEMPLATE_STATUSES),
|
|
162
|
+
createdBy: zod_1.z.uuid().nullable(),
|
|
163
|
+
createdAt: date_time_schema_js_1.utcTimestampSchema,
|
|
164
|
+
activatedBy: zod_1.z.uuid().nullable(),
|
|
165
|
+
activatedAt: date_time_schema_js_1.utcTimestampSchema.nullable(),
|
|
166
|
+
});
|
|
167
|
+
exports.templateListResponseSchema = zod_1.z.array(exports.templateResponseSchema);
|
|
168
|
+
/** Renders unsaved text, or a saved version, with the catalogue's sample values. */
|
|
169
|
+
exports.previewTemplateSchema = zod_1.z.union([
|
|
170
|
+
zod_1.z.strictObject({ templateId: zod_1.z.uuid() }),
|
|
171
|
+
zod_1.z.strictObject(templateFields),
|
|
172
|
+
]);
|
|
173
|
+
exports.renderedMessageSchema = zod_1.z.object({
|
|
174
|
+
channel: zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS),
|
|
175
|
+
subject: zod_1.z.string().nullable(),
|
|
176
|
+
bodyText: zod_1.z.string(),
|
|
177
|
+
bodyHtml: zod_1.z.string().nullable(),
|
|
178
|
+
/** SMS only: how many 160-character segments it takes. */
|
|
179
|
+
smsSegments: zod_1.z.number().int().nullable(),
|
|
180
|
+
});
|
|
181
|
+
exports.testSendResponseSchema = zod_1.z.object({
|
|
182
|
+
channel: zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS),
|
|
183
|
+
/** The signed-in admin's own address, masked, such as `b***@tbs.coach`. */
|
|
184
|
+
sentTo: zod_1.z.string(),
|
|
185
|
+
});
|
|
186
|
+
/** A catalogue entry, with the built-in default template for each channel. */
|
|
187
|
+
exports.messageTypeResponseSchema = zod_1.z.object({
|
|
188
|
+
type: zod_1.z.enum(index_js_1.MESSAGE_TYPES),
|
|
189
|
+
label: zod_1.z.string(),
|
|
190
|
+
description: zod_1.z.string(),
|
|
191
|
+
channels: zod_1.z.array(zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS)),
|
|
192
|
+
variables: zod_1.z.array(zod_1.z.object({
|
|
193
|
+
name: zod_1.z.string(),
|
|
194
|
+
description: zod_1.z.string(),
|
|
195
|
+
example: zod_1.z.string(),
|
|
196
|
+
})),
|
|
197
|
+
requiredVariables: zod_1.z.array(zod_1.z.string()),
|
|
198
|
+
defaults: zod_1.z.array(zod_1.z.object({
|
|
199
|
+
channel: zod_1.z.enum(index_js_1.COMMUNICATION_CHANNELS),
|
|
200
|
+
subject: zod_1.z.string().nullable(),
|
|
201
|
+
bodyText: zod_1.z.string(),
|
|
202
|
+
bodyHtml: zod_1.z.string().nullable(),
|
|
203
|
+
variables: zod_1.z.array(zod_1.z.object({ name: zod_1.z.string() })),
|
|
204
|
+
})),
|
|
205
|
+
});
|
|
206
|
+
exports.messageTypeListResponseSchema = zod_1.z.array(exports.messageTypeResponseSchema);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const APP_ENVIRONMENTS: readonly ["development", "staging", "production"];
|
|
2
2
|
export type AppEnvironment = (typeof APP_ENVIRONMENTS)[number];
|
|
3
3
|
/** Each user has exactly one role. A new role is added here and nowhere else. */
|
|
4
|
-
export declare const ROLES: readonly ["ADMIN", "COACH", "
|
|
4
|
+
export declare const ROLES: readonly ["ADMIN", "COACH", "CLIENT"];
|
|
5
5
|
export type Role = (typeof ROLES)[number];
|
|
6
6
|
/**
|
|
7
7
|
* `pending_verification` is a self-registered user who has not yet proved they
|
|
@@ -23,5 +23,24 @@ export declare const TOKEN_TRANSPORTS: readonly ["body", "cookie"];
|
|
|
23
23
|
export type TokenTransport = (typeof TOKEN_TRANSPORTS)[number];
|
|
24
24
|
export declare const AUDIT_OUTCOMES: readonly ["success", "failure"];
|
|
25
25
|
export type AuditOutcome = (typeof AUDIT_OUTCOMES)[number];
|
|
26
|
-
export declare const FEATURE_FLAG_RULE_TYPES: readonly ["user", "
|
|
26
|
+
export declare const FEATURE_FLAG_RULE_TYPES: readonly ["user", "client", "trainer", "role"];
|
|
27
27
|
export type FeatureFlagRuleType = (typeof FEATURE_FLAG_RULE_TYPES)[number];
|
|
28
|
+
/**
|
|
29
|
+
* The ways a message can reach someone. WhatsApp and push are added here when
|
|
30
|
+
* their channel modules are built.
|
|
31
|
+
*/
|
|
32
|
+
export declare const COMMUNICATION_CHANNELS: readonly ["EMAIL", "SMS"];
|
|
33
|
+
export type CommunicationChannel = (typeof COMMUNICATION_CHANNELS)[number];
|
|
34
|
+
/**
|
|
35
|
+
* Every message the platform sends. Each has a catalogue entry in
|
|
36
|
+
* `@tbs-fitness/contract/communication` saying which channels may carry it and
|
|
37
|
+
* which variables its sender supplies.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MESSAGE_TYPES: readonly ["auth.otp.signup_verification", "auth.otp.password_reset", "auth.password_reset_link"];
|
|
40
|
+
export type MessageType = (typeof MESSAGE_TYPES)[number];
|
|
41
|
+
/** What happened to one message on one channel. */
|
|
42
|
+
export declare const DELIVERY_STATUSES: readonly ["sent", "skipped_disabled", "skipped_unreachable", "failed", "failed_missing_variables"];
|
|
43
|
+
export type DeliveryStatus = (typeof DELIVERY_STATUSES)[number];
|
|
44
|
+
/** Only one version of a template per message type and channel is active. */
|
|
45
|
+
export declare const TEMPLATE_STATUSES: readonly ["draft", "active", "archived"];
|
|
46
|
+
export type TemplateStatus = (typeof TEMPLATE_STATUSES)[number];
|