@k-msg/template 0.1.1 → 0.1.2
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 +11 -15
- package/dist/builder/template.builder.d.ts +139 -0
- package/dist/index.d.ts +12 -461
- package/dist/index.js +21 -1276
- package/dist/index.js.map +89 -1
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +89 -0
- package/dist/interpolator.d.ts +8 -0
- package/dist/parser/button.parser.d.ts +25 -0
- package/dist/parser/validator.d.ts +24 -0
- package/dist/parser/variable.parser.d.ts +27 -0
- package/dist/registry/template.registry.d.ts +168 -0
- package/dist/service.d.ts +14 -0
- package/dist/services/template.service.d.ts +10 -0
- package/dist/types/template.types.d.ts +150 -0
- package/package.json +18 -12
- package/dist/index.cjs +0 -1315
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -463
package/dist/index.cjs
DELETED
|
@@ -1,1315 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
ButtonParser: () => ButtonParser,
|
|
24
|
-
TemplateBuilder: () => TemplateBuilder,
|
|
25
|
-
TemplateBuilders: () => TemplateBuilders,
|
|
26
|
-
TemplateCategory: () => TemplateCategory,
|
|
27
|
-
TemplateRegistry: () => TemplateRegistry,
|
|
28
|
-
TemplateService: () => TemplateService,
|
|
29
|
-
TemplateStatus: () => TemplateStatus,
|
|
30
|
-
TemplateType: () => TemplateType,
|
|
31
|
-
TemplateValidator: () => TemplateValidator,
|
|
32
|
-
VariableParser: () => VariableParser
|
|
33
|
-
});
|
|
34
|
-
module.exports = __toCommonJS(index_exports);
|
|
35
|
-
|
|
36
|
-
// src/parser/variable.parser.ts
|
|
37
|
-
var VariableParser = class {
|
|
38
|
-
static VARIABLE_PATTERN = /#{([^}]+)}/g;
|
|
39
|
-
/**
|
|
40
|
-
* 템플릿 내용에서 변수를 추출합니다
|
|
41
|
-
*/
|
|
42
|
-
static extractVariables(content) {
|
|
43
|
-
const variables = [];
|
|
44
|
-
const matches = content.matchAll(this.VARIABLE_PATTERN);
|
|
45
|
-
for (const match of matches) {
|
|
46
|
-
const variableName = match[1].trim();
|
|
47
|
-
if (variableName && !variables.includes(variableName)) {
|
|
48
|
-
variables.push(variableName);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return variables;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* 템플릿 내용의 변수를 실제 값으로 치환합니다
|
|
55
|
-
*/
|
|
56
|
-
static replaceVariables(content, variables) {
|
|
57
|
-
return content.replace(this.VARIABLE_PATTERN, (match, variableName) => {
|
|
58
|
-
const value = variables[variableName.trim()];
|
|
59
|
-
if (value === void 0 || value === null) {
|
|
60
|
-
return match;
|
|
61
|
-
}
|
|
62
|
-
if (value instanceof Date) {
|
|
63
|
-
return value.toISOString();
|
|
64
|
-
}
|
|
65
|
-
return String(value);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* 변수 정의와 실제 제공된 값을 검증합니다
|
|
70
|
-
*/
|
|
71
|
-
static validateVariables(variableDefinitions, providedVariables) {
|
|
72
|
-
const errors = [];
|
|
73
|
-
for (const definition of variableDefinitions) {
|
|
74
|
-
const value = providedVariables[definition.name];
|
|
75
|
-
if (definition.required && (value === void 0 || value === null)) {
|
|
76
|
-
errors.push(`Required variable '${definition.name}' is missing`);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (value === void 0 || value === null) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (!this.validateVariableType(value, definition.type)) {
|
|
83
|
-
errors.push(`Variable '${definition.name}' has invalid type. Expected: ${definition.type}`);
|
|
84
|
-
}
|
|
85
|
-
if (definition.maxLength && String(value).length > definition.maxLength) {
|
|
86
|
-
errors.push(`Variable '${definition.name}' exceeds maximum length of ${definition.maxLength}`);
|
|
87
|
-
}
|
|
88
|
-
if (definition.format && !new RegExp(definition.format).test(String(value))) {
|
|
89
|
-
errors.push(`Variable '${definition.name}' does not match required format: ${definition.format}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
isValid: errors.length === 0,
|
|
94
|
-
errors
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
static validateVariableType(value, expectedType) {
|
|
98
|
-
switch (expectedType) {
|
|
99
|
-
case "string":
|
|
100
|
-
return typeof value === "string";
|
|
101
|
-
case "number":
|
|
102
|
-
return typeof value === "number" && !isNaN(value);
|
|
103
|
-
case "date":
|
|
104
|
-
return value instanceof Date || !isNaN(Date.parse(value));
|
|
105
|
-
case "custom":
|
|
106
|
-
return true;
|
|
107
|
-
// 커스텀 타입은 별도 검증 로직 필요
|
|
108
|
-
default:
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* 템플릿에서 사용된 변수와 정의된 변수의 일치성을 검사합니다
|
|
114
|
-
*/
|
|
115
|
-
static validateTemplateVariables(content, variableDefinitions) {
|
|
116
|
-
const usedVariables = this.extractVariables(content);
|
|
117
|
-
const definedVariables = variableDefinitions.map((v) => v.name);
|
|
118
|
-
const errors = [];
|
|
119
|
-
for (const usedVar of usedVariables) {
|
|
120
|
-
if (!definedVariables.includes(usedVar)) {
|
|
121
|
-
errors.push(`Variable '${usedVar}' is used in template but not defined`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
for (const definition of variableDefinitions) {
|
|
125
|
-
if (definition.required && !usedVariables.includes(definition.name)) {
|
|
126
|
-
errors.push(`Required variable '${definition.name}' is defined but not used in template`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
isValid: errors.length === 0,
|
|
131
|
-
errors
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// src/types/template.types.ts
|
|
137
|
-
var import_zod = require("zod");
|
|
138
|
-
var TemplateType = /* @__PURE__ */ ((TemplateType2) => {
|
|
139
|
-
TemplateType2["ALIMTALK"] = "ALIMTALK";
|
|
140
|
-
TemplateType2["SMS"] = "SMS";
|
|
141
|
-
TemplateType2["LMS"] = "LMS";
|
|
142
|
-
TemplateType2["MMS"] = "MMS";
|
|
143
|
-
TemplateType2["RCS"] = "RCS";
|
|
144
|
-
return TemplateType2;
|
|
145
|
-
})(TemplateType || {});
|
|
146
|
-
var TemplateCategory = /* @__PURE__ */ ((TemplateCategory2) => {
|
|
147
|
-
TemplateCategory2["AUTHENTICATION"] = "AUTHENTICATION";
|
|
148
|
-
TemplateCategory2["NOTIFICATION"] = "NOTIFICATION";
|
|
149
|
-
TemplateCategory2["PROMOTION"] = "PROMOTION";
|
|
150
|
-
TemplateCategory2["INFORMATION"] = "INFORMATION";
|
|
151
|
-
TemplateCategory2["RESERVATION"] = "RESERVATION";
|
|
152
|
-
TemplateCategory2["SHIPPING"] = "SHIPPING";
|
|
153
|
-
TemplateCategory2["PAYMENT"] = "PAYMENT";
|
|
154
|
-
return TemplateCategory2;
|
|
155
|
-
})(TemplateCategory || {});
|
|
156
|
-
var TemplateStatus = /* @__PURE__ */ ((TemplateStatus2) => {
|
|
157
|
-
TemplateStatus2["DRAFT"] = "DRAFT";
|
|
158
|
-
TemplateStatus2["PENDING"] = "PENDING";
|
|
159
|
-
TemplateStatus2["APPROVED"] = "APPROVED";
|
|
160
|
-
TemplateStatus2["REJECTED"] = "REJECTED";
|
|
161
|
-
TemplateStatus2["DISABLED"] = "DISABLED";
|
|
162
|
-
return TemplateStatus2;
|
|
163
|
-
})(TemplateStatus || {});
|
|
164
|
-
var TemplateVariableSchema = import_zod.z.object({
|
|
165
|
-
name: import_zod.z.string().min(1),
|
|
166
|
-
type: import_zod.z.enum(["string", "number", "date", "custom"]),
|
|
167
|
-
required: import_zod.z.boolean(),
|
|
168
|
-
maxLength: import_zod.z.number().optional(),
|
|
169
|
-
format: import_zod.z.string().optional(),
|
|
170
|
-
description: import_zod.z.string().optional(),
|
|
171
|
-
example: import_zod.z.string().optional()
|
|
172
|
-
});
|
|
173
|
-
var TemplateButtonSchema = import_zod.z.object({
|
|
174
|
-
type: import_zod.z.enum(["WL", "AL", "DS", "BK", "MD"]),
|
|
175
|
-
name: import_zod.z.string().min(1),
|
|
176
|
-
linkMobile: import_zod.z.string().url().optional(),
|
|
177
|
-
linkPc: import_zod.z.string().url().optional(),
|
|
178
|
-
linkIos: import_zod.z.string().url().optional(),
|
|
179
|
-
linkAndroid: import_zod.z.string().url().optional(),
|
|
180
|
-
schemeIos: import_zod.z.string().optional(),
|
|
181
|
-
schemeAndroid: import_zod.z.string().optional()
|
|
182
|
-
});
|
|
183
|
-
var AlimTalkTemplateSchema = import_zod.z.object({
|
|
184
|
-
id: import_zod.z.string(),
|
|
185
|
-
code: import_zod.z.string(),
|
|
186
|
-
name: import_zod.z.string().min(1),
|
|
187
|
-
content: import_zod.z.string().min(1),
|
|
188
|
-
variables: import_zod.z.array(TemplateVariableSchema),
|
|
189
|
-
buttons: import_zod.z.array(TemplateButtonSchema).optional(),
|
|
190
|
-
category: import_zod.z.nativeEnum(TemplateCategory),
|
|
191
|
-
status: import_zod.z.nativeEnum(TemplateStatus),
|
|
192
|
-
provider: import_zod.z.string(),
|
|
193
|
-
metadata: import_zod.z.object({
|
|
194
|
-
createdAt: import_zod.z.date(),
|
|
195
|
-
updatedAt: import_zod.z.date(),
|
|
196
|
-
approvedAt: import_zod.z.date().optional(),
|
|
197
|
-
rejectedAt: import_zod.z.date().optional(),
|
|
198
|
-
rejectionReason: import_zod.z.string().optional(),
|
|
199
|
-
usage: import_zod.z.object({
|
|
200
|
-
sent: import_zod.z.number().min(0),
|
|
201
|
-
delivered: import_zod.z.number().min(0),
|
|
202
|
-
failed: import_zod.z.number().min(0)
|
|
203
|
-
})
|
|
204
|
-
})
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// src/parser/button.parser.ts
|
|
208
|
-
var ButtonParser = class {
|
|
209
|
-
/**
|
|
210
|
-
* 버튼 설정의 유효성을 검증합니다
|
|
211
|
-
*/
|
|
212
|
-
static validateButtons(buttons) {
|
|
213
|
-
const errors = [];
|
|
214
|
-
if (buttons.length > 5) {
|
|
215
|
-
errors.push("Maximum 5 buttons are allowed");
|
|
216
|
-
}
|
|
217
|
-
for (let i = 0; i < buttons.length; i++) {
|
|
218
|
-
const button = buttons[i];
|
|
219
|
-
const buttonIndex = i + 1;
|
|
220
|
-
if (!button.name || button.name.trim().length === 0) {
|
|
221
|
-
errors.push(`Button ${buttonIndex}: name is required`);
|
|
222
|
-
} else if (button.name.length > 14) {
|
|
223
|
-
errors.push(`Button ${buttonIndex}: name cannot exceed 14 characters`);
|
|
224
|
-
}
|
|
225
|
-
this.validateButtonByType(button, buttonIndex, errors);
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
isValid: errors.length === 0,
|
|
229
|
-
errors
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
static validateButtonByType(button, buttonIndex, errors) {
|
|
233
|
-
switch (button.type) {
|
|
234
|
-
case "WL":
|
|
235
|
-
this.validateWebLinkButton(button, buttonIndex, errors);
|
|
236
|
-
break;
|
|
237
|
-
case "AL":
|
|
238
|
-
this.validateAppLinkButton(button, buttonIndex, errors);
|
|
239
|
-
break;
|
|
240
|
-
case "DS":
|
|
241
|
-
this.validateDeliveryButton(button, buttonIndex, errors);
|
|
242
|
-
break;
|
|
243
|
-
case "BK":
|
|
244
|
-
this.validateBotKeywordButton(button, buttonIndex, errors);
|
|
245
|
-
break;
|
|
246
|
-
case "MD":
|
|
247
|
-
this.validateMessageDeliveryButton(button, buttonIndex, errors);
|
|
248
|
-
break;
|
|
249
|
-
default:
|
|
250
|
-
errors.push(`Button ${buttonIndex}: invalid button type '${button.type}'`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
static validateWebLinkButton(button, buttonIndex, errors) {
|
|
254
|
-
if (!button.linkMobile && !button.linkPc) {
|
|
255
|
-
errors.push(`Button ${buttonIndex}: web link button must have at least mobile or PC link`);
|
|
256
|
-
}
|
|
257
|
-
if (button.linkMobile && !this.isValidUrl(button.linkMobile)) {
|
|
258
|
-
errors.push(`Button ${buttonIndex}: invalid mobile link URL`);
|
|
259
|
-
}
|
|
260
|
-
if (button.linkPc && !this.isValidUrl(button.linkPc)) {
|
|
261
|
-
errors.push(`Button ${buttonIndex}: invalid PC link URL`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
static validateAppLinkButton(button, buttonIndex, errors) {
|
|
265
|
-
const hasAnyLink = button.linkIos || button.linkAndroid || button.schemeIos || button.schemeAndroid;
|
|
266
|
-
if (!hasAnyLink) {
|
|
267
|
-
errors.push(`Button ${buttonIndex}: app link button must have at least one app link or scheme`);
|
|
268
|
-
}
|
|
269
|
-
if (button.linkIos && !this.isValidUrl(button.linkIos)) {
|
|
270
|
-
errors.push(`Button ${buttonIndex}: invalid iOS link URL`);
|
|
271
|
-
}
|
|
272
|
-
if (button.linkAndroid && !this.isValidUrl(button.linkAndroid)) {
|
|
273
|
-
errors.push(`Button ${buttonIndex}: invalid Android link URL`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
static validateDeliveryButton(button, buttonIndex, errors) {
|
|
277
|
-
}
|
|
278
|
-
static validateBotKeywordButton(button, buttonIndex, errors) {
|
|
279
|
-
}
|
|
280
|
-
static validateMessageDeliveryButton(button, buttonIndex, errors) {
|
|
281
|
-
}
|
|
282
|
-
static isValidUrl(url) {
|
|
283
|
-
try {
|
|
284
|
-
new URL(url);
|
|
285
|
-
return true;
|
|
286
|
-
} catch {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* 버튼을 JSON 문자열로 직렬화합니다 (카카오 API 형식)
|
|
292
|
-
*/
|
|
293
|
-
static serializeButtons(buttons) {
|
|
294
|
-
const serializedButtons = buttons.map((button) => ({
|
|
295
|
-
name: button.name,
|
|
296
|
-
type: button.type,
|
|
297
|
-
url_mobile: button.linkMobile,
|
|
298
|
-
url_pc: button.linkPc,
|
|
299
|
-
scheme_ios: button.schemeIos,
|
|
300
|
-
scheme_android: button.schemeAndroid
|
|
301
|
-
}));
|
|
302
|
-
return JSON.stringify(serializedButtons);
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* JSON 문자열에서 버튼 배열로 역직렬화합니다
|
|
306
|
-
*/
|
|
307
|
-
static deserializeButtons(buttonsJson) {
|
|
308
|
-
try {
|
|
309
|
-
const parsed = JSON.parse(buttonsJson);
|
|
310
|
-
return parsed.map((button) => ({
|
|
311
|
-
name: button.name,
|
|
312
|
-
type: button.type,
|
|
313
|
-
linkMobile: button.url_mobile,
|
|
314
|
-
linkPc: button.url_pc,
|
|
315
|
-
linkIos: button.url_ios,
|
|
316
|
-
linkAndroid: button.url_android,
|
|
317
|
-
schemeIos: button.scheme_ios,
|
|
318
|
-
schemeAndroid: button.scheme_android
|
|
319
|
-
}));
|
|
320
|
-
} catch (error) {
|
|
321
|
-
throw new Error(`Failed to parse buttons JSON: ${error}`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
// src/parser/validator.ts
|
|
327
|
-
var TemplateValidator = class {
|
|
328
|
-
/**
|
|
329
|
-
* 알림톡 템플릿의 전체적인 유효성을 검증합니다
|
|
330
|
-
*/
|
|
331
|
-
static validate(template) {
|
|
332
|
-
const errors = [];
|
|
333
|
-
const warnings = [];
|
|
334
|
-
this.validateBasicFields(template, errors);
|
|
335
|
-
this.validateContent(template, errors, warnings);
|
|
336
|
-
const variableValidation = VariableParser.validateTemplateVariables(
|
|
337
|
-
template.content,
|
|
338
|
-
template.variables
|
|
339
|
-
);
|
|
340
|
-
errors.push(...variableValidation.errors);
|
|
341
|
-
if (template.buttons && template.buttons.length > 0) {
|
|
342
|
-
const buttonValidation = ButtonParser.validateButtons(template.buttons);
|
|
343
|
-
errors.push(...buttonValidation.errors);
|
|
344
|
-
}
|
|
345
|
-
this.validateByCategory(template, errors, warnings);
|
|
346
|
-
return {
|
|
347
|
-
isValid: errors.length === 0,
|
|
348
|
-
errors,
|
|
349
|
-
warnings
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
static validateBasicFields(template, errors) {
|
|
353
|
-
if (!template.name || template.name.trim().length === 0) {
|
|
354
|
-
errors.push("Template name is required");
|
|
355
|
-
}
|
|
356
|
-
if (!template.code || template.code.trim().length === 0) {
|
|
357
|
-
errors.push("Template code is required");
|
|
358
|
-
}
|
|
359
|
-
if (!template.provider || template.provider.trim().length === 0) {
|
|
360
|
-
errors.push("Provider is required");
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
static validateContent(template, errors, warnings) {
|
|
364
|
-
if (!template.content || template.content.trim().length === 0) {
|
|
365
|
-
errors.push("Template content is required");
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
const content = template.content;
|
|
369
|
-
if (content.length > 1e3) {
|
|
370
|
-
errors.push("Template content cannot exceed 1000 characters");
|
|
371
|
-
}
|
|
372
|
-
if (this.containsProhibitedCharacters(content)) {
|
|
373
|
-
errors.push("Template content contains prohibited characters");
|
|
374
|
-
}
|
|
375
|
-
const lineBreaks = (content.match(/\n/g) || []).length;
|
|
376
|
-
if (lineBreaks > 20) {
|
|
377
|
-
warnings.push("Template content has many line breaks, which may affect readability");
|
|
378
|
-
}
|
|
379
|
-
this.validateUrlsInContent(content, warnings);
|
|
380
|
-
}
|
|
381
|
-
static containsProhibitedCharacters(content) {
|
|
382
|
-
const prohibitedChars = /[<>]/;
|
|
383
|
-
return prohibitedChars.test(content);
|
|
384
|
-
}
|
|
385
|
-
static validateUrlsInContent(content, warnings) {
|
|
386
|
-
const urlPattern = /https?:\/\/[^\s]+/g;
|
|
387
|
-
const urls = content.match(urlPattern) || [];
|
|
388
|
-
for (const url of urls) {
|
|
389
|
-
try {
|
|
390
|
-
new URL(url);
|
|
391
|
-
} catch {
|
|
392
|
-
warnings.push(`Invalid URL found in content: ${url}`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
static validateByCategory(template, errors, warnings) {
|
|
397
|
-
switch (template.category) {
|
|
398
|
-
case "AUTHENTICATION" /* AUTHENTICATION */:
|
|
399
|
-
this.validateAuthenticationTemplate(template, errors, warnings);
|
|
400
|
-
break;
|
|
401
|
-
case "PROMOTION" /* PROMOTION */:
|
|
402
|
-
this.validatePromotionTemplate(template, errors, warnings);
|
|
403
|
-
break;
|
|
404
|
-
case "PAYMENT" /* PAYMENT */:
|
|
405
|
-
this.validatePaymentTemplate(template, errors, warnings);
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
static validateAuthenticationTemplate(template, errors, warnings) {
|
|
410
|
-
const hasAuthCode = template.variables.some(
|
|
411
|
-
(v) => v.name.includes("\uC778\uC99D") || v.name.includes("\uCF54\uB4DC") || v.name.includes("\uBC88\uD638")
|
|
412
|
-
);
|
|
413
|
-
if (!hasAuthCode) {
|
|
414
|
-
warnings.push("Authentication template should include an authentication code variable");
|
|
415
|
-
}
|
|
416
|
-
if (template.buttons && template.buttons.length > 0) {
|
|
417
|
-
warnings.push("Authentication templates typically should not have buttons");
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
static validatePromotionTemplate(template, errors, warnings) {
|
|
421
|
-
if (!template.buttons || template.buttons.length === 0) {
|
|
422
|
-
warnings.push("Promotion templates typically should have action buttons");
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
static validatePaymentTemplate(template, errors, warnings) {
|
|
426
|
-
const hasAmountVariable = template.variables.some(
|
|
427
|
-
(v) => v.name.includes("\uAE08\uC561") || v.name.includes("\uAC00\uACA9") || v.name.includes("\uC6D0")
|
|
428
|
-
);
|
|
429
|
-
if (!hasAmountVariable) {
|
|
430
|
-
warnings.push("Payment template should include an amount variable");
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* 빠른 검증 - 기본적인 필수 필드만 검사
|
|
435
|
-
*/
|
|
436
|
-
static quickValidate(template) {
|
|
437
|
-
const errors = [];
|
|
438
|
-
if (!template.name) errors.push("Name is required");
|
|
439
|
-
if (!template.content) errors.push("Content is required");
|
|
440
|
-
if (!template.category) errors.push("Category is required");
|
|
441
|
-
if (!template.provider) errors.push("Provider is required");
|
|
442
|
-
return {
|
|
443
|
-
isValid: errors.length === 0,
|
|
444
|
-
errors,
|
|
445
|
-
warnings: []
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
// src/services/template.service.ts
|
|
451
|
-
var TemplateService = class {
|
|
452
|
-
templates = /* @__PURE__ */ new Map();
|
|
453
|
-
async createTemplate(template) {
|
|
454
|
-
const variables = VariableParser.extractVariables(template.content);
|
|
455
|
-
const newTemplate = {
|
|
456
|
-
...template,
|
|
457
|
-
id: this.generateTemplateId(),
|
|
458
|
-
variables: variables.map((name) => ({
|
|
459
|
-
name,
|
|
460
|
-
type: "string",
|
|
461
|
-
required: true
|
|
462
|
-
})),
|
|
463
|
-
metadata: {
|
|
464
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
465
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
466
|
-
usage: { sent: 0, delivered: 0, failed: 0 }
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
const validation = TemplateValidator.validate(newTemplate);
|
|
470
|
-
if (!validation.isValid) {
|
|
471
|
-
throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
|
|
472
|
-
}
|
|
473
|
-
this.templates.set(newTemplate.id, newTemplate);
|
|
474
|
-
return newTemplate;
|
|
475
|
-
}
|
|
476
|
-
async getTemplate(templateId) {
|
|
477
|
-
return this.templates.get(templateId) || null;
|
|
478
|
-
}
|
|
479
|
-
async updateTemplate(templateId, updates) {
|
|
480
|
-
const template = this.templates.get(templateId);
|
|
481
|
-
if (!template) {
|
|
482
|
-
throw new Error(`Template ${templateId} not found`);
|
|
483
|
-
}
|
|
484
|
-
const updatedTemplate = {
|
|
485
|
-
...template,
|
|
486
|
-
...updates,
|
|
487
|
-
metadata: {
|
|
488
|
-
...template.metadata,
|
|
489
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
this.templates.set(templateId, updatedTemplate);
|
|
493
|
-
return updatedTemplate;
|
|
494
|
-
}
|
|
495
|
-
async deleteTemplate(templateId) {
|
|
496
|
-
this.templates.delete(templateId);
|
|
497
|
-
}
|
|
498
|
-
async renderTemplate(templateId, variables) {
|
|
499
|
-
const template = this.templates.get(templateId);
|
|
500
|
-
if (!template) {
|
|
501
|
-
throw new Error(`Template ${templateId} not found`);
|
|
502
|
-
}
|
|
503
|
-
const validation = VariableParser.validateVariables(template.variables, variables);
|
|
504
|
-
if (!validation.isValid) {
|
|
505
|
-
throw new Error(`Variable validation failed: ${validation.errors.join(", ")}`);
|
|
506
|
-
}
|
|
507
|
-
return VariableParser.replaceVariables(template.content, variables);
|
|
508
|
-
}
|
|
509
|
-
generateTemplateId() {
|
|
510
|
-
return `tpl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
// src/builder/template.builder.ts
|
|
515
|
-
var TemplateBuilder = class _TemplateBuilder {
|
|
516
|
-
template = {
|
|
517
|
-
variables: [],
|
|
518
|
-
buttons: [],
|
|
519
|
-
status: "DRAFT" /* DRAFT */,
|
|
520
|
-
metadata: {
|
|
521
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
522
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
523
|
-
usage: { sent: 0, delivered: 0, failed: 0 }
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
/**
|
|
527
|
-
* Set template name
|
|
528
|
-
*/
|
|
529
|
-
name(name) {
|
|
530
|
-
this.template.name = name;
|
|
531
|
-
return this;
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Set template code (provider specific)
|
|
535
|
-
*/
|
|
536
|
-
code(code) {
|
|
537
|
-
this.template.code = code;
|
|
538
|
-
return this;
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Set template content with variables
|
|
542
|
-
*/
|
|
543
|
-
content(content) {
|
|
544
|
-
this.template.content = content;
|
|
545
|
-
const extractedVariables = VariableParser.extractVariables(content);
|
|
546
|
-
const existingVariableNames = (this.template.variables || []).map((v) => v.name);
|
|
547
|
-
for (const varName of extractedVariables) {
|
|
548
|
-
if (!existingVariableNames.includes(varName)) {
|
|
549
|
-
this.variable(varName, "string", true);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
return this;
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Set template category
|
|
556
|
-
*/
|
|
557
|
-
category(category) {
|
|
558
|
-
this.template.category = category;
|
|
559
|
-
return this;
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Set template provider
|
|
563
|
-
*/
|
|
564
|
-
provider(provider) {
|
|
565
|
-
this.template.provider = provider;
|
|
566
|
-
return this;
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Set template status
|
|
570
|
-
*/
|
|
571
|
-
status(status) {
|
|
572
|
-
this.template.status = status;
|
|
573
|
-
return this;
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Add a variable definition
|
|
577
|
-
*/
|
|
578
|
-
variable(name, type = "string", required = true, options = {}) {
|
|
579
|
-
if (!this.template.variables) {
|
|
580
|
-
this.template.variables = [];
|
|
581
|
-
}
|
|
582
|
-
this.template.variables = this.template.variables.filter((v) => v.name !== name);
|
|
583
|
-
const variable = {
|
|
584
|
-
name,
|
|
585
|
-
type,
|
|
586
|
-
required,
|
|
587
|
-
...options
|
|
588
|
-
};
|
|
589
|
-
this.template.variables.push(variable);
|
|
590
|
-
return this;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Add multiple variables at once
|
|
594
|
-
*/
|
|
595
|
-
variables(variables) {
|
|
596
|
-
for (const variable of variables) {
|
|
597
|
-
this.variable(
|
|
598
|
-
variable.name,
|
|
599
|
-
variable.type || "string",
|
|
600
|
-
variable.required ?? true,
|
|
601
|
-
{
|
|
602
|
-
maxLength: variable.maxLength,
|
|
603
|
-
format: variable.format,
|
|
604
|
-
description: variable.description,
|
|
605
|
-
example: variable.example
|
|
606
|
-
}
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
return this;
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Add a web link button
|
|
613
|
-
*/
|
|
614
|
-
webLinkButton(name, mobileUrl, pcUrl) {
|
|
615
|
-
return this.button({
|
|
616
|
-
type: "WL",
|
|
617
|
-
name,
|
|
618
|
-
linkMobile: mobileUrl,
|
|
619
|
-
linkPc: pcUrl
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Add an app link button
|
|
624
|
-
*/
|
|
625
|
-
appLinkButton(name, options) {
|
|
626
|
-
return this.button({
|
|
627
|
-
type: "AL",
|
|
628
|
-
name,
|
|
629
|
-
linkIos: options.iosUrl,
|
|
630
|
-
linkAndroid: options.androidUrl,
|
|
631
|
-
schemeIos: options.iosScheme,
|
|
632
|
-
schemeAndroid: options.androidScheme
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Add a delivery tracking button
|
|
637
|
-
*/
|
|
638
|
-
deliveryButton(name) {
|
|
639
|
-
return this.button({
|
|
640
|
-
type: "DS",
|
|
641
|
-
name
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Add a bot keyword button
|
|
646
|
-
*/
|
|
647
|
-
botKeywordButton(name) {
|
|
648
|
-
return this.button({
|
|
649
|
-
type: "BK",
|
|
650
|
-
name
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Add a message delivery button
|
|
655
|
-
*/
|
|
656
|
-
messageDeliveryButton(name) {
|
|
657
|
-
return this.button({
|
|
658
|
-
type: "MD",
|
|
659
|
-
name
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Add a custom button
|
|
664
|
-
*/
|
|
665
|
-
button(button) {
|
|
666
|
-
if (!this.template.buttons) {
|
|
667
|
-
this.template.buttons = [];
|
|
668
|
-
}
|
|
669
|
-
if (this.template.buttons.length >= 5) {
|
|
670
|
-
throw new Error("Maximum 5 buttons are allowed per template");
|
|
671
|
-
}
|
|
672
|
-
this.template.buttons.push(button);
|
|
673
|
-
return this;
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Clear all buttons
|
|
677
|
-
*/
|
|
678
|
-
clearButtons() {
|
|
679
|
-
this.template.buttons = [];
|
|
680
|
-
return this;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Set template metadata
|
|
684
|
-
*/
|
|
685
|
-
metadata(metadata) {
|
|
686
|
-
this.template.metadata = {
|
|
687
|
-
...this.template.metadata,
|
|
688
|
-
...metadata,
|
|
689
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
690
|
-
};
|
|
691
|
-
return this;
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Validate the current template
|
|
695
|
-
*/
|
|
696
|
-
validate() {
|
|
697
|
-
const quickValidation = TemplateValidator.quickValidate(this.template);
|
|
698
|
-
if (!quickValidation.isValid) {
|
|
699
|
-
return quickValidation;
|
|
700
|
-
}
|
|
701
|
-
try {
|
|
702
|
-
const fullTemplate = this.build();
|
|
703
|
-
return TemplateValidator.validate(fullTemplate);
|
|
704
|
-
} catch (error) {
|
|
705
|
-
return {
|
|
706
|
-
isValid: false,
|
|
707
|
-
errors: [error instanceof Error ? error.message : "Unknown validation error"],
|
|
708
|
-
warnings: []
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Preview the template with sample variables
|
|
714
|
-
*/
|
|
715
|
-
preview(sampleVariables = {}) {
|
|
716
|
-
if (!this.template.content) {
|
|
717
|
-
throw new Error("Template content is required for preview");
|
|
718
|
-
}
|
|
719
|
-
const variables = { ...this.generateSampleVariables(), ...sampleVariables };
|
|
720
|
-
return VariableParser.replaceVariables(this.template.content, variables);
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Generate sample variables based on variable definitions
|
|
724
|
-
*/
|
|
725
|
-
generateSampleVariables() {
|
|
726
|
-
const samples = {};
|
|
727
|
-
for (const variable of this.template.variables || []) {
|
|
728
|
-
if (variable.example) {
|
|
729
|
-
samples[variable.name] = variable.example;
|
|
730
|
-
} else {
|
|
731
|
-
switch (variable.type) {
|
|
732
|
-
case "string":
|
|
733
|
-
samples[variable.name] = variable.name.includes("name") || variable.name.includes("\uC774\uB984") ? "\uD64D\uAE38\uB3D9" : variable.name.includes("code") || variable.name.includes("\uCF54\uB4DC") ? "123456" : `\uC0D8\uD50C${variable.name}`;
|
|
734
|
-
break;
|
|
735
|
-
case "number":
|
|
736
|
-
samples[variable.name] = variable.name.includes("amount") || variable.name.includes("\uAE08\uC561") ? 1e4 : variable.name.includes("count") || variable.name.includes("\uAC1C\uC218") ? 1 : 123;
|
|
737
|
-
break;
|
|
738
|
-
case "date":
|
|
739
|
-
samples[variable.name] = /* @__PURE__ */ new Date();
|
|
740
|
-
break;
|
|
741
|
-
default:
|
|
742
|
-
samples[variable.name] = `\uC0D8\uD50C\uAC12`;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
return samples;
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Clone the current builder
|
|
750
|
-
*/
|
|
751
|
-
clone() {
|
|
752
|
-
const cloned = new _TemplateBuilder();
|
|
753
|
-
cloned.template = JSON.parse(JSON.stringify(this.template));
|
|
754
|
-
return cloned;
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* Reset the builder to start fresh
|
|
758
|
-
*/
|
|
759
|
-
reset() {
|
|
760
|
-
this.template = {
|
|
761
|
-
variables: [],
|
|
762
|
-
buttons: [],
|
|
763
|
-
status: "DRAFT" /* DRAFT */,
|
|
764
|
-
metadata: {
|
|
765
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
766
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
767
|
-
usage: { sent: 0, delivered: 0, failed: 0 }
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
return this;
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Build the final template
|
|
774
|
-
*/
|
|
775
|
-
build() {
|
|
776
|
-
if (!this.template.name) throw new Error("Template name is required");
|
|
777
|
-
if (!this.template.code) throw new Error("Template code is required");
|
|
778
|
-
if (!this.template.content) throw new Error("Template content is required");
|
|
779
|
-
if (!this.template.category) throw new Error("Template category is required");
|
|
780
|
-
if (!this.template.provider) throw new Error("Template provider is required");
|
|
781
|
-
const template = {
|
|
782
|
-
id: this.template.id || this.generateTemplateId(),
|
|
783
|
-
name: this.template.name,
|
|
784
|
-
code: this.template.code,
|
|
785
|
-
content: this.template.content,
|
|
786
|
-
variables: this.template.variables || [],
|
|
787
|
-
buttons: this.template.buttons,
|
|
788
|
-
category: this.template.category,
|
|
789
|
-
status: this.template.status || "DRAFT" /* DRAFT */,
|
|
790
|
-
provider: this.template.provider,
|
|
791
|
-
metadata: {
|
|
792
|
-
...this.template.metadata,
|
|
793
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
const validation = TemplateValidator.validate(template);
|
|
797
|
-
if (!validation.isValid) {
|
|
798
|
-
throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
|
|
799
|
-
}
|
|
800
|
-
return template;
|
|
801
|
-
}
|
|
802
|
-
generateTemplateId() {
|
|
803
|
-
return `tpl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
804
|
-
}
|
|
805
|
-
};
|
|
806
|
-
var TemplateBuilders = {
|
|
807
|
-
/**
|
|
808
|
-
* Create an authentication template builder
|
|
809
|
-
*/
|
|
810
|
-
authentication(name, provider) {
|
|
811
|
-
return new TemplateBuilder().name(name).category("AUTHENTICATION" /* AUTHENTICATION */).provider(provider).variable("code", "string", true, {
|
|
812
|
-
maxLength: 10,
|
|
813
|
-
description: "Authentication code",
|
|
814
|
-
example: "123456"
|
|
815
|
-
});
|
|
816
|
-
},
|
|
817
|
-
/**
|
|
818
|
-
* Create a notification template builder
|
|
819
|
-
*/
|
|
820
|
-
notification(name, provider) {
|
|
821
|
-
return new TemplateBuilder().name(name).category("NOTIFICATION" /* NOTIFICATION */).provider(provider).variable("name", "string", true, {
|
|
822
|
-
description: "Recipient name",
|
|
823
|
-
example: "\uD64D\uAE38\uB3D9"
|
|
824
|
-
});
|
|
825
|
-
},
|
|
826
|
-
/**
|
|
827
|
-
* Create a promotion template builder
|
|
828
|
-
*/
|
|
829
|
-
promotion(name, provider) {
|
|
830
|
-
return new TemplateBuilder().name(name).category("PROMOTION" /* PROMOTION */).provider(provider).variable("name", "string", true, {
|
|
831
|
-
description: "Customer name",
|
|
832
|
-
example: "\uD64D\uAE38\uB3D9"
|
|
833
|
-
}).variable("discount", "number", false, {
|
|
834
|
-
description: "Discount percentage",
|
|
835
|
-
example: "20"
|
|
836
|
-
});
|
|
837
|
-
},
|
|
838
|
-
/**
|
|
839
|
-
* Create a payment template builder
|
|
840
|
-
*/
|
|
841
|
-
payment(name, provider) {
|
|
842
|
-
return new TemplateBuilder().name(name).category("PAYMENT" /* PAYMENT */).provider(provider).variable("name", "string", true, {
|
|
843
|
-
description: "Customer name",
|
|
844
|
-
example: "\uD64D\uAE38\uB3D9"
|
|
845
|
-
}).variable("amount", "number", true, {
|
|
846
|
-
description: "Payment amount",
|
|
847
|
-
example: "10000"
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
// src/registry/template.registry.ts
|
|
853
|
-
var import_events = require("events");
|
|
854
|
-
var TemplateRegistry = class extends import_events.EventEmitter {
|
|
855
|
-
constructor(options = {}) {
|
|
856
|
-
super();
|
|
857
|
-
this.options = options;
|
|
858
|
-
this.options = { ...this.defaultOptions, ...options };
|
|
859
|
-
if (this.options.enableAutoBackup) {
|
|
860
|
-
this.startAutoBackup();
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
templates = /* @__PURE__ */ new Map();
|
|
864
|
-
templatesByCode = /* @__PURE__ */ new Map();
|
|
865
|
-
templatesByProvider = /* @__PURE__ */ new Map();
|
|
866
|
-
templatesByCategory = /* @__PURE__ */ new Map();
|
|
867
|
-
templateHistories = /* @__PURE__ */ new Map();
|
|
868
|
-
usageStats = /* @__PURE__ */ new Map();
|
|
869
|
-
backupTimer;
|
|
870
|
-
defaultOptions = {
|
|
871
|
-
enableVersioning: true,
|
|
872
|
-
maxVersionsPerTemplate: 10,
|
|
873
|
-
enableUsageTracking: true,
|
|
874
|
-
enableAutoBackup: false,
|
|
875
|
-
backupInterval: 36e5,
|
|
876
|
-
// 1 hour
|
|
877
|
-
enableValidationOnRegister: true
|
|
878
|
-
};
|
|
879
|
-
/**
|
|
880
|
-
* Register a new template
|
|
881
|
-
*/
|
|
882
|
-
async register(template) {
|
|
883
|
-
if (this.options.enableValidationOnRegister) {
|
|
884
|
-
const validation = TemplateValidator.validate(template);
|
|
885
|
-
if (!validation.isValid) {
|
|
886
|
-
throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
const existingTemplate = this.getByCode(template.code, template.provider);
|
|
890
|
-
if (existingTemplate && existingTemplate.id !== template.id) {
|
|
891
|
-
throw new Error(`Template with code '${template.code}' already exists for provider '${template.provider}'`);
|
|
892
|
-
}
|
|
893
|
-
this.templates.set(template.id, template);
|
|
894
|
-
this.templatesByCode.set(`${template.provider}:${template.code}`, template);
|
|
895
|
-
this.updateIndexes(template);
|
|
896
|
-
if (this.options.enableVersioning) {
|
|
897
|
-
this.initializeVersionHistory(template);
|
|
898
|
-
}
|
|
899
|
-
if (this.options.enableUsageTracking) {
|
|
900
|
-
this.initializeUsageStats(template);
|
|
901
|
-
}
|
|
902
|
-
this.emit("template:registered", { template });
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Update an existing template
|
|
906
|
-
*/
|
|
907
|
-
async update(templateId, updates) {
|
|
908
|
-
const existing = this.templates.get(templateId);
|
|
909
|
-
if (!existing) {
|
|
910
|
-
throw new Error(`Template ${templateId} not found`);
|
|
911
|
-
}
|
|
912
|
-
const updatedTemplate = {
|
|
913
|
-
...existing,
|
|
914
|
-
...updates,
|
|
915
|
-
id: templateId,
|
|
916
|
-
// Ensure ID doesn't change
|
|
917
|
-
metadata: {
|
|
918
|
-
...existing.metadata,
|
|
919
|
-
...updates.metadata,
|
|
920
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
if (this.options.enableValidationOnRegister) {
|
|
924
|
-
const validation = TemplateValidator.validate(updatedTemplate);
|
|
925
|
-
if (!validation.isValid) {
|
|
926
|
-
throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
if (this.options.enableVersioning) {
|
|
930
|
-
this.addVersionToHistory(existing, updatedTemplate);
|
|
931
|
-
}
|
|
932
|
-
this.templates.set(templateId, updatedTemplate);
|
|
933
|
-
this.templatesByCode.set(`${updatedTemplate.provider}:${updatedTemplate.code}`, updatedTemplate);
|
|
934
|
-
this.updateIndexes(updatedTemplate);
|
|
935
|
-
this.emit("template:updated", {
|
|
936
|
-
oldTemplate: existing,
|
|
937
|
-
newTemplate: updatedTemplate
|
|
938
|
-
});
|
|
939
|
-
return updatedTemplate;
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Get template by ID
|
|
943
|
-
*/
|
|
944
|
-
get(templateId) {
|
|
945
|
-
return this.templates.get(templateId) || null;
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Get template by code and provider
|
|
949
|
-
*/
|
|
950
|
-
getByCode(code, provider) {
|
|
951
|
-
return this.templatesByCode.get(`${provider}:${code}`) || null;
|
|
952
|
-
}
|
|
953
|
-
/**
|
|
954
|
-
* Search templates with filters and pagination
|
|
955
|
-
*/
|
|
956
|
-
search(filters = {}, options = {}) {
|
|
957
|
-
let templates = Array.from(this.templates.values());
|
|
958
|
-
if (filters.provider) {
|
|
959
|
-
templates = templates.filter((t) => t.provider === filters.provider);
|
|
960
|
-
}
|
|
961
|
-
if (filters.category) {
|
|
962
|
-
templates = templates.filter((t) => t.category === filters.category);
|
|
963
|
-
}
|
|
964
|
-
if (filters.status) {
|
|
965
|
-
templates = templates.filter((t) => t.status === filters.status);
|
|
966
|
-
}
|
|
967
|
-
if (filters.nameContains) {
|
|
968
|
-
const searchTerm = filters.nameContains.toLowerCase();
|
|
969
|
-
templates = templates.filter((t) => t.name.toLowerCase().includes(searchTerm));
|
|
970
|
-
}
|
|
971
|
-
if (filters.codeContains) {
|
|
972
|
-
const searchTerm = filters.codeContains.toLowerCase();
|
|
973
|
-
templates = templates.filter((t) => t.code.toLowerCase().includes(searchTerm));
|
|
974
|
-
}
|
|
975
|
-
if (filters.createdAfter) {
|
|
976
|
-
templates = templates.filter((t) => t.metadata.createdAt >= filters.createdAfter);
|
|
977
|
-
}
|
|
978
|
-
if (filters.createdBefore) {
|
|
979
|
-
templates = templates.filter((t) => t.metadata.createdAt <= filters.createdBefore);
|
|
980
|
-
}
|
|
981
|
-
if (filters.usageMin !== void 0) {
|
|
982
|
-
templates = templates.filter((t) => t.metadata.usage.sent >= filters.usageMin);
|
|
983
|
-
}
|
|
984
|
-
if (filters.usageMax !== void 0) {
|
|
985
|
-
templates = templates.filter((t) => t.metadata.usage.sent <= filters.usageMax);
|
|
986
|
-
}
|
|
987
|
-
const sortBy = options.sortBy || "createdAt";
|
|
988
|
-
const sortOrder = options.sortOrder || "desc";
|
|
989
|
-
templates.sort((a, b) => {
|
|
990
|
-
let aValue, bValue;
|
|
991
|
-
switch (sortBy) {
|
|
992
|
-
case "name":
|
|
993
|
-
aValue = a.name;
|
|
994
|
-
bValue = b.name;
|
|
995
|
-
break;
|
|
996
|
-
case "code":
|
|
997
|
-
aValue = a.code;
|
|
998
|
-
bValue = b.code;
|
|
999
|
-
break;
|
|
1000
|
-
case "createdAt":
|
|
1001
|
-
aValue = a.metadata.createdAt.getTime();
|
|
1002
|
-
bValue = b.metadata.createdAt.getTime();
|
|
1003
|
-
break;
|
|
1004
|
-
case "updatedAt":
|
|
1005
|
-
aValue = a.metadata.updatedAt.getTime();
|
|
1006
|
-
bValue = b.metadata.updatedAt.getTime();
|
|
1007
|
-
break;
|
|
1008
|
-
case "usage":
|
|
1009
|
-
aValue = a.metadata.usage.sent;
|
|
1010
|
-
bValue = b.metadata.usage.sent;
|
|
1011
|
-
break;
|
|
1012
|
-
default:
|
|
1013
|
-
aValue = a.metadata.createdAt.getTime();
|
|
1014
|
-
bValue = b.metadata.createdAt.getTime();
|
|
1015
|
-
}
|
|
1016
|
-
if (sortOrder === "asc") {
|
|
1017
|
-
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
|
|
1018
|
-
} else {
|
|
1019
|
-
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
|
|
1020
|
-
}
|
|
1021
|
-
});
|
|
1022
|
-
const page = options.page || 1;
|
|
1023
|
-
const limit = options.limit || 20;
|
|
1024
|
-
const start = (page - 1) * limit;
|
|
1025
|
-
const end = start + limit;
|
|
1026
|
-
const paginatedTemplates = templates.slice(start, end);
|
|
1027
|
-
return {
|
|
1028
|
-
templates: paginatedTemplates,
|
|
1029
|
-
total: templates.length,
|
|
1030
|
-
page,
|
|
1031
|
-
limit,
|
|
1032
|
-
hasMore: end < templates.length
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* Get templates by provider
|
|
1037
|
-
*/
|
|
1038
|
-
getByProvider(provider) {
|
|
1039
|
-
const templateIds = this.templatesByProvider.get(provider) || /* @__PURE__ */ new Set();
|
|
1040
|
-
return Array.from(templateIds).map((id) => this.templates.get(id)).filter((template) => template !== void 0);
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Get templates by category
|
|
1044
|
-
*/
|
|
1045
|
-
getByCategory(category) {
|
|
1046
|
-
const templateIds = this.templatesByCategory.get(category) || /* @__PURE__ */ new Set();
|
|
1047
|
-
return Array.from(templateIds).map((id) => this.templates.get(id)).filter((template) => template !== void 0);
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Delete template
|
|
1051
|
-
*/
|
|
1052
|
-
async delete(templateId) {
|
|
1053
|
-
const template = this.templates.get(templateId);
|
|
1054
|
-
if (!template) {
|
|
1055
|
-
return false;
|
|
1056
|
-
}
|
|
1057
|
-
this.templates.delete(templateId);
|
|
1058
|
-
this.templatesByCode.delete(`${template.provider}:${template.code}`);
|
|
1059
|
-
const providerSet = this.templatesByProvider.get(template.provider);
|
|
1060
|
-
if (providerSet) {
|
|
1061
|
-
providerSet.delete(templateId);
|
|
1062
|
-
if (providerSet.size === 0) {
|
|
1063
|
-
this.templatesByProvider.delete(template.provider);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
const categorySet = this.templatesByCategory.get(template.category);
|
|
1067
|
-
if (categorySet) {
|
|
1068
|
-
categorySet.delete(templateId);
|
|
1069
|
-
if (categorySet.size === 0) {
|
|
1070
|
-
this.templatesByCategory.delete(template.category);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
this.templateHistories.delete(templateId);
|
|
1074
|
-
this.usageStats.delete(templateId);
|
|
1075
|
-
this.emit("template:deleted", { template });
|
|
1076
|
-
return true;
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1079
|
-
* Get template version history
|
|
1080
|
-
*/
|
|
1081
|
-
getHistory(templateId) {
|
|
1082
|
-
return this.templateHistories.get(templateId) || null;
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Get specific template version
|
|
1086
|
-
*/
|
|
1087
|
-
getVersion(templateId, version) {
|
|
1088
|
-
const history = this.templateHistories.get(templateId);
|
|
1089
|
-
if (!history) return null;
|
|
1090
|
-
const versionEntry = history.versions.find((v) => v.version === version);
|
|
1091
|
-
return versionEntry ? versionEntry.template : null;
|
|
1092
|
-
}
|
|
1093
|
-
/**
|
|
1094
|
-
* Restore template to a specific version
|
|
1095
|
-
*/
|
|
1096
|
-
async restoreVersion(templateId, version) {
|
|
1097
|
-
const versionTemplate = this.getVersion(templateId, version);
|
|
1098
|
-
if (!versionTemplate) {
|
|
1099
|
-
throw new Error(`Version ${version} not found for template ${templateId}`);
|
|
1100
|
-
}
|
|
1101
|
-
return this.update(templateId, {
|
|
1102
|
-
...versionTemplate,
|
|
1103
|
-
metadata: {
|
|
1104
|
-
...versionTemplate.metadata,
|
|
1105
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Get template usage statistics
|
|
1111
|
-
*/
|
|
1112
|
-
getUsageStats(templateId) {
|
|
1113
|
-
return this.usageStats.get(templateId) || null;
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Update template usage statistics
|
|
1117
|
-
*/
|
|
1118
|
-
updateUsageStats(templateId, stats) {
|
|
1119
|
-
const template = this.templates.get(templateId);
|
|
1120
|
-
if (!template) return;
|
|
1121
|
-
if (stats.sent) template.metadata.usage.sent += stats.sent;
|
|
1122
|
-
if (stats.delivered) template.metadata.usage.delivered += stats.delivered;
|
|
1123
|
-
if (stats.failed) template.metadata.usage.failed += stats.failed;
|
|
1124
|
-
if (this.options.enableUsageTracking) {
|
|
1125
|
-
const usageStats = this.usageStats.get(templateId);
|
|
1126
|
-
if (usageStats) {
|
|
1127
|
-
if (stats.sent) usageStats.totalSent += stats.sent;
|
|
1128
|
-
if (stats.delivered) usageStats.totalDelivered += stats.delivered;
|
|
1129
|
-
if (stats.failed) usageStats.totalFailed += stats.failed;
|
|
1130
|
-
usageStats.deliveryRate = usageStats.totalSent > 0 ? usageStats.totalDelivered / usageStats.totalSent * 100 : 0;
|
|
1131
|
-
usageStats.failureRate = usageStats.totalSent > 0 ? usageStats.totalFailed / usageStats.totalSent * 100 : 0;
|
|
1132
|
-
usageStats.lastUsed = /* @__PURE__ */ new Date();
|
|
1133
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1134
|
-
let todayStats = usageStats.usageByDay.find((d) => d.date === today);
|
|
1135
|
-
if (!todayStats) {
|
|
1136
|
-
todayStats = { date: today, sent: 0, delivered: 0, failed: 0 };
|
|
1137
|
-
usageStats.usageByDay.push(todayStats);
|
|
1138
|
-
usageStats.usageByDay = usageStats.usageByDay.sort((a, b) => b.date.localeCompare(a.date)).slice(0, 30);
|
|
1139
|
-
}
|
|
1140
|
-
if (stats.sent) todayStats.sent += stats.sent;
|
|
1141
|
-
if (stats.delivered) todayStats.delivered += stats.delivered;
|
|
1142
|
-
if (stats.failed) todayStats.failed += stats.failed;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
this.emit("usage:updated", { templateId, stats });
|
|
1146
|
-
}
|
|
1147
|
-
/**
|
|
1148
|
-
* Get registry statistics
|
|
1149
|
-
*/
|
|
1150
|
-
getStats() {
|
|
1151
|
-
const templates = Array.from(this.templates.values());
|
|
1152
|
-
const byProvider = {};
|
|
1153
|
-
const byCategory = {};
|
|
1154
|
-
const byStatus = {};
|
|
1155
|
-
for (const template of templates) {
|
|
1156
|
-
byProvider[template.provider] = (byProvider[template.provider] || 0) + 1;
|
|
1157
|
-
byCategory[template.category] = (byCategory[template.category] || 0) + 1;
|
|
1158
|
-
byStatus[template.status] = (byStatus[template.status] || 0) + 1;
|
|
1159
|
-
}
|
|
1160
|
-
return {
|
|
1161
|
-
totalTemplates: templates.length,
|
|
1162
|
-
byProvider,
|
|
1163
|
-
byCategory,
|
|
1164
|
-
byStatus
|
|
1165
|
-
};
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Export templates to JSON
|
|
1169
|
-
*/
|
|
1170
|
-
export(filters) {
|
|
1171
|
-
const result = this.search(filters, { limit: 1e4 });
|
|
1172
|
-
return JSON.stringify({
|
|
1173
|
-
templates: result.templates,
|
|
1174
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1175
|
-
total: result.total
|
|
1176
|
-
}, null, 2);
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* Import templates from JSON
|
|
1180
|
-
*/
|
|
1181
|
-
async import(jsonData, options = {}) {
|
|
1182
|
-
const result = { imported: 0, skipped: 0, errors: [] };
|
|
1183
|
-
try {
|
|
1184
|
-
const data = JSON.parse(jsonData);
|
|
1185
|
-
const templates = data.templates || [];
|
|
1186
|
-
for (const templateData of templates) {
|
|
1187
|
-
try {
|
|
1188
|
-
const existingTemplate = this.get(templateData.id);
|
|
1189
|
-
if (existingTemplate && !options.overwrite) {
|
|
1190
|
-
result.skipped++;
|
|
1191
|
-
continue;
|
|
1192
|
-
}
|
|
1193
|
-
await this.register(templateData);
|
|
1194
|
-
result.imported++;
|
|
1195
|
-
} catch (error) {
|
|
1196
|
-
result.errors.push(`Template ${templateData.id}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
result.errors.push(`Invalid JSON format: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1201
|
-
}
|
|
1202
|
-
return result;
|
|
1203
|
-
}
|
|
1204
|
-
/**
|
|
1205
|
-
* Clear all templates (use with caution!)
|
|
1206
|
-
*/
|
|
1207
|
-
clear() {
|
|
1208
|
-
this.templates.clear();
|
|
1209
|
-
this.templatesByCode.clear();
|
|
1210
|
-
this.templatesByProvider.clear();
|
|
1211
|
-
this.templatesByCategory.clear();
|
|
1212
|
-
this.templateHistories.clear();
|
|
1213
|
-
this.usageStats.clear();
|
|
1214
|
-
this.emit("registry:cleared");
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Stop the registry and cleanup
|
|
1218
|
-
*/
|
|
1219
|
-
destroy() {
|
|
1220
|
-
if (this.backupTimer) {
|
|
1221
|
-
clearInterval(this.backupTimer);
|
|
1222
|
-
this.backupTimer = void 0;
|
|
1223
|
-
}
|
|
1224
|
-
this.removeAllListeners();
|
|
1225
|
-
this.emit("registry:destroyed");
|
|
1226
|
-
}
|
|
1227
|
-
updateIndexes(template) {
|
|
1228
|
-
if (!this.templatesByProvider.has(template.provider)) {
|
|
1229
|
-
this.templatesByProvider.set(template.provider, /* @__PURE__ */ new Set());
|
|
1230
|
-
}
|
|
1231
|
-
this.templatesByProvider.get(template.provider).add(template.id);
|
|
1232
|
-
if (!this.templatesByCategory.has(template.category)) {
|
|
1233
|
-
this.templatesByCategory.set(template.category, /* @__PURE__ */ new Set());
|
|
1234
|
-
}
|
|
1235
|
-
this.templatesByCategory.get(template.category).add(template.id);
|
|
1236
|
-
}
|
|
1237
|
-
initializeVersionHistory(template) {
|
|
1238
|
-
const history = {
|
|
1239
|
-
templateId: template.id,
|
|
1240
|
-
versions: [{
|
|
1241
|
-
version: 1,
|
|
1242
|
-
template: JSON.parse(JSON.stringify(template)),
|
|
1243
|
-
changes: ["Initial version"],
|
|
1244
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
1245
|
-
}],
|
|
1246
|
-
currentVersion: 1
|
|
1247
|
-
};
|
|
1248
|
-
this.templateHistories.set(template.id, history);
|
|
1249
|
-
}
|
|
1250
|
-
addVersionToHistory(oldTemplate, newTemplate) {
|
|
1251
|
-
const history = this.templateHistories.get(newTemplate.id);
|
|
1252
|
-
if (!history) return;
|
|
1253
|
-
const changes = [];
|
|
1254
|
-
if (oldTemplate.name !== newTemplate.name) changes.push("Name changed");
|
|
1255
|
-
if (oldTemplate.content !== newTemplate.content) changes.push("Content changed");
|
|
1256
|
-
if (oldTemplate.category !== newTemplate.category) changes.push("Category changed");
|
|
1257
|
-
if (oldTemplate.status !== newTemplate.status) changes.push("Status changed");
|
|
1258
|
-
if (JSON.stringify(oldTemplate.variables) !== JSON.stringify(newTemplate.variables)) {
|
|
1259
|
-
changes.push("Variables changed");
|
|
1260
|
-
}
|
|
1261
|
-
if (JSON.stringify(oldTemplate.buttons) !== JSON.stringify(newTemplate.buttons)) {
|
|
1262
|
-
changes.push("Buttons changed");
|
|
1263
|
-
}
|
|
1264
|
-
if (changes.length === 0) return;
|
|
1265
|
-
const newVersion = {
|
|
1266
|
-
version: history.currentVersion + 1,
|
|
1267
|
-
template: JSON.parse(JSON.stringify(newTemplate)),
|
|
1268
|
-
changes,
|
|
1269
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
1270
|
-
};
|
|
1271
|
-
history.versions.push(newVersion);
|
|
1272
|
-
history.currentVersion = newVersion.version;
|
|
1273
|
-
if (history.versions.length > this.options.maxVersionsPerTemplate) {
|
|
1274
|
-
history.versions = history.versions.slice(-this.options.maxVersionsPerTemplate);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
initializeUsageStats(template) {
|
|
1278
|
-
const stats = {
|
|
1279
|
-
templateId: template.id,
|
|
1280
|
-
totalSent: template.metadata.usage.sent,
|
|
1281
|
-
totalDelivered: template.metadata.usage.delivered,
|
|
1282
|
-
totalFailed: template.metadata.usage.failed,
|
|
1283
|
-
deliveryRate: 0,
|
|
1284
|
-
failureRate: 0,
|
|
1285
|
-
usageByDay: []
|
|
1286
|
-
};
|
|
1287
|
-
if (stats.totalSent > 0) {
|
|
1288
|
-
stats.deliveryRate = stats.totalDelivered / stats.totalSent * 100;
|
|
1289
|
-
stats.failureRate = stats.totalFailed / stats.totalSent * 100;
|
|
1290
|
-
}
|
|
1291
|
-
this.usageStats.set(template.id, stats);
|
|
1292
|
-
}
|
|
1293
|
-
startAutoBackup() {
|
|
1294
|
-
this.backupTimer = setInterval(() => {
|
|
1295
|
-
this.emit("backup:requested", {
|
|
1296
|
-
data: this.export(),
|
|
1297
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1298
|
-
});
|
|
1299
|
-
}, this.options.backupInterval);
|
|
1300
|
-
}
|
|
1301
|
-
};
|
|
1302
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1303
|
-
0 && (module.exports = {
|
|
1304
|
-
ButtonParser,
|
|
1305
|
-
TemplateBuilder,
|
|
1306
|
-
TemplateBuilders,
|
|
1307
|
-
TemplateCategory,
|
|
1308
|
-
TemplateRegistry,
|
|
1309
|
-
TemplateService,
|
|
1310
|
-
TemplateStatus,
|
|
1311
|
-
TemplateType,
|
|
1312
|
-
TemplateValidator,
|
|
1313
|
-
VariableParser
|
|
1314
|
-
});
|
|
1315
|
-
//# sourceMappingURL=index.cjs.map
|