@antelopejs/dms-mailing 0.6.1 → 0.7.1
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 +8 -7
- package/dist/constants.d.ts +1 -2
- package/dist/constants.js +2 -3
- package/dist/constants.js.map +1 -1
- package/dist/data/templates-table.d.ts +4 -1
- package/dist/data/templates-table.js +55 -20
- package/dist/data/templates-table.js.map +1 -1
- package/dist/data-types/categories-type.d.ts +5 -5
- package/dist/db/models/index.d.ts +0 -1
- package/dist/db/models/index.js +0 -1
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/send-events.model.js +2 -3
- package/dist/db/models/send-events.model.js.map +1 -1
- package/dist/db/models/sends.model.js +2 -3
- package/dist/db/models/sends.model.js.map +1 -1
- package/dist/db/models/templates.model.d.ts +10 -1
- package/dist/db/models/templates.model.js +29 -10
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/db/tables/index.d.ts +0 -1
- package/dist/db/tables/index.js +0 -1
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/settings.table.d.ts +0 -1
- package/dist/db/tables/settings.table.js +0 -4
- package/dist/db/tables/settings.table.js.map +1 -1
- package/dist/db/tables/templates.table.d.ts +7 -4
- package/dist/db/tables/templates.table.js +4 -14
- package/dist/db/tables/templates.table.js.map +1 -1
- package/dist/engine/formats.d.ts +13 -0
- package/dist/engine/formats.js +85 -0
- package/dist/engine/formats.js.map +1 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +1 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/resolve.d.ts +2 -1
- package/dist/engine/resolve.js +14 -7
- package/dist/engine/resolve.js.map +1 -1
- package/dist/engine/tokens.d.ts +2 -1
- package/dist/engine/tokens.js +3 -5
- package/dist/engine/tokens.js.map +1 -1
- package/dist/frontend-module.d.ts +2 -0
- package/dist/frontend-module.js +24 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/tenant-export.js +4 -1
- package/dist/hooks/tenant-export.js.map +1 -1
- package/dist/implementations/dms-mailing/index.d.ts +3 -3
- package/dist/implementations/dms-mailing/index.js +8 -7
- package/dist/implementations/dms-mailing/index.js.map +1 -1
- package/dist/index.js +2 -20
- package/dist/index.js.map +1 -1
- package/dist/pages/overview.d.ts +1 -1
- package/dist/pages/overview.js +11 -13
- package/dist/pages/overview.js.map +1 -1
- package/dist/pages/sends.d.ts +1 -1
- package/dist/pages/sends.js +16 -8
- package/dist/pages/sends.js.map +1 -1
- package/dist/pages/settings/form.js +8 -0
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/templates.js +3 -8
- package/dist/pages/templates.js.map +1 -1
- package/dist/pages/toolbar.d.ts +12 -0
- package/dist/pages/toolbar.js +41 -0
- package/dist/pages/toolbar.js.map +1 -0
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +8 -8
- package/dist/realtime.js.map +1 -1
- package/dist/routes/metrics.d.ts +9 -2
- package/dist/routes/metrics.js +90 -35
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/period.d.ts +1 -0
- package/dist/routes/period.js +19 -0
- package/dist/routes/period.js.map +1 -1
- package/dist/routes/sends.js +6 -3
- package/dist/routes/sends.js.map +1 -1
- package/dist/routes/settings.js +6 -7
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/templates.d.ts +8 -4
- package/dist/routes/templates.js +41 -20
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/categories.d.ts +2 -1
- package/dist/services/categories.js +9 -0
- package/dist/services/categories.js.map +1 -1
- package/dist/services/declarations.js +42 -184
- package/dist/services/declarations.js.map +1 -1
- package/dist/services/events.js +1 -1
- package/dist/services/events.js.map +1 -1
- package/dist/services/localized-text.d.ts +7 -0
- package/dist/services/localized-text.js +28 -0
- package/dist/services/localized-text.js.map +1 -0
- package/dist/services/send-import.js +0 -2
- package/dist/services/send-import.js.map +1 -1
- package/dist/services/send-stat-items.js +34 -12
- package/dist/services/send-stat-items.js.map +1 -1
- package/dist/services/send-stats.d.ts +7 -0
- package/dist/services/send-stats.js +17 -5
- package/dist/services/send-stats.js.map +1 -1
- package/dist/services/send.d.ts +2 -0
- package/dist/services/send.js +11 -12
- package/dist/services/send.js.map +1 -1
- package/dist/services/sender-declaration.d.ts +2 -0
- package/dist/services/sender-declaration.js +18 -0
- package/dist/services/sender-declaration.js.map +1 -0
- package/dist/services/settings.d.ts +1 -1
- package/dist/services/settings.js +1 -5
- package/dist/services/settings.js.map +1 -1
- package/dist/services/starters.d.ts +2 -2
- package/dist/services/starters.js +1 -10
- package/dist/services/starters.js.map +1 -1
- package/dist/services/templates.d.ts +13 -7
- package/dist/services/templates.js +72 -33
- package/dist/services/templates.js.map +1 -1
- package/dist/services/webhook-status.d.ts +2 -1
- package/dist/services/webhook-status.js +6 -5
- package/dist/services/webhook-status.js.map +1 -1
- package/dist/types/settings.d.ts +8 -0
- package/dist/types/settings.js +33 -6
- package/dist/types/settings.js.map +1 -1
- package/dist/validation/declarations.schema.d.ts +25 -7
- package/dist/validation/declarations.schema.js +16 -3
- package/dist/validation/declarations.schema.js.map +1 -1
- package/dist/validation/settings.schema.d.ts +41 -41
- package/dist/validation/settings.schema.js +30 -6
- package/dist/validation/settings.schema.js.map +1 -1
- package/dist/validation/templates.schema.d.ts +13 -0
- package/dist/validation/templates.schema.js +6 -1
- package/dist/validation/templates.schema.js.map +1 -1
- package/frontend-vue/app/components/CategoriesInput.vue +15 -13
- package/frontend-vue/app/components/FunnelCard.vue +46 -3
- package/frontend-vue/app/components/NewTemplateModal.vue +4 -2
- package/frontend-vue/app/components/OverviewGate.vue +0 -8
- package/frontend-vue/app/components/ProviderPill.vue +50 -0
- package/frontend-vue/app/components/SendDrawer.vue +206 -183
- package/frontend-vue/app/components/StarterGallery.vue +1 -1
- package/frontend-vue/app/components/TemplateCard.vue +4 -1
- package/frontend-vue/app/components/TemplateDrawer.vue +3 -5
- package/frontend-vue/app/components/TemplateGallery.vue +57 -17
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +41 -11
- package/frontend-vue/app/components/editor/EditorTemplatePane.vue +1 -3
- package/frontend-vue/app/components/editor/EditorTitle.vue +1 -7
- package/frontend-vue/app/composables/useLocalizedDefaults.ts +5 -7
- package/frontend-vue/app/composables/useMailingApi.ts +3 -3
- package/frontend-vue/app/composables/useMailingReads.ts +2 -2
- package/frontend-vue/app/composables/useTemplateCategories.ts +8 -7
- package/frontend-vue/app/types/mailing.ts +18 -0
- package/frontend-vue/app/utils/categories.ts +58 -49
- package/frontend-vue/app/utils/funnel.ts +25 -0
- package/frontend-vue/app/utils/period.ts +0 -7
- package/frontend-vue/app/utils/starters.ts +3 -1
- package/frontend-vue/app/utils/tokens.ts +6 -1
- package/frontend-vue/i18n/locales/mailing-en-GB.json +19 -28
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +19 -28
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/categories-input.test.ts +4 -10
- package/frontend-vue/tests/categories.test.ts +55 -17
- package/frontend-vue/tests/conditions.test.ts +1 -4
- package/frontend-vue/tests/funnel-card.test.ts +27 -0
- package/frontend-vue/tests/funnel.test.ts +25 -0
- package/frontend-vue/tests/localized-defaults.test.ts +0 -29
- package/frontend-vue/tests/new-template-modal.test.ts +1 -1
- package/frontend-vue/tests/overview-gate.test.ts +1 -24
- package/frontend-vue/tests/page-reads.test.ts +9 -25
- package/frontend-vue/tests/period.test.ts +0 -12
- package/frontend-vue/tests/replay-toast.test.ts +2 -1
- package/frontend-vue/tests/send-drawer.test.ts +123 -0
- package/frontend-vue/tests/template-drawer.test.ts +1 -1
- package/frontend-vue/tests/template-gallery.test.ts +90 -3
- package/frontend-vue/tests/template-preview-frame.test.ts +92 -0
- package/package.json +8 -7
- package/skills/dms-mailing/SKILL.md +25 -18
- package/src/constants.ts +2 -2
- package/src/data/sends-table.ts +1 -1
- package/src/data/templates-table.ts +125 -33
- package/src/db/models/index.ts +0 -1
- package/src/db/models/send-events.model.ts +2 -2
- package/src/db/models/sends.model.ts +7 -8
- package/src/db/models/templates.model.ts +64 -16
- package/src/db/tables/index.ts +0 -1
- package/src/db/tables/settings.table.ts +0 -8
- package/src/db/tables/templates.table.ts +12 -17
- package/src/engine/formats.ts +124 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/resolve.ts +18 -7
- package/src/engine/tokens.ts +7 -5
- package/src/frontend-module.ts +36 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/tenant-export.ts +8 -1
- package/src/implementations/dms-mailing/index.ts +5 -16
- package/src/index.ts +3 -29
- package/src/pages/overview.ts +20 -16
- package/src/pages/sends.ts +27 -11
- package/src/pages/settings/form.ts +16 -0
- package/src/pages/templates.ts +4 -8
- package/src/pages/toolbar.ts +77 -0
- package/src/realtime.ts +17 -8
- package/src/routes/metrics.ts +158 -45
- package/src/routes/period.ts +32 -0
- package/src/routes/sends.ts +13 -4
- package/src/routes/settings.ts +11 -8
- package/src/routes/templates.ts +81 -37
- package/src/services/categories.ts +14 -1
- package/src/services/declarations.ts +64 -317
- package/src/services/events.ts +1 -1
- package/src/services/localized-text.ts +53 -0
- package/src/services/send-import.ts +2 -2
- package/src/services/send-stat-items.ts +60 -13
- package/src/services/send-stats.ts +37 -5
- package/src/services/send.ts +19 -20
- package/src/services/sender-declaration.ts +27 -0
- package/src/services/settings.ts +3 -10
- package/src/services/starters.ts +3 -15
- package/src/services/templates.ts +109 -63
- package/src/services/webhook-status.ts +3 -3
- package/src/types/settings.ts +55 -5
- package/src/validation/declarations.schema.ts +29 -3
- package/src/validation/settings.schema.ts +56 -14
- package/src/validation/templates.schema.ts +10 -0
- package/dist/db/models/template-slugs.model.d.ts +0 -27
- package/dist/db/models/template-slugs.model.js +0 -43
- package/dist/db/models/template-slugs.model.js.map +0 -1
- package/dist/db/tables/template-slugs.table.d.ts +0 -7
- package/dist/db/tables/template-slugs.table.js +0 -35
- package/dist/db/tables/template-slugs.table.js.map +0 -1
- package/dist/hooks/database-ready.d.ts +0 -7
- package/dist/hooks/database-ready.js +0 -26
- package/dist/hooks/database-ready.js.map +0 -1
- package/dist/services/fingerprint.d.ts +0 -1
- package/dist/services/fingerprint.js +0 -20
- package/dist/services/fingerprint.js.map +0 -1
- package/frontend-vue/app/components/PageHeader.vue +0 -94
- package/frontend-vue/app/composables/useOverviewSetup.ts +0 -21
- package/src/db/models/template-slugs.model.ts +0 -61
- package/src/db/tables/template-slugs.table.ts +0 -29
- package/src/hooks/database-ready.ts +0 -47
- package/src/services/fingerprint.ts +0 -22
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { HTTPResult } from "@antelopejs/interface-api";
|
|
3
|
+
import { IsDuplicateKeyError } from "@antelopejs/interface-database";
|
|
3
4
|
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
4
5
|
import {
|
|
5
|
-
DEFAULT_FALLBACK_LOCALE,
|
|
6
6
|
HTTP_CONFLICT,
|
|
7
7
|
HTTP_NOT_FOUND,
|
|
8
8
|
LOCALE_LIST_SEPARATOR,
|
|
@@ -10,10 +10,9 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
type MailingTemplate,
|
|
12
12
|
type MailingTemplateVersion,
|
|
13
|
+
type TemplateFields,
|
|
13
14
|
TemplateModel,
|
|
14
|
-
TemplateSlugModel,
|
|
15
15
|
TemplateVersionModel,
|
|
16
|
-
templateSlugIdOf,
|
|
17
16
|
} from "../db";
|
|
18
17
|
import { diffContents, diffVariables, type TemplateChange } from "../engine";
|
|
19
18
|
import {
|
|
@@ -27,6 +26,7 @@ import type {
|
|
|
27
26
|
TemplateContent,
|
|
28
27
|
VariableDefinition,
|
|
29
28
|
} from "../types";
|
|
29
|
+
import { DEFAULT_FALLBACK_LOCALE } from "../types";
|
|
30
30
|
|
|
31
31
|
export interface TemplateAuthor {
|
|
32
32
|
name?: string;
|
|
@@ -44,6 +44,7 @@ const STALE_DRAFT: TemplateAttentionReason = "stale_draft";
|
|
|
44
44
|
const VERSION_NOT_FOUND = "$dms_mailing.errors.version_not_found";
|
|
45
45
|
const TEMPLATE_NOT_FOUND = "$dms_mailing.errors.template_not_found";
|
|
46
46
|
const DUPLICATE_SLUG = "$dms_mailing.errors.duplicate_slug";
|
|
47
|
+
const SLUG_FIELD = "slug";
|
|
47
48
|
|
|
48
49
|
/** The template `id` names; 404 when there is none. */
|
|
49
50
|
export async function requireTemplate(
|
|
@@ -56,49 +57,18 @@ export async function requireTemplate(
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* Runs a write that gives a template its slug, refused with a 409 when
|
|
61
|
+
* another template of the tenant holds it: the slug's unique index settles
|
|
62
|
+
* concurrent creations, so nothing reads the table before writing.
|
|
62
63
|
*/
|
|
63
|
-
async function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (await claims.insertClaim(id, slug, templateId)) return true;
|
|
71
|
-
const holder = await claims.get(id);
|
|
72
|
-
if (holder?.templateId === templateId) return true;
|
|
73
|
-
if (holder) {
|
|
74
|
-
const templates = GetModel(TemplateModel, tenantId);
|
|
75
|
-
if (await templates.get(holder.templateId)) return false;
|
|
76
|
-
await claims.dropClaimOf(id, holder.templateId);
|
|
64
|
+
export async function refuseTakenSlug<T>(write: Promise<T>): Promise<T> {
|
|
65
|
+
try {
|
|
66
|
+
return await write;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (IsDuplicateKeyError(error) && error.fields.includes(SLUG_FIELD))
|
|
69
|
+
throw new HTTPResult(HTTP_CONFLICT, DUPLICATE_SLUG);
|
|
70
|
+
throw error;
|
|
77
71
|
}
|
|
78
|
-
return claims.insertClaim(id, slug, templateId);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Keeps the template just inserted if it got its slug, or removes it again
|
|
83
|
-
* and refuses it like any taken slug: two creations racing past the free-slug
|
|
84
|
-
* check both insert, and only the claim's primary key tells them apart.
|
|
85
|
-
*/
|
|
86
|
-
export async function assertSlugClaimed(
|
|
87
|
-
tenantId: string,
|
|
88
|
-
slug: string,
|
|
89
|
-
templateId: string,
|
|
90
|
-
): Promise<void> {
|
|
91
|
-
if (await claimTemplateSlug(tenantId, slug, templateId)) return;
|
|
92
|
-
await GetModel(TemplateModel, tenantId).delete(templateId);
|
|
93
|
-
throw new HTTPResult(HTTP_CONFLICT, DUPLICATE_SLUG);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Frees the slugs of deleted templates for new ones. */
|
|
97
|
-
export async function releaseTemplateSlugs(
|
|
98
|
-
tenantId: string,
|
|
99
|
-
templateIds: string[],
|
|
100
|
-
): Promise<void> {
|
|
101
|
-
await GetModel(TemplateSlugModel, tenantId).releaseTemplates(templateIds);
|
|
102
72
|
}
|
|
103
73
|
|
|
104
74
|
/** The content customers receive: the last published version. */
|
|
@@ -153,6 +123,25 @@ export interface TemplateDraft {
|
|
|
153
123
|
variables: VariableDefinition[];
|
|
154
124
|
}
|
|
155
125
|
|
|
126
|
+
/** A snapshot and the version it belongs to; 0 for a draft. */
|
|
127
|
+
export interface VersionedSnapshot extends TemplateDraft {
|
|
128
|
+
version: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** What a publish would ship: the draft's content and declarations. */
|
|
132
|
+
export const workingSnapshot = (template: MailingTemplate): TemplateDraft => ({
|
|
133
|
+
content: workingContent(template),
|
|
134
|
+
variables: workingVariables(template),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/** What customers receive: the published content and declarations. */
|
|
138
|
+
export const publishedSnapshot = (
|
|
139
|
+
template: MailingTemplate,
|
|
140
|
+
): TemplateDraft => ({
|
|
141
|
+
content: parseContent(template),
|
|
142
|
+
variables: parseVariables(template),
|
|
143
|
+
});
|
|
144
|
+
|
|
156
145
|
/**
|
|
157
146
|
* What `draft` would change for customers and for the callers' contract. A
|
|
158
147
|
* template never published has nothing to compare against: every locale and
|
|
@@ -240,7 +229,7 @@ export function isStaleRevision(
|
|
|
240
229
|
function draftWriteFields(
|
|
241
230
|
template: MailingTemplate,
|
|
242
231
|
author: string,
|
|
243
|
-
):
|
|
232
|
+
): TemplateFields {
|
|
244
233
|
return {
|
|
245
234
|
draftRevision: template.draftRevision + 1,
|
|
246
235
|
draftUpdatedAt: new Date(),
|
|
@@ -274,7 +263,7 @@ export async function saveDraft(
|
|
|
274
263
|
const locales = isPublished(template) ? {} : { locales: localesOf(content) };
|
|
275
264
|
const isClean = isPublishedDraft(template, draft);
|
|
276
265
|
const fields = draftWriteFields(template, author);
|
|
277
|
-
await GetModel(TemplateModel, tenantId).
|
|
266
|
+
await GetModel(TemplateModel, tenantId).updateFields(template._id, {
|
|
278
267
|
json_draft: isClean ? "" : JSON.stringify(content),
|
|
279
268
|
json_variables_draft: isClean ? "" : JSON.stringify(variables),
|
|
280
269
|
isDraftPending: !isClean,
|
|
@@ -290,7 +279,7 @@ export async function discardDraft(
|
|
|
290
279
|
template: MailingTemplate,
|
|
291
280
|
author: string,
|
|
292
281
|
): Promise<void> {
|
|
293
|
-
await GetModel(TemplateModel, tenantId).
|
|
282
|
+
await GetModel(TemplateModel, tenantId).updateFields(template._id, {
|
|
294
283
|
json_draft: "",
|
|
295
284
|
json_variables_draft: "",
|
|
296
285
|
isDraftPending: false,
|
|
@@ -353,7 +342,7 @@ export async function publishSnapshot(
|
|
|
353
342
|
template: MailingTemplate,
|
|
354
343
|
draft: TemplateDraft,
|
|
355
344
|
author: string,
|
|
356
|
-
metadata:
|
|
345
|
+
metadata: TemplateFields = {},
|
|
357
346
|
): Promise<PublishOutcome | undefined> {
|
|
358
347
|
const previous = publishedVersionOf(template);
|
|
359
348
|
const changes = changesOf(template, draft);
|
|
@@ -361,7 +350,7 @@ export async function publishSnapshot(
|
|
|
361
350
|
const version = isUnchanged ? previous : previous + 1;
|
|
362
351
|
const versions = GetModel(TemplateVersionModel, tenantId);
|
|
363
352
|
await versions.materialize(template);
|
|
364
|
-
const shipped:
|
|
353
|
+
const shipped: TemplateFields = isUnchanged
|
|
365
354
|
? {}
|
|
366
355
|
: {
|
|
367
356
|
json_content: JSON.stringify(draft.content),
|
|
@@ -381,7 +370,7 @@ export async function publishSnapshot(
|
|
|
381
370
|
json_changes: JSON.stringify(changes),
|
|
382
371
|
}),
|
|
383
372
|
};
|
|
384
|
-
const patch:
|
|
373
|
+
const patch: TemplateFields = {
|
|
385
374
|
...metadata,
|
|
386
375
|
...shipped,
|
|
387
376
|
...history,
|
|
@@ -431,19 +420,34 @@ export async function publishTemplate(
|
|
|
431
420
|
return outcome;
|
|
432
421
|
}
|
|
433
422
|
|
|
434
|
-
/**
|
|
435
|
-
|
|
423
|
+
/**
|
|
424
|
+
* The content and declarations a send of `version` carried: the draft's for
|
|
425
|
+
* a test of one.
|
|
426
|
+
*/
|
|
427
|
+
export async function snapshotOfVersion(
|
|
436
428
|
tenantId: string,
|
|
437
429
|
template: MailingTemplate,
|
|
438
430
|
version: number,
|
|
439
|
-
): Promise<
|
|
440
|
-
if (!version) return
|
|
431
|
+
): Promise<TemplateDraft> {
|
|
432
|
+
if (!version) return workingSnapshot(template);
|
|
441
433
|
const row = await GetModel(TemplateVersionModel, tenantId).getVersion(
|
|
442
434
|
template._id,
|
|
443
435
|
version,
|
|
444
436
|
);
|
|
445
437
|
if (!row) throw new HTTPResult(HTTP_NOT_FOUND, VERSION_NOT_FOUND);
|
|
446
|
-
return
|
|
438
|
+
return {
|
|
439
|
+
content: parseVersionContent(row),
|
|
440
|
+
variables: parseVersionVariables(row),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** The content a send of `version` carried: the draft's for a test of one. */
|
|
445
|
+
export async function contentOfVersion(
|
|
446
|
+
tenantId: string,
|
|
447
|
+
template: MailingTemplate,
|
|
448
|
+
version: number,
|
|
449
|
+
): Promise<TemplateContent> {
|
|
450
|
+
return (await snapshotOfVersion(tenantId, template, version)).content;
|
|
447
451
|
}
|
|
448
452
|
|
|
449
453
|
export function parseVersionContent(
|
|
@@ -457,9 +461,7 @@ export function parseVersionContent(
|
|
|
457
461
|
* copy starts as a draft of the source's working content: it was never
|
|
458
462
|
* published, so customers receive nothing from it yet.
|
|
459
463
|
*/
|
|
460
|
-
export function contentFieldsOf(
|
|
461
|
-
source: MailingTemplate,
|
|
462
|
-
): Partial<MailingTemplate> {
|
|
464
|
+
export function contentFieldsOf(source: MailingTemplate): TemplateFields {
|
|
463
465
|
const content = workingContent(source);
|
|
464
466
|
return {
|
|
465
467
|
json_content: "",
|
|
@@ -483,9 +485,9 @@ export async function initializeTemplate(
|
|
|
483
485
|
tenantId: string,
|
|
484
486
|
templateId: string,
|
|
485
487
|
author: string,
|
|
486
|
-
fields:
|
|
488
|
+
fields: TemplateFields,
|
|
487
489
|
): Promise<void> {
|
|
488
|
-
await GetModel(TemplateModel, tenantId).
|
|
490
|
+
await GetModel(TemplateModel, tenantId).updateFields(templateId, {
|
|
489
491
|
status: "draft",
|
|
490
492
|
json_variables: "[]",
|
|
491
493
|
json_test_data: "{}",
|
|
@@ -495,9 +497,53 @@ export async function initializeTemplate(
|
|
|
495
497
|
await refreshTemplateAttention(tenantId);
|
|
496
498
|
}
|
|
497
499
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
/** The first version a template publishes. */
|
|
501
|
+
const FIRST_VERSION = 1;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* A new template, published at once as version 1 of `draft`: its content
|
|
505
|
+
* and its history entry land in the one write that creates it, so no reader
|
|
506
|
+
* ever sees it unpublished. Materialize its version row afterwards, as a
|
|
507
|
+
* publication does.
|
|
508
|
+
*/
|
|
509
|
+
export function publishedAtCreation(
|
|
510
|
+
id: string,
|
|
511
|
+
draft: TemplateDraft,
|
|
512
|
+
author: string,
|
|
513
|
+
): TemplateFields {
|
|
514
|
+
const shipped: TemplateFields = {
|
|
515
|
+
json_content: JSON.stringify(draft.content),
|
|
516
|
+
json_variables: JSON.stringify(draft.variables),
|
|
517
|
+
locales: localesOf(draft.content),
|
|
518
|
+
publishedAt: new Date(),
|
|
519
|
+
publishedBy: author,
|
|
520
|
+
};
|
|
521
|
+
const changes = [
|
|
522
|
+
...diffContents({ locales: {} }, draft.content),
|
|
523
|
+
...diffVariables([], draft.variables),
|
|
524
|
+
];
|
|
525
|
+
return {
|
|
526
|
+
_id: id,
|
|
527
|
+
...shipped,
|
|
528
|
+
json_published_version: JSON.stringify({
|
|
529
|
+
...shipped,
|
|
530
|
+
_id: randomUUID(),
|
|
531
|
+
templateId: id,
|
|
532
|
+
version: FIRST_VERSION,
|
|
533
|
+
json_changes: JSON.stringify(changes),
|
|
534
|
+
}),
|
|
535
|
+
json_draft: "",
|
|
536
|
+
json_variables_draft: "",
|
|
537
|
+
isDraftPending: false,
|
|
538
|
+
draftRevision: FIRST_DRAFT_REVISION,
|
|
539
|
+
status: "live",
|
|
540
|
+
publishedVersion: FIRST_VERSION,
|
|
541
|
+
updatedBy: author,
|
|
542
|
+
...NO_ATTENTION,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function blankFields(content: TemplateContent): TemplateFields {
|
|
501
547
|
return {
|
|
502
548
|
json_content: "",
|
|
503
549
|
json_draft: JSON.stringify(content),
|
|
@@ -8,13 +8,13 @@ import type {
|
|
|
8
8
|
WebhookStatus,
|
|
9
9
|
} from "../types";
|
|
10
10
|
import { webhookSecretMatches } from "./events";
|
|
11
|
+
import { WEBHOOK_SECRET_HEADER } from "../constants";
|
|
12
|
+
|
|
13
|
+
export { WEBHOOK_SECRET_HEADER };
|
|
11
14
|
|
|
12
15
|
/** The message id of the event the settings' "Send a test event" posts. */
|
|
13
16
|
export const WEBHOOK_TEST_MESSAGE_ID = "dms-mailing-webhook-test";
|
|
14
17
|
|
|
15
|
-
/** The header a provider (or its relay) proves it knows the secret with. */
|
|
16
|
-
export const WEBHOOK_SECRET_HEADER = "x-mailing-webhook-secret";
|
|
17
|
-
|
|
18
18
|
const WEBHOOK_TEST_EVENT_TYPE: SendEventType = "delivered";
|
|
19
19
|
const WEBHOOK_TEST_TIMEOUT_MS = 10_000;
|
|
20
20
|
const JSON_CONTENT_TYPE = "application/json";
|
package/src/types/settings.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
import type { LocalizedText } from "@antelopejs/interface-dms-mailing";
|
|
2
|
+
|
|
3
|
+
/** A template category as stored: its label written once per language. */
|
|
1
4
|
export interface TemplateCategory {
|
|
5
|
+
id: string;
|
|
6
|
+
label: LocalizedText;
|
|
7
|
+
icon: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A template category as one reader sees it: the label in their language. */
|
|
11
|
+
export interface CategoryInLocale {
|
|
2
12
|
id: string;
|
|
3
13
|
label: string;
|
|
4
14
|
icon: string;
|
|
@@ -16,9 +26,49 @@ export interface MailingSettingsValues {
|
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export const DEFAULT_CATEGORIES: TemplateCategory[] = [
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
{
|
|
30
|
+
id: "orders",
|
|
31
|
+
label: { en: "Orders", fr: "Commandes" },
|
|
32
|
+
icon: "i-ph-package",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "account",
|
|
36
|
+
label: { en: "Account & access", fr: "Compte et accès" },
|
|
37
|
+
icon: "i-ph-lock",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "billing",
|
|
41
|
+
label: { en: "Billing", fr: "Facturation" },
|
|
42
|
+
icon: "i-ph-file-text",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "marketing",
|
|
46
|
+
label: { en: "Marketing", fr: "Marketing" },
|
|
47
|
+
icon: "i-ph-sparkle",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "forms",
|
|
51
|
+
label: { en: "Forms", fr: "Formulaires" },
|
|
52
|
+
icon: "i-ph-textbox",
|
|
53
|
+
},
|
|
24
54
|
];
|
|
55
|
+
|
|
56
|
+
export const DEFAULT_FALLBACK_LOCALE = "en";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The module defaults: what a tenant's settings start with, and what the
|
|
60
|
+
* settings form offers to put back ("Use default"). The sender is empty and
|
|
61
|
+
* the webhook secret is drawn per tenant, so neither has one.
|
|
62
|
+
*/
|
|
63
|
+
export const MODULE_DEFAULT_SETTINGS: Pick<
|
|
64
|
+
MailingSettingsValues,
|
|
65
|
+
| "fallbackLocale"
|
|
66
|
+
| "logRetentionDays"
|
|
67
|
+
| "blockOnMissingVariables"
|
|
68
|
+
| "categories"
|
|
69
|
+
> = {
|
|
70
|
+
fallbackLocale: DEFAULT_FALLBACK_LOCALE,
|
|
71
|
+
logRetentionDays: 90,
|
|
72
|
+
blockOnMissingVariables: false,
|
|
73
|
+
categories: DEFAULT_CATEGORIES,
|
|
74
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { LOCALIZED_TEXT_FALLBACK_LOCALE } from "@antelopejs/interface-dms-mailing";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { MAX_NAME_LENGTH } from "../constants";
|
|
3
4
|
import { SEND_EVENT_TYPES } from "../types";
|
|
4
|
-
import { categorySchema } from "./settings.schema";
|
|
5
|
+
import { categorySchema, settingsSchema } from "./settings.schema";
|
|
5
6
|
import {
|
|
6
7
|
slugSchema,
|
|
7
8
|
templateContentSchema,
|
|
@@ -13,6 +14,17 @@ const MAX_IMPORT_ID_LENGTH = 200;
|
|
|
13
14
|
|
|
14
15
|
const tenantIdSchema = z.string().trim().min(1);
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* A name or a label declared from code: one non-blank text per language, the
|
|
19
|
+
* fallback language's required, since every reader without a text of their
|
|
20
|
+
* own reads it.
|
|
21
|
+
*/
|
|
22
|
+
export const localizedTextSchema = z
|
|
23
|
+
.record(z.string().trim().min(1).max(MAX_NAME_LENGTH))
|
|
24
|
+
.refine((text) => Boolean(text[LOCALIZED_TEXT_FALLBACK_LOCALE]), {
|
|
25
|
+
message: `a text in "${LOCALIZED_TEXT_FALLBACK_LOCALE}" is required`,
|
|
26
|
+
});
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* A template declared from code: the slug and name rules of the dashboard's
|
|
18
30
|
* creation, and the content and declarations its editor saves.
|
|
@@ -20,7 +32,7 @@ const tenantIdSchema = z.string().trim().min(1);
|
|
|
20
32
|
export const templateDeclarationSchema = z.object({
|
|
21
33
|
tenantId: tenantIdSchema,
|
|
22
34
|
slug: slugSchema,
|
|
23
|
-
name:
|
|
35
|
+
name: localizedTextSchema,
|
|
24
36
|
category: z.string().trim().default(""),
|
|
25
37
|
content: templateContentSchema,
|
|
26
38
|
variables: variableListSchema.default([]),
|
|
@@ -34,13 +46,27 @@ export type ParsedTemplateDeclaration = z.infer<
|
|
|
34
46
|
|
|
35
47
|
export const categoryDeclarationSchema = categorySchema.extend({
|
|
36
48
|
tenantId: tenantIdSchema,
|
|
37
|
-
label:
|
|
49
|
+
label: localizedTextSchema,
|
|
38
50
|
});
|
|
39
51
|
|
|
40
52
|
export type ParsedCategoryDeclaration = z.infer<
|
|
41
53
|
typeof categoryDeclarationSchema
|
|
42
54
|
>;
|
|
43
55
|
|
|
56
|
+
const SENDER_REQUIRED = "a sender address is required";
|
|
57
|
+
|
|
58
|
+
/** A sender declared from code: the settings form's rules, an address required. */
|
|
59
|
+
export const senderDeclarationSchema = z.object({
|
|
60
|
+
tenantId: tenantIdSchema,
|
|
61
|
+
email: settingsSchema.shape.senderEmail.pipe(
|
|
62
|
+
z.string().min(1, SENDER_REQUIRED),
|
|
63
|
+
),
|
|
64
|
+
name: settingsSchema.shape.senderName.default(""),
|
|
65
|
+
replyTo: settingsSchema.shape.replyTo.default(""),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export type ParsedSenderDeclaration = z.infer<typeof senderDeclarationSchema>;
|
|
69
|
+
|
|
44
70
|
const recipientSchema = z.union([
|
|
45
71
|
z.string().trim().email(),
|
|
46
72
|
z.object({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { domainToASCII } from "node:url";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { MAX_NAME_LENGTH, SLUG_PATTERN } from "../constants";
|
|
4
|
+
import { LOCALIZED_TEXT_FALLBACK_LOCALE } from "@antelopejs/interface-dms-mailing";
|
|
4
5
|
import { MARKETING_CATEGORY } from "../engine";
|
|
5
6
|
|
|
6
7
|
export const MIN_RETENTION_DAYS = 1;
|
|
@@ -31,7 +32,8 @@ const MISSING_VARIABLES_POLICIES = ["refuse", "send"] as const;
|
|
|
31
32
|
|
|
32
33
|
export const categorySchema = z.object({
|
|
33
34
|
id: z.string().max(MAX_NAME_LENGTH).regex(SLUG_PATTERN),
|
|
34
|
-
label:
|
|
35
|
+
/** One label per language (`{ en: "Orders", fr: "Commandes" }`). */
|
|
36
|
+
label: z.record(z.string().trim().max(MAX_NAME_LENGTH)),
|
|
35
37
|
icon: z.string().regex(CATEGORY_ICON_PATTERN),
|
|
36
38
|
});
|
|
37
39
|
|
|
@@ -45,19 +47,44 @@ const categoryEntrySchema = categorySchema.extend({
|
|
|
45
47
|
|
|
46
48
|
export type CategoryEntry = z.infer<typeof categoryEntrySchema>;
|
|
47
49
|
|
|
50
|
+
/** A category entry as a reader of one language sees its label. */
|
|
51
|
+
interface EntryInLocale {
|
|
52
|
+
id: string;
|
|
53
|
+
label: string;
|
|
54
|
+
moveTo?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every language a list labels its categories in, the fallback one first:
|
|
59
|
+
* the rules below hold in each of them.
|
|
60
|
+
*/
|
|
61
|
+
function languagesOf(entries: CategoryEntry[]): string[] {
|
|
62
|
+
const codes = entries.flatMap((entry) => Object.keys(entry.label));
|
|
63
|
+
return [...new Set([LOCALIZED_TEXT_FALLBACK_LOCALE, ...codes])];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The entries as a reader of `locale` reads them, the fallback filling in. */
|
|
67
|
+
function entriesIn(entries: CategoryEntry[], locale: string): EntryInLocale[] {
|
|
68
|
+
return entries.map(({ id, label, moveTo }) => ({
|
|
69
|
+
id,
|
|
70
|
+
label: label[locale] || label[LOCALIZED_TEXT_FALLBACK_LOCALE] || "",
|
|
71
|
+
moveTo,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
/** Why a category list is refused, and the part of it at fault. */
|
|
49
76
|
interface CategoryIssue {
|
|
50
77
|
message: string;
|
|
51
78
|
path: Array<string | number>;
|
|
52
79
|
}
|
|
53
80
|
|
|
54
|
-
const isKept = (entry:
|
|
81
|
+
const isKept = (entry: EntryInLocale): boolean => entry.moveTo === undefined;
|
|
55
82
|
|
|
56
83
|
/**
|
|
57
84
|
* A kept entry repeating the label (whatever its case) or the id of an
|
|
58
85
|
* earlier one, under the row it belongs to.
|
|
59
86
|
*/
|
|
60
|
-
function duplicateIssues(entries:
|
|
87
|
+
function duplicateIssues(entries: EntryInLocale[]): CategoryIssue[] {
|
|
61
88
|
const labels = new Set<string>();
|
|
62
89
|
const ids = new Set<string>();
|
|
63
90
|
const issues: CategoryIssue[] = [];
|
|
@@ -79,7 +106,7 @@ function duplicateIssues(entries: CategoryEntry[]): CategoryIssue[] {
|
|
|
79
106
|
* label's own minimum, which the form would turn into a browser-side check:
|
|
80
107
|
* the server words it, under the row, together with the other row issues.
|
|
81
108
|
*/
|
|
82
|
-
function blankLabelIssues(entries:
|
|
109
|
+
function blankLabelIssues(entries: EntryInLocale[]): CategoryIssue[] {
|
|
83
110
|
return entries.flatMap((entry, index) =>
|
|
84
111
|
entry.label ? [] : [{ message: LABEL_REQUIRED, path: [index, LABEL_PART] }],
|
|
85
112
|
);
|
|
@@ -92,7 +119,7 @@ function marketingIssues(kept: Set<string>): CategoryIssue[] {
|
|
|
92
119
|
}
|
|
93
120
|
|
|
94
121
|
function moveIssues(
|
|
95
|
-
entries:
|
|
122
|
+
entries: EntryInLocale[],
|
|
96
123
|
kept: Set<string>,
|
|
97
124
|
): CategoryIssue[] {
|
|
98
125
|
return entries.flatMap((entry, index) =>
|
|
@@ -106,20 +133,35 @@ function refineCategoryEntries(
|
|
|
106
133
|
entries: CategoryEntry[],
|
|
107
134
|
context: z.RefinementCtx,
|
|
108
135
|
): void {
|
|
109
|
-
const kept = new Set(
|
|
136
|
+
const kept = new Set(
|
|
137
|
+
entries.filter((entry) => entry.moveTo === undefined).map(({ id }) => id),
|
|
138
|
+
);
|
|
139
|
+
const labelIssues = languagesOf(entries).flatMap((locale) => {
|
|
140
|
+
const inLocale = entriesIn(entries, locale);
|
|
141
|
+
return [...blankLabelIssues(inLocale), ...duplicateIssues(inLocale)];
|
|
142
|
+
});
|
|
143
|
+
const seen = new Set<string>();
|
|
110
144
|
[
|
|
111
|
-
...
|
|
112
|
-
...duplicateIssues(entries),
|
|
145
|
+
...labelIssues,
|
|
113
146
|
...marketingIssues(kept),
|
|
114
|
-
...moveIssues(entries, kept),
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
|
|
147
|
+
...moveIssues(entriesIn(entries, LOCALIZED_TEXT_FALLBACK_LOCALE), kept),
|
|
148
|
+
]
|
|
149
|
+
.filter((issue) => {
|
|
150
|
+
// One issue per row and part, whichever language shows it first.
|
|
151
|
+
const id = JSON.stringify(issue);
|
|
152
|
+
if (seen.has(id)) return false;
|
|
153
|
+
seen.add(id);
|
|
154
|
+
return true;
|
|
155
|
+
})
|
|
156
|
+
.forEach((issue) =>
|
|
157
|
+
context.addIssue({ code: z.ZodIssueCode.custom, ...issue }),
|
|
158
|
+
);
|
|
118
159
|
}
|
|
119
160
|
|
|
120
161
|
/**
|
|
121
|
-
* The category list of the settings:
|
|
122
|
-
* move names a kept one, and the
|
|
162
|
+
* The category list of the settings: in every language, each kept label is
|
|
163
|
+
* set and unique; ids are unique, every move names a kept one, and the
|
|
164
|
+
* marketing category stays.
|
|
123
165
|
*/
|
|
124
166
|
export const categoriesSchema = z
|
|
125
167
|
.array(categoryEntrySchema)
|
|
@@ -182,6 +182,16 @@ export const testSendSchema = previewSchema.extend({
|
|
|
182
182
|
to: z.array(z.string().email()).min(1).max(MAX_TEST_RECIPIENT_COUNT),
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* The details a template's drawer or the editor saves: the name typed in the
|
|
187
|
+
* reader's language, the slug it was read with, the category (`null` clears).
|
|
188
|
+
*/
|
|
189
|
+
export const templateDetailsSchema = z.object({
|
|
190
|
+
name: z.string().trim().min(1).max(MAX_NAME_LENGTH),
|
|
191
|
+
slug: z.string().optional(),
|
|
192
|
+
category: z.string().nullable().optional(),
|
|
193
|
+
});
|
|
194
|
+
|
|
185
195
|
export const duplicateSchema = z.object({
|
|
186
196
|
slug: slugSchema,
|
|
187
197
|
name: z.string().trim().min(1).max(MAX_NAME_LENGTH),
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { MailingTemplateSlug } from "../tables";
|
|
2
|
-
export declare function templateSlugIdOf(tenantId: string, slug: string): string;
|
|
3
|
-
declare const TemplateSlugModel_base: {
|
|
4
|
-
new (database: import("@antelopejs/interface-database").SchemaInstance<any>): {
|
|
5
|
-
readonly table: import("@antelopejs/interface-database").Table<MailingTemplateSlug>;
|
|
6
|
-
readonly database: import("@antelopejs/interface-database").SchemaInstance<any>;
|
|
7
|
-
get(id: string): PromiseLike<MailingTemplateSlug | undefined>;
|
|
8
|
-
getBy(index: keyof MailingTemplateSlug, ...keys: any[]): PromiseLike<MailingTemplateSlug[]>;
|
|
9
|
-
getAll(): PromiseLike<MailingTemplateSlug[]>;
|
|
10
|
-
insert(obj: import("@antelopejs/interface-database-decorators").DeepPartial<MailingTemplateSlug> | import("@antelopejs/interface-database-decorators").DeepPartial<MailingTemplateSlug>[], options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<string[]>;
|
|
11
|
-
update(id: string, obj: import("@antelopejs/interface-database-decorators").DeepPartial<MailingTemplateSlug>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
|
|
12
|
-
update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<MailingTemplateSlug>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
|
|
13
|
-
delete(id: string): Promise<number>;
|
|
14
|
-
};
|
|
15
|
-
readonly schemaName: string;
|
|
16
|
-
readonly tableClass: import("@antelopejs/interface-database-decorators").Constructible<MailingTemplateSlug>;
|
|
17
|
-
fromPlainData(obj: any): MailingTemplateSlug;
|
|
18
|
-
fromDatabase(obj: any): MailingTemplateSlug | undefined;
|
|
19
|
-
toDatabase(obj: any): Record<string, any>;
|
|
20
|
-
validate(obj: unknown, options?: import("@antelopejs/interface-database-decorators").ValidateInputOptions): import("@antelopejs/interface-database-decorators").ValidationResult;
|
|
21
|
-
};
|
|
22
|
-
export declare class TemplateSlugModel extends TemplateSlugModel_base {
|
|
23
|
-
insertClaim(id: string, slug: string, templateId: string): Promise<boolean>;
|
|
24
|
-
dropClaimOf(id: string, templateId: string): Promise<void>;
|
|
25
|
-
releaseTemplates(templateIds: string[]): Promise<void>;
|
|
26
|
-
}
|
|
27
|
-
export {};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TemplateSlugModel = void 0;
|
|
4
|
-
exports.templateSlugIdOf = templateSlugIdOf;
|
|
5
|
-
const node_crypto_1 = require("node:crypto");
|
|
6
|
-
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
7
|
-
const tables_1 = require("../tables");
|
|
8
|
-
const ID_LENGTH = 32;
|
|
9
|
-
function templateSlugIdOf(tenantId, slug) {
|
|
10
|
-
return (0, node_crypto_1.createHash)("sha256")
|
|
11
|
-
.update(`${tenantId}\n${slug}`)
|
|
12
|
-
.digest("hex")
|
|
13
|
-
.slice(0, ID_LENGTH);
|
|
14
|
-
}
|
|
15
|
-
class TemplateSlugModel extends (0, interface_database_decorators_1.BasicDataModel)(tables_1.MailingTemplateSlug, tables_1.MAILING_TEMPLATE_SLUGS_TABLE_NAME) {
|
|
16
|
-
async insertClaim(id, slug, templateId) {
|
|
17
|
-
try {
|
|
18
|
-
await this.insert({ _id: id, slug, templateId });
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
if (await this.get(id))
|
|
23
|
-
return false;
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async dropClaimOf(id, templateId) {
|
|
28
|
-
await this.table
|
|
29
|
-
.atomicMutation(id, {
|
|
30
|
-
type: "deleteIfEqual",
|
|
31
|
-
field: "templateId",
|
|
32
|
-
expectedValue: templateId,
|
|
33
|
-
})
|
|
34
|
-
.run();
|
|
35
|
-
}
|
|
36
|
-
async releaseTemplates(templateIds) {
|
|
37
|
-
if (!templateIds.length)
|
|
38
|
-
return;
|
|
39
|
-
await this.table.getAll(templateIds, "templateId").delete().run();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
exports.TemplateSlugModel = TemplateSlugModel;
|
|
43
|
-
//# sourceMappingURL=template-slugs.model.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"template-slugs.model.js","sourceRoot":"","sources":["../../../src/db/models/template-slugs.model.ts"],"names":[],"mappings":";;;AAcA,4CAKC;AAnBD,6CAAyC;AACzC,6FAA2E;AAC3E,sCAGmB;AAEnB,MAAM,SAAS,GAAG,EAAE,CAAC;AAOrB,SAAgB,gBAAgB,CAAC,QAAgB,EAAE,IAAY;IAC7D,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,GAAG,QAAQ,KAAK,IAAI,EAAE,CAAC;SAC9B,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACzB,CAAC;AAED,MAAa,iBAAkB,SAAQ,IAAA,8CAAc,EACnD,4BAAmB,EACnB,0CAAiC,CAClC;IAMC,KAAK,CAAC,WAAW,CACf,EAAU,EACV,IAAY,EACZ,UAAkB;QAElB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO,KAAK,CAAC;YACrC,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAGD,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,UAAkB;QAC9C,MAAM,IAAI,CAAC,KAAK;aACb,cAAc,CAAC,EAAE,EAAE;YAClB,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,YAAY;YACnB,aAAa,EAAE,UAAU;SAC1B,CAAC;aACD,GAAG,EAAE,CAAC;IACX,CAAC;IAGD,KAAK,CAAC,gBAAgB,CAAC,WAAqB;QAC1C,IAAI,CAAC,WAAW,CAAC,MAAM;YAAE,OAAO;QAChC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;IACpE,CAAC;CACF;AAvCD,8CAuCC"}
|