@antelopejs/dms-mailing 0.9.0 → 0.9.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 +2 -2
- package/dist/db/models/sends.model.d.ts +3 -1
- package/dist/db/models/sends.model.js +26 -2
- package/dist/db/models/sends.model.js.map +1 -1
- package/dist/db/tables/settings.table.d.ts +1 -0
- package/dist/db/tables/settings.table.js +4 -0
- package/dist/db/tables/settings.table.js.map +1 -1
- package/dist/hooks/tenant-export.d.ts +1 -1
- package/dist/hooks/tenant-export.js.map +1 -1
- package/dist/implementations/dms-mailing/index.d.ts +1 -0
- package/dist/implementations/dms-mailing/index.js +3 -1
- package/dist/implementations/dms-mailing/index.js.map +1 -1
- package/dist/pages/sends.js.map +1 -1
- package/dist/routes/metrics.js +1 -2
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/settings.d.ts +3 -2
- package/dist/routes/settings.js +8 -4
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/templates.js +2 -2
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/attention.d.ts +1 -1
- package/dist/services/attention.js +3 -3
- package/dist/services/attention.js.map +1 -1
- package/dist/services/metrics.js +5 -1
- package/dist/services/metrics.js.map +1 -1
- package/dist/services/send-import.js +4 -4
- package/dist/services/send-import.js.map +1 -1
- package/dist/services/settings.d.ts +2 -1
- package/dist/services/settings.js +19 -1
- package/dist/services/settings.js.map +1 -1
- package/dist/services/template-attention.d.ts +1 -1
- package/dist/services/template-attention.js +2 -4
- package/dist/services/template-attention.js.map +1 -1
- package/dist/services/webhook-secret-declaration.d.ts +4 -0
- package/dist/services/webhook-secret-declaration.js +25 -0
- package/dist/services/webhook-secret-declaration.js.map +1 -0
- package/dist/services/webhook-status.js +7 -2
- package/dist/services/webhook-status.js.map +1 -1
- package/dist/types/settings.d.ts +3 -0
- package/dist/types/settings.js +6 -1
- package/dist/types/settings.js.map +1 -1
- package/dist/validation/declarations.schema.d.ts +13 -0
- package/dist/validation/declarations.schema.js +6 -1
- package/dist/validation/declarations.schema.js.map +1 -1
- package/frontend-vue/app/components/editor/EditorCanvas.vue +8 -4
- package/frontend-vue/app/composables/useSortableList.ts +2 -2
- package/frontend-vue/package.json +37 -36
- package/frontend-vue/pnpm-lock.yaml +25 -5
- package/frontend-vue/tests/editor-canvas.test.ts +142 -0
- package/frontend-vue/tests/sortable-list.test-d.ts +19 -0
- package/frontend-vue/tsconfig.typecheck.json +11 -0
- package/frontend-vue/vitest.config.ts +16 -0
- package/package.json +5 -5
- package/skills/dms-mailing/SKILL.md +2 -0
- package/src/db/models/sends.model.ts +34 -4
- package/src/db/tables/settings.table.ts +4 -0
- package/src/hooks/tenant-export.ts +4 -1
- package/src/implementations/dms-mailing/index.ts +1 -0
- package/src/pages/sends.ts +3 -2
- package/src/routes/metrics.ts +1 -2
- package/src/routes/settings.ts +19 -7
- package/src/routes/templates.ts +6 -7
- package/src/services/attention.ts +6 -6
- package/src/services/metrics.ts +3 -1
- package/src/services/send-import.ts +10 -8
- package/src/services/settings.ts +40 -2
- package/src/services/template-attention.ts +4 -5
- package/src/services/webhook-secret-declaration.ts +47 -0
- package/src/services/webhook-status.ts +9 -5
- package/src/types/settings.ts +14 -0
- package/src/validation/declarations.schema.ts +7 -0
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
type TemplateAttentionFields,
|
|
6
6
|
TemplateModel,
|
|
7
7
|
} from "../db";
|
|
8
|
-
import {
|
|
8
|
+
import type { TemplateAttentionReason } from "../types";
|
|
9
9
|
import {
|
|
10
10
|
knownLocalesOf,
|
|
11
11
|
type TemplateAttentionContext,
|
|
12
12
|
templateAttentionReasons,
|
|
13
13
|
} from "./attention";
|
|
14
|
-
import {
|
|
14
|
+
import { statsWindowStart } from "./metrics";
|
|
15
15
|
|
|
16
16
|
/** How the stored `attention` field separates its reasons. */
|
|
17
17
|
const REASON_SEPARATOR = ",";
|
|
@@ -31,14 +31,13 @@ async function attentionContext(
|
|
|
31
31
|
templates: MailingTemplate[],
|
|
32
32
|
): Promise<TemplateAttentionContext> {
|
|
33
33
|
const now = Date.now();
|
|
34
|
-
const
|
|
34
|
+
const slugs = await GetModel(SendModel, tenantId).listSentSlugs(
|
|
35
35
|
statsWindowStart(now),
|
|
36
36
|
new Date(now),
|
|
37
37
|
);
|
|
38
|
-
const real = forAudience(sends, BUSINESS_AUDIENCE);
|
|
39
38
|
return {
|
|
40
39
|
now,
|
|
41
|
-
sentSlugs: new Set(
|
|
40
|
+
sentSlugs: new Set(slugs),
|
|
42
41
|
knownLocales: knownLocalesOf(templates),
|
|
43
42
|
};
|
|
44
43
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DeclareWebhookSecretResult,
|
|
3
|
+
WebhookSecretDeclaration,
|
|
4
|
+
WebhookSecretOutcome,
|
|
5
|
+
} from "@antelopejs/interface-dms-mailing";
|
|
6
|
+
import type { MailingSettingsValues } from "../types";
|
|
7
|
+
import { webhookSecretDeclarationSchema } from "../validation/declarations.schema";
|
|
8
|
+
import { parseDeclared } from "./declaration-error";
|
|
9
|
+
import { webhookSecretMatches } from "./events";
|
|
10
|
+
import { getSettings, saveWebhookSecret } from "./settings";
|
|
11
|
+
|
|
12
|
+
const SET: WebhookSecretOutcome = "set";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What declaring `secret` does to the stored one: nothing when it is already
|
|
16
|
+
* stored, nothing when the workspace owner set or rotated it and the caller
|
|
17
|
+
* did not ask to replace it, else a write.
|
|
18
|
+
*/
|
|
19
|
+
export function webhookSecretOutcome(
|
|
20
|
+
stored: Pick<MailingSettingsValues, "webhookSecret" | "webhookSecretSource">,
|
|
21
|
+
secret: string,
|
|
22
|
+
replaceRotated: boolean,
|
|
23
|
+
): WebhookSecretOutcome {
|
|
24
|
+
if (webhookSecretMatches(secret, stored.webhookSecret)) return "unchanged";
|
|
25
|
+
if (stored.webhookSecretSource === "operator" && !replaceRotated)
|
|
26
|
+
return "kept";
|
|
27
|
+
return SET;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Sets the workspace's webhook secret a project ships as code. The secret is
|
|
32
|
+
* never returned, and a refusal names the rule it broke, never the value.
|
|
33
|
+
*/
|
|
34
|
+
export async function declareWebhookSecret(
|
|
35
|
+
input: WebhookSecretDeclaration,
|
|
36
|
+
): Promise<DeclareWebhookSecretResult> {
|
|
37
|
+
const { tenantId, secret, replaceRotated } = parseDeclared(
|
|
38
|
+
webhookSecretDeclarationSchema,
|
|
39
|
+
input,
|
|
40
|
+
"invalid_webhook_secret",
|
|
41
|
+
"webhook secret declaration",
|
|
42
|
+
);
|
|
43
|
+
const stored = await getSettings(tenantId);
|
|
44
|
+
const outcome = webhookSecretOutcome(stored, secret, replaceRotated);
|
|
45
|
+
if (outcome === SET) await saveWebhookSecret(tenantId, secret, "declared");
|
|
46
|
+
return { outcome };
|
|
47
|
+
}
|
|
@@ -144,15 +144,19 @@ export async function readWebhookStatus(
|
|
|
144
144
|
);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function isWebhookRefusal(value: unknown): value is WebhookRefusal {
|
|
148
|
+
return isText(value) && Object.hasOwn(WEBHOOK_REFUSAL_MESSAGES, value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The webhook's own refusal reason in a response, not one another endpoint gives. */
|
|
147
152
|
async function refusalOf(
|
|
148
153
|
response: Response,
|
|
149
154
|
): Promise<WebhookRefusal | undefined> {
|
|
150
155
|
try {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return body.reason;
|
|
156
|
+
const body: unknown = await response.json();
|
|
157
|
+
return isObject(body) && isWebhookRefusal(body.reason)
|
|
158
|
+
? body.reason
|
|
159
|
+
: undefined;
|
|
156
160
|
} catch {
|
|
157
161
|
return undefined;
|
|
158
162
|
}
|
package/src/types/settings.ts
CHANGED
|
@@ -14,6 +14,19 @@ export interface CategoryInLocale {
|
|
|
14
14
|
icon: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Who set the webhook secret: the module drew it (`generated`), a project
|
|
19
|
+
* declared it (`declared`), or the workspace owner set or rotated it in the
|
|
20
|
+
* settings (`operator`). A declaration replaces only the first two.
|
|
21
|
+
*/
|
|
22
|
+
export const WEBHOOK_SECRET_SOURCES = [
|
|
23
|
+
"generated",
|
|
24
|
+
"declared",
|
|
25
|
+
"operator",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export type WebhookSecretSource = (typeof WEBHOOK_SECRET_SOURCES)[number];
|
|
29
|
+
|
|
17
30
|
export interface MailingSettingsValues {
|
|
18
31
|
fallbackLocale: string;
|
|
19
32
|
logRetentionDays: number;
|
|
@@ -22,6 +35,7 @@ export interface MailingSettingsValues {
|
|
|
22
35
|
senderEmail: string;
|
|
23
36
|
replyTo: string;
|
|
24
37
|
webhookSecret: string;
|
|
38
|
+
webhookSecretSource: WebhookSecretSource;
|
|
25
39
|
categories: TemplateCategory[];
|
|
26
40
|
}
|
|
27
41
|
|
|
@@ -68,6 +68,13 @@ export const senderDeclarationSchema = z.object({
|
|
|
68
68
|
|
|
69
69
|
export type ParsedSenderDeclaration = z.infer<typeof senderDeclarationSchema>;
|
|
70
70
|
|
|
71
|
+
/** A webhook secret declared from code: the settings form's rule. */
|
|
72
|
+
export const webhookSecretDeclarationSchema = z.object({
|
|
73
|
+
tenantId: tenantIdSchema,
|
|
74
|
+
secret: settingsSchema.shape.webhookSecret,
|
|
75
|
+
replaceRotated: z.boolean().default(false),
|
|
76
|
+
});
|
|
77
|
+
|
|
71
78
|
const recipientSchema = z.union([
|
|
72
79
|
z.string().trim().email(),
|
|
73
80
|
z.object({
|