@mobilizehub/payload-plugin 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/resend-adapter.d.ts +34 -0
- package/dist/adapters/resend-adapter.js +219 -0
- package/dist/adapters/resend-adapter.js.map +1 -0
- package/dist/collections/broadcasts/generateBroadcastsCollection.d.ts +3 -0
- package/dist/collections/broadcasts/generateBroadcastsCollection.js +241 -0
- package/dist/collections/broadcasts/generateBroadcastsCollection.js.map +1 -0
- package/dist/collections/emails/generateEmailsCollection.d.ts +3 -0
- package/dist/collections/emails/generateEmailsCollection.js +204 -0
- package/dist/collections/emails/generateEmailsCollection.js.map +1 -0
- package/dist/collections/emails/hooks/sync-status-from-activity.d.ts +5 -0
- package/dist/collections/emails/hooks/sync-status-from-activity.js +64 -0
- package/dist/collections/emails/hooks/sync-status-from-activity.js.map +1 -0
- package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.d.ts +2 -0
- package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js +48 -0
- package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js.map +1 -0
- package/dist/components/broadcast-metrics-card.d.ts +7 -0
- package/dist/components/broadcast-metrics-card.js +159 -0
- package/dist/components/broadcast-metrics-card.js.map +1 -0
- package/dist/components/broadcast-send-modal.d.ts +9 -0
- package/dist/components/broadcast-send-modal.js +51 -0
- package/dist/components/broadcast-send-modal.js.map +1 -0
- package/dist/components/broadcast-send-test-drawer.d.ts +7 -0
- package/dist/components/broadcast-send-test-drawer.js +154 -0
- package/dist/components/broadcast-send-test-drawer.js.map +1 -0
- package/dist/components/email-activity.d.ts +4 -0
- package/dist/components/email-activity.js +359 -0
- package/dist/components/email-activity.js.map +1 -0
- package/dist/components/email-preview.d.ts +2 -0
- package/dist/components/email-preview.js +95 -0
- package/dist/components/email-preview.js.map +1 -0
- package/dist/endpoints/sendBroadcastHandler.d.ts +9 -0
- package/dist/endpoints/sendBroadcastHandler.js +107 -0
- package/dist/endpoints/sendBroadcastHandler.js.map +1 -0
- package/dist/endpoints/sendTestBroadcastHandler.d.ts +10 -0
- package/dist/endpoints/sendTestBroadcastHandler.js +143 -0
- package/dist/endpoints/sendTestBroadcastHandler.js.map +1 -0
- package/dist/endpoints/unsubscribeHandler.d.ts +9 -0
- package/dist/endpoints/unsubscribeHandler.js +153 -0
- package/dist/endpoints/unsubscribeHandler.js.map +1 -0
- package/dist/exports/client.d.ts +3 -1
- package/dist/exports/client.js +3 -0
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/rsc.d.ts +2 -1
- package/dist/exports/rsc.js +2 -0
- package/dist/exports/rsc.js.map +1 -1
- package/dist/index.js +48 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/unsubscribe.d.ts +6 -0
- package/dist/react/unsubscribe.js +16 -0
- package/dist/react/unsubscribe.js.map +1 -0
- package/dist/tasks/sendBroadcastsTask.d.ts +11 -0
- package/dist/tasks/sendBroadcastsTask.js +196 -0
- package/dist/tasks/sendBroadcastsTask.js.map +1 -0
- package/dist/tasks/sendEmailTask.d.ts +9 -0
- package/dist/tasks/sendEmailTask.js +167 -0
- package/dist/tasks/sendEmailTask.js.map +1 -0
- package/dist/types/index.d.ts +124 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/api-response.d.ts +72 -0
- package/dist/utils/api-response.js +66 -0
- package/dist/utils/api-response.js.map +1 -0
- package/dist/utils/email.d.ts +36 -0
- package/dist/utils/email.js +40 -0
- package/dist/utils/email.js.map +1 -0
- package/dist/utils/lexical.d.ts +13 -0
- package/dist/utils/lexical.js +27 -0
- package/dist/utils/lexical.js.map +1 -0
- package/dist/utils/unsubscribe-token.d.ts +67 -0
- package/dist/utils/unsubscribe-token.js +103 -0
- package/dist/utils/unsubscribe-token.js.map +1 -0
- package/package.json +20 -9
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useFormFields } from '@payloadcms/ui';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
export const EmailPreviewField = ()=>{
|
|
6
|
+
const htmlField = useFormFields(([fields])=>{
|
|
7
|
+
return fields['html'];
|
|
8
|
+
});
|
|
9
|
+
const value = htmlField?.value;
|
|
10
|
+
if (!value) {
|
|
11
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
12
|
+
className: "field-type text",
|
|
13
|
+
style: {
|
|
14
|
+
flex: '1 1 auto'
|
|
15
|
+
},
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
18
|
+
className: "field-label",
|
|
19
|
+
htmlFor: "email-preview",
|
|
20
|
+
children: [
|
|
21
|
+
"Email Preview ",
|
|
22
|
+
/*#__PURE__*/ _jsx("span", {
|
|
23
|
+
className: "required",
|
|
24
|
+
children: "*"
|
|
25
|
+
})
|
|
26
|
+
]
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
29
|
+
className: "field-type__wrap",
|
|
30
|
+
children: [
|
|
31
|
+
/*#__PURE__*/ _jsx("input", {
|
|
32
|
+
id: "email-preview",
|
|
33
|
+
type: "hidden"
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx("div", {
|
|
36
|
+
style: {
|
|
37
|
+
backgroundColor: 'var(--theme-input-bg)',
|
|
38
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
39
|
+
borderRadius: 'var(--style-radius-s)',
|
|
40
|
+
display: 'block',
|
|
41
|
+
minHeight: '400px',
|
|
42
|
+
width: '100%'
|
|
43
|
+
},
|
|
44
|
+
title: "Email Preview",
|
|
45
|
+
children: "No email content available"
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
53
|
+
className: "field-type text",
|
|
54
|
+
style: {
|
|
55
|
+
flex: '1 1 auto'
|
|
56
|
+
},
|
|
57
|
+
children: [
|
|
58
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
59
|
+
className: "field-label",
|
|
60
|
+
htmlFor: "email-preview",
|
|
61
|
+
children: [
|
|
62
|
+
"Email Preview ",
|
|
63
|
+
/*#__PURE__*/ _jsx("span", {
|
|
64
|
+
className: "required",
|
|
65
|
+
children: "*"
|
|
66
|
+
})
|
|
67
|
+
]
|
|
68
|
+
}),
|
|
69
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
70
|
+
className: "field-type__wrap",
|
|
71
|
+
children: [
|
|
72
|
+
/*#__PURE__*/ _jsx("input", {
|
|
73
|
+
id: "email-preview",
|
|
74
|
+
type: "hidden"
|
|
75
|
+
}),
|
|
76
|
+
/*#__PURE__*/ _jsx("iframe", {
|
|
77
|
+
sandbox: "allow-same-origin",
|
|
78
|
+
srcDoc: value,
|
|
79
|
+
style: {
|
|
80
|
+
backgroundColor: 'var(--theme-input-bg)',
|
|
81
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
82
|
+
borderRadius: 'var(--style-radius-s)',
|
|
83
|
+
display: 'block',
|
|
84
|
+
height: '100vh',
|
|
85
|
+
width: '100%'
|
|
86
|
+
},
|
|
87
|
+
title: "Email Preview"
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=email-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/email-preview.tsx"],"sourcesContent":["'use client'\nimport { useFormFields } from '@payloadcms/ui'\nimport React from 'react'\n\nexport const EmailPreviewField: React.FC = () => {\n const htmlField = useFormFields(([fields]) => {\n return fields['html']\n })\n const value = htmlField?.value as string\n\n if (!value) {\n return (\n <div\n className=\"field-type text\"\n style={{\n flex: '1 1 auto',\n }}\n >\n <label className=\"field-label\" htmlFor=\"email-preview\">\n Email Preview <span className=\"required\">*</span>\n </label>\n <div className=\"field-type__wrap\">\n <input id=\"email-preview\" type=\"hidden\" />\n <div\n style={{\n backgroundColor: 'var(--theme-input-bg)',\n border: '1px solid var(--theme-elevation-150)',\n borderRadius: 'var(--style-radius-s)',\n display: 'block',\n minHeight: '400px',\n width: '100%',\n }}\n title=\"Email Preview\"\n >\n No email content available\n </div>\n </div>\n </div>\n )\n }\n\n return (\n <div\n className=\"field-type text\"\n style={{\n flex: '1 1 auto',\n }}\n >\n <label className=\"field-label\" htmlFor=\"email-preview\">\n Email Preview <span className=\"required\">*</span>\n </label>\n <div className=\"field-type__wrap\">\n <input id=\"email-preview\" type=\"hidden\" />\n <iframe\n sandbox=\"allow-same-origin\"\n srcDoc={value}\n style={{\n backgroundColor: 'var(--theme-input-bg)',\n border: '1px solid var(--theme-elevation-150)',\n borderRadius: 'var(--style-radius-s)',\n display: 'block',\n height: '100vh',\n width: '100%',\n }}\n title=\"Email Preview\"\n />\n </div>\n </div>\n )\n}\n"],"names":["useFormFields","React","EmailPreviewField","htmlField","fields","value","div","className","style","flex","label","htmlFor","span","input","id","type","backgroundColor","border","borderRadius","display","minHeight","width","title","iframe","sandbox","srcDoc","height"],"mappings":"AAAA;;AACA,SAASA,aAAa,QAAQ,iBAAgB;AAC9C,OAAOC,WAAW,QAAO;AAEzB,OAAO,MAAMC,oBAA8B;IACzC,MAAMC,YAAYH,cAAc,CAAC,CAACI,OAAO;QACvC,OAAOA,MAAM,CAAC,OAAO;IACvB;IACA,MAAMC,QAAQF,WAAWE;IAEzB,IAAI,CAACA,OAAO;QACV,qBACE,MAACC;YACCC,WAAU;YACVC,OAAO;gBACLC,MAAM;YACR;;8BAEA,MAACC;oBAAMH,WAAU;oBAAcI,SAAQ;;wBAAgB;sCACvC,KAACC;4BAAKL,WAAU;sCAAW;;;;8BAE3C,MAACD;oBAAIC,WAAU;;sCACb,KAACM;4BAAMC,IAAG;4BAAgBC,MAAK;;sCAC/B,KAACT;4BACCE,OAAO;gCACLQ,iBAAiB;gCACjBC,QAAQ;gCACRC,cAAc;gCACdC,SAAS;gCACTC,WAAW;gCACXC,OAAO;4BACT;4BACAC,OAAM;sCACP;;;;;;IAMT;IAEA,qBACE,MAAChB;QACCC,WAAU;QACVC,OAAO;YACLC,MAAM;QACR;;0BAEA,MAACC;gBAAMH,WAAU;gBAAcI,SAAQ;;oBAAgB;kCACvC,KAACC;wBAAKL,WAAU;kCAAW;;;;0BAE3C,MAACD;gBAAIC,WAAU;;kCACb,KAACM;wBAAMC,IAAG;wBAAgBC,MAAK;;kCAC/B,KAACQ;wBACCC,SAAQ;wBACRC,QAAQpB;wBACRG,OAAO;4BACLQ,iBAAiB;4BACjBC,QAAQ;4BACRC,cAAc;4BACdC,SAAS;4BACTO,QAAQ;4BACRL,OAAO;wBACT;wBACAC,OAAM;;;;;;AAKhB,EAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PayloadHandler } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the send-broadcast endpoint handler.
|
|
4
|
+
*
|
|
5
|
+
* Initiates sending a broadcast by validating the broadcast is in draft status,
|
|
6
|
+
* counting eligible contacts, and updating the status to 'sending'. The actual
|
|
7
|
+
* email delivery is handled by the send-broadcasts scheduled task.
|
|
8
|
+
*/
|
|
9
|
+
export declare const sendBroadcastHandler: () => PayloadHandler;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates the request body contains a valid broadcastId.
|
|
4
|
+
*/ function validateRequestBody(body) {
|
|
5
|
+
if (!body.broadcastId) {
|
|
6
|
+
return {
|
|
7
|
+
error: 'broadcastId is required',
|
|
8
|
+
success: false
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
broadcastId: body.broadcastId,
|
|
13
|
+
success: true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fetches a broadcast by ID.
|
|
18
|
+
* Returns null if the broadcast doesn't exist.
|
|
19
|
+
*/ async function findBroadcastById(payload, broadcastId) {
|
|
20
|
+
try {
|
|
21
|
+
const broadcast = await payload.findByID({
|
|
22
|
+
id: broadcastId,
|
|
23
|
+
collection: 'broadcasts'
|
|
24
|
+
});
|
|
25
|
+
return broadcast;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the where clause for counting eligible contacts.
|
|
32
|
+
* Adds tag filtering if the broadcast targets specific tags.
|
|
33
|
+
*/ function buildContactsWhereClause(broadcast) {
|
|
34
|
+
const whereClause = {
|
|
35
|
+
emailOptIn: {
|
|
36
|
+
equals: true
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
if (broadcast.to === 'tags' && Array.isArray(broadcast.tags) && broadcast.tags.length > 0) {
|
|
40
|
+
const tagIds = broadcast.tags.map((t)=>typeof t === 'object' ? t.id : t);
|
|
41
|
+
whereClause.tags = {
|
|
42
|
+
in: tagIds
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return whereClause;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates the send-broadcast endpoint handler.
|
|
49
|
+
*
|
|
50
|
+
* Initiates sending a broadcast by validating the broadcast is in draft status,
|
|
51
|
+
* counting eligible contacts, and updating the status to 'sending'. The actual
|
|
52
|
+
* email delivery is handled by the send-broadcasts scheduled task.
|
|
53
|
+
*/ export const sendBroadcastHandler = ()=>{
|
|
54
|
+
return async (req)=>{
|
|
55
|
+
const { payload } = req;
|
|
56
|
+
const logger = payload.logger;
|
|
57
|
+
if (!req.json) {
|
|
58
|
+
return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400);
|
|
59
|
+
}
|
|
60
|
+
if (!req.user) {
|
|
61
|
+
return errorResponse(ErrorCodes.UNAUTHORIZED, 'Unauthorized', 401);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const body = await req.json();
|
|
65
|
+
const validation = validateRequestBody(body);
|
|
66
|
+
if (!validation.success) {
|
|
67
|
+
logger.error(validation.error);
|
|
68
|
+
return errorResponse(ErrorCodes.BAD_REQUEST, validation.error, 400);
|
|
69
|
+
}
|
|
70
|
+
const { broadcastId } = validation;
|
|
71
|
+
const broadcast = await findBroadcastById(payload, broadcastId);
|
|
72
|
+
if (!broadcast) {
|
|
73
|
+
logger.error(`Broadcast ${broadcastId} not found`);
|
|
74
|
+
return errorResponse(ErrorCodes.BROADCAST_NOT_FOUND, 'Broadcast not found', 404);
|
|
75
|
+
}
|
|
76
|
+
if (broadcast.status !== 'draft') {
|
|
77
|
+
logger.error(`Broadcast ${broadcastId} is not in draft status (current: ${broadcast.status})`);
|
|
78
|
+
return errorResponse(ErrorCodes.VALIDATION_ERROR, 'Broadcast must be in draft status to send', 400);
|
|
79
|
+
}
|
|
80
|
+
const whereClause = buildContactsWhereClause(broadcast);
|
|
81
|
+
const contactsCount = await payload.db.count({
|
|
82
|
+
collection: 'contacts',
|
|
83
|
+
where: whereClause
|
|
84
|
+
});
|
|
85
|
+
await payload.update({
|
|
86
|
+
id: broadcastId,
|
|
87
|
+
collection: 'broadcasts',
|
|
88
|
+
data: {
|
|
89
|
+
meta: {
|
|
90
|
+
contactsCount: contactsCount.totalDocs,
|
|
91
|
+
processedCount: 0
|
|
92
|
+
},
|
|
93
|
+
status: 'sending'
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
logger.info(`Broadcast ${broadcastId} queued for sending`);
|
|
97
|
+
return successResponse({
|
|
98
|
+
message: 'Broadcast queued for sending'
|
|
99
|
+
}, 200);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
logger.error(err, 'Error occurred while queueing broadcast');
|
|
102
|
+
return errorResponse(ErrorCodes.INTERNAL_ERROR, 'Error queueing broadcast', 500);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=sendBroadcastHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/sendBroadcastHandler.ts"],"sourcesContent":["import type { Payload, PayloadHandler, Where } from 'payload'\n\nimport { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js'\n\ntype SendBroadcastBody = {\n broadcastId?: unknown\n}\n\ntype Broadcast = {\n id: number | string\n status?: string\n tags?: Array<{ id: number | string } | number | string>\n to?: string\n}\n\n/**\n * Validates the request body contains a valid broadcastId.\n */\nfunction validateRequestBody(\n body: SendBroadcastBody,\n): { broadcastId: number | string; success: true } | { error: string; success: false } {\n if (!body.broadcastId) {\n return { error: 'broadcastId is required', success: false }\n }\n return { broadcastId: body.broadcastId as number | string, success: true }\n}\n\n/**\n * Fetches a broadcast by ID.\n * Returns null if the broadcast doesn't exist.\n */\nasync function findBroadcastById(\n payload: Payload,\n broadcastId: number | string,\n): Promise<Broadcast | null> {\n try {\n const broadcast = await payload.findByID({\n id: broadcastId,\n collection: 'broadcasts',\n })\n return broadcast as Broadcast | null\n } catch {\n return null\n }\n}\n\n/**\n * Builds the where clause for counting eligible contacts.\n * Adds tag filtering if the broadcast targets specific tags.\n */\nfunction buildContactsWhereClause(broadcast: Broadcast): Where {\n const whereClause: Where = {\n emailOptIn: { equals: true },\n }\n\n if (broadcast.to === 'tags' && Array.isArray(broadcast.tags) && broadcast.tags.length > 0) {\n const tagIds = broadcast.tags.map((t) => (typeof t === 'object' ? t.id : t))\n whereClause.tags = { in: tagIds }\n }\n\n return whereClause\n}\n\n/**\n * Creates the send-broadcast endpoint handler.\n *\n * Initiates sending a broadcast by validating the broadcast is in draft status,\n * counting eligible contacts, and updating the status to 'sending'. The actual\n * email delivery is handled by the send-broadcasts scheduled task.\n */\nexport const sendBroadcastHandler = (): PayloadHandler => {\n return async (req) => {\n const { payload } = req\n const logger = payload.logger\n\n if (!req.json) {\n return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400)\n }\n\n if (!req.user) {\n return errorResponse(ErrorCodes.UNAUTHORIZED, 'Unauthorized', 401)\n }\n\n try {\n const body = (await req.json()) as SendBroadcastBody\n\n const validation = validateRequestBody(body)\n if (!validation.success) {\n logger.error(validation.error)\n return errorResponse(ErrorCodes.BAD_REQUEST, validation.error, 400)\n }\n\n const { broadcastId } = validation\n\n const broadcast = await findBroadcastById(payload, broadcastId)\n\n if (!broadcast) {\n logger.error(`Broadcast ${broadcastId} not found`)\n return errorResponse(ErrorCodes.BROADCAST_NOT_FOUND, 'Broadcast not found', 404)\n }\n\n if (broadcast.status !== 'draft') {\n logger.error(`Broadcast ${broadcastId} is not in draft status (current: ${broadcast.status})`)\n return errorResponse(\n ErrorCodes.VALIDATION_ERROR,\n 'Broadcast must be in draft status to send',\n 400,\n )\n }\n\n const whereClause = buildContactsWhereClause(broadcast)\n\n const contactsCount = await payload.db.count({\n collection: 'contacts',\n where: whereClause,\n })\n\n await payload.update({\n id: broadcastId,\n collection: 'broadcasts',\n data: {\n meta: {\n contactsCount: contactsCount.totalDocs,\n processedCount: 0,\n },\n status: 'sending',\n },\n })\n\n logger.info(`Broadcast ${broadcastId} queued for sending`)\n\n return successResponse({ message: 'Broadcast queued for sending' }, 200)\n } catch (err) {\n logger.error(err, 'Error occurred while queueing broadcast')\n return errorResponse(ErrorCodes.INTERNAL_ERROR, 'Error queueing broadcast', 500)\n }\n }\n}\n"],"names":["ErrorCodes","errorResponse","successResponse","validateRequestBody","body","broadcastId","error","success","findBroadcastById","payload","broadcast","findByID","id","collection","buildContactsWhereClause","whereClause","emailOptIn","equals","to","Array","isArray","tags","length","tagIds","map","t","in","sendBroadcastHandler","req","logger","json","BAD_REQUEST","user","UNAUTHORIZED","validation","BROADCAST_NOT_FOUND","status","VALIDATION_ERROR","contactsCount","db","count","where","update","data","meta","totalDocs","processedCount","info","message","err","INTERNAL_ERROR"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PayloadHandler } from 'payload';
|
|
2
|
+
import type { MobilizehubPluginConfig } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the send-test-email endpoint handler.
|
|
5
|
+
*
|
|
6
|
+
* Allows authenticated users to send a test email for a broadcast to a
|
|
7
|
+
* specified email address. Validates the broadcast exists and has all
|
|
8
|
+
* required fields before rendering and sending.
|
|
9
|
+
*/
|
|
10
|
+
export declare const sendTestEmailHandler: (config: Pick<MobilizehubPluginConfig, "email">) => PayloadHandler;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js';
|
|
2
|
+
import { formatFromAddress } from '../utils/email.js';
|
|
3
|
+
import { parseLexicalContent } from '../utils/lexical.js';
|
|
4
|
+
/**
|
|
5
|
+
* Validates the request body contains valid broadcastId and testEmail fields.
|
|
6
|
+
*/ function validateRequestBody(body) {
|
|
7
|
+
const { broadcastId, testEmail } = body;
|
|
8
|
+
if (typeof broadcastId !== 'number') {
|
|
9
|
+
return {
|
|
10
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
11
|
+
error: 'broadcastId is required and must be a number',
|
|
12
|
+
success: false
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (typeof testEmail !== 'string' || testEmail.length === 0) {
|
|
16
|
+
return {
|
|
17
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
18
|
+
error: 'testEmail is required',
|
|
19
|
+
success: false
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (!testEmail.includes('@')) {
|
|
23
|
+
return {
|
|
24
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
25
|
+
error: 'testEmail must be a valid email address',
|
|
26
|
+
success: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
broadcastId,
|
|
31
|
+
success: true,
|
|
32
|
+
testEmail
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validates that a broadcast has all required fields for sending.
|
|
37
|
+
*/ function validateBroadcastFields(broadcast) {
|
|
38
|
+
if (!broadcast.content) {
|
|
39
|
+
return {
|
|
40
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
41
|
+
error: 'Broadcast content is required',
|
|
42
|
+
success: false
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!broadcast.fromName) {
|
|
46
|
+
return {
|
|
47
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
48
|
+
error: 'Broadcast fromName is required',
|
|
49
|
+
success: false
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!broadcast.fromAddress) {
|
|
53
|
+
return {
|
|
54
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
55
|
+
error: 'Broadcast fromAddress is required',
|
|
56
|
+
success: false
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (!broadcast.subject) {
|
|
60
|
+
return {
|
|
61
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
62
|
+
error: 'Broadcast subject is required',
|
|
63
|
+
success: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
success: true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fetches a broadcast, renders its content, and sends a test email.
|
|
72
|
+
* Test emails do not include an unsubscribe token.
|
|
73
|
+
*/ async function sendTestEmail(req, broadcastId, testEmail, emailConfig) {
|
|
74
|
+
const { payload } = req;
|
|
75
|
+
const logger = payload.logger;
|
|
76
|
+
const broadcast = await payload.findByID({
|
|
77
|
+
id: broadcastId,
|
|
78
|
+
collection: 'broadcasts'
|
|
79
|
+
});
|
|
80
|
+
if (!broadcast) {
|
|
81
|
+
logger.error(`Broadcast with ID ${broadcastId} not found`);
|
|
82
|
+
return errorResponse(ErrorCodes.BROADCAST_NOT_FOUND, 'Broadcast not found', 404);
|
|
83
|
+
}
|
|
84
|
+
const validation = validateBroadcastFields(broadcast);
|
|
85
|
+
if (!validation.success) {
|
|
86
|
+
logger.error(`Broadcast ${broadcastId}: ${validation.error}`);
|
|
87
|
+
return errorResponse(validation.code, validation.error, 400);
|
|
88
|
+
}
|
|
89
|
+
const { render, sendEmail } = emailConfig(req);
|
|
90
|
+
const parsedContent = await parseLexicalContent(broadcast.content, payload.config);
|
|
91
|
+
const fromAddress = formatFromAddress(broadcast.fromName, broadcast.fromAddress);
|
|
92
|
+
const html = render({
|
|
93
|
+
from: fromAddress,
|
|
94
|
+
html: parsedContent.html,
|
|
95
|
+
markdown: parsedContent.markdown,
|
|
96
|
+
plainText: parsedContent.plainText,
|
|
97
|
+
subject: broadcast.subject,
|
|
98
|
+
to: testEmail,
|
|
99
|
+
token: ''
|
|
100
|
+
});
|
|
101
|
+
const emailInput = {
|
|
102
|
+
from: fromAddress,
|
|
103
|
+
html,
|
|
104
|
+
subject: broadcast.subject,
|
|
105
|
+
to: testEmail
|
|
106
|
+
};
|
|
107
|
+
await sendEmail(emailInput);
|
|
108
|
+
logger.info(`Test email sent to ${testEmail} for broadcast ${broadcastId}`);
|
|
109
|
+
return successResponse({
|
|
110
|
+
message: 'Test email sent successfully'
|
|
111
|
+
}, 200);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates the send-test-email endpoint handler.
|
|
115
|
+
*
|
|
116
|
+
* Allows authenticated users to send a test email for a broadcast to a
|
|
117
|
+
* specified email address. Validates the broadcast exists and has all
|
|
118
|
+
* required fields before rendering and sending.
|
|
119
|
+
*/ export const sendTestEmailHandler = (config)=>{
|
|
120
|
+
return async (req)=>{
|
|
121
|
+
const { payload } = req;
|
|
122
|
+
const logger = payload.logger;
|
|
123
|
+
if (!req.json) {
|
|
124
|
+
return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400);
|
|
125
|
+
}
|
|
126
|
+
if (!req.user) {
|
|
127
|
+
return errorResponse(ErrorCodes.UNAUTHORIZED, 'Unauthorized', 401);
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const body = await req.json();
|
|
131
|
+
const validation = validateRequestBody(body);
|
|
132
|
+
if (!validation.success) {
|
|
133
|
+
return errorResponse(validation.code, validation.error, 400);
|
|
134
|
+
}
|
|
135
|
+
return await sendTestEmail(req, validation.broadcastId, validation.testEmail, config.email);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error(error, 'Error sending test email');
|
|
138
|
+
return errorResponse(ErrorCodes.INTERNAL_ERROR, 'Error sending test email', 500);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=sendTestBroadcastHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/sendTestBroadcastHandler.ts"],"sourcesContent":["import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'\nimport type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport type { EmailMessage, MobilizehubPluginConfig } from '../types/index.js'\n\nimport { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js'\nimport { formatFromAddress } from '../utils/email.js'\nimport { parseLexicalContent } from '../utils/lexical.js'\n\ntype Broadcast = {\n content?: SerializedEditorState\n fromAddress?: string\n fromName?: string\n id: number | string\n subject?: string\n}\n\ntype SendTestEmailBody = {\n broadcastId?: unknown\n testEmail?: unknown\n}\n\ntype ValidationResult =\n | { broadcastId: number; success: true; testEmail: string }\n | { code: string; error: string; success: false }\n\n/**\n * Validates the request body contains valid broadcastId and testEmail fields.\n */\nfunction validateRequestBody(body: SendTestEmailBody): ValidationResult {\n const { broadcastId, testEmail } = body\n\n if (typeof broadcastId !== 'number') {\n return {\n code: ErrorCodes.VALIDATION_ERROR,\n error: 'broadcastId is required and must be a number',\n success: false,\n }\n }\n\n if (typeof testEmail !== 'string' || testEmail.length === 0) {\n return { code: ErrorCodes.VALIDATION_ERROR, error: 'testEmail is required', success: false }\n }\n\n if (!testEmail.includes('@')) {\n return {\n code: ErrorCodes.VALIDATION_ERROR,\n error: 'testEmail must be a valid email address',\n success: false,\n }\n }\n\n return { broadcastId, success: true, testEmail }\n}\n\n/**\n * Validates that a broadcast has all required fields for sending.\n */\nfunction validateBroadcastFields(\n broadcast: Broadcast,\n): { code: string; error: string; success: false } | { success: true } {\n if (!broadcast.content) {\n return {\n code: ErrorCodes.VALIDATION_ERROR,\n error: 'Broadcast content is required',\n success: false,\n }\n }\n\n if (!broadcast.fromName) {\n return {\n code: ErrorCodes.VALIDATION_ERROR,\n error: 'Broadcast fromName is required',\n success: false,\n }\n }\n\n if (!broadcast.fromAddress) {\n return {\n code: ErrorCodes.VALIDATION_ERROR,\n error: 'Broadcast fromAddress is required',\n success: false,\n }\n }\n\n if (!broadcast.subject) {\n return {\n code: ErrorCodes.VALIDATION_ERROR,\n error: 'Broadcast subject is required',\n success: false,\n }\n }\n\n return { success: true }\n}\n\n/**\n * Fetches a broadcast, renders its content, and sends a test email.\n * Test emails do not include an unsubscribe token.\n */\nasync function sendTestEmail(\n req: PayloadRequest,\n broadcastId: number,\n testEmail: string,\n emailConfig: MobilizehubPluginConfig['email'],\n): Promise<Response> {\n const { payload } = req\n const logger = payload.logger\n\n const broadcast = (await payload.findByID({\n id: broadcastId,\n collection: 'broadcasts',\n })) as Broadcast | null\n\n if (!broadcast) {\n logger.error(`Broadcast with ID ${broadcastId} not found`)\n return errorResponse(ErrorCodes.BROADCAST_NOT_FOUND, 'Broadcast not found', 404)\n }\n\n const validation = validateBroadcastFields(broadcast)\n if (!validation.success) {\n logger.error(`Broadcast ${broadcastId}: ${validation.error}`)\n return errorResponse(validation.code, validation.error, 400)\n }\n\n const { render, sendEmail } = emailConfig(req)\n\n const parsedContent = await parseLexicalContent(broadcast.content!, payload.config)\n\n const fromAddress = formatFromAddress(broadcast.fromName!, broadcast.fromAddress!)\n\n const html = render({\n from: fromAddress,\n html: parsedContent.html,\n markdown: parsedContent.markdown,\n plainText: parsedContent.plainText,\n subject: broadcast.subject!,\n to: testEmail,\n token: '',\n })\n\n const emailInput: Pick<EmailMessage, 'from' | 'html' | 'subject' | 'to'> = {\n from: fromAddress,\n html,\n subject: broadcast.subject!,\n to: testEmail,\n }\n\n await sendEmail(emailInput)\n\n logger.info(`Test email sent to ${testEmail} for broadcast ${broadcastId}`)\n\n return successResponse({ message: 'Test email sent successfully' }, 200)\n}\n\n/**\n * Creates the send-test-email endpoint handler.\n *\n * Allows authenticated users to send a test email for a broadcast to a\n * specified email address. Validates the broadcast exists and has all\n * required fields before rendering and sending.\n */\nexport const sendTestEmailHandler = (\n config: Pick<MobilizehubPluginConfig, 'email'>,\n): PayloadHandler => {\n return async (req) => {\n const { payload } = req\n const logger = payload.logger\n\n if (!req.json) {\n return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400)\n }\n\n if (!req.user) {\n return errorResponse(ErrorCodes.UNAUTHORIZED, 'Unauthorized', 401)\n }\n\n try {\n const body = (await req.json()) as SendTestEmailBody\n\n const validation = validateRequestBody(body)\n if (!validation.success) {\n return errorResponse(validation.code, validation.error, 400)\n }\n\n return await sendTestEmail(req, validation.broadcastId, validation.testEmail, config.email)\n } catch (error) {\n logger.error(error as Error, 'Error sending test email')\n return errorResponse(ErrorCodes.INTERNAL_ERROR, 'Error sending test email', 500)\n }\n }\n}\n"],"names":["ErrorCodes","errorResponse","successResponse","formatFromAddress","parseLexicalContent","validateRequestBody","body","broadcastId","testEmail","code","VALIDATION_ERROR","error","success","length","includes","validateBroadcastFields","broadcast","content","fromName","fromAddress","subject","sendTestEmail","req","emailConfig","payload","logger","findByID","id","collection","BROADCAST_NOT_FOUND","validation","render","sendEmail","parsedContent","config","html","from","markdown","plainText","to","token","emailInput","info","message","sendTestEmailHandler","json","BAD_REQUEST","user","UNAUTHORIZED","email","INTERNAL_ERROR"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PayloadHandler } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the unsubscribe endpoint handler.
|
|
4
|
+
*
|
|
5
|
+
* Processes unsubscribe requests by validating a signed token, looking up
|
|
6
|
+
* the associated email and contact, then setting the contact's emailOptIn
|
|
7
|
+
* to false. Handles already-unsubscribed contacts gracefully.
|
|
8
|
+
*/
|
|
9
|
+
export declare const unsubscribeHandler: () => PayloadHandler;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js';
|
|
3
|
+
import { verifyUnsubscribeToken } from '../utils/unsubscribe-token.js';
|
|
4
|
+
/**
|
|
5
|
+
* Schema for validating unsubscribe request body.
|
|
6
|
+
*/ const UnsubscribeBodySchema = z.object({
|
|
7
|
+
token: z.string().min(1, {
|
|
8
|
+
message: 'Token is required'
|
|
9
|
+
})
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Validates the request body against the schema.
|
|
13
|
+
* Returns typed data on success, or error message on failure.
|
|
14
|
+
*/ function validateRequestBody(body) {
|
|
15
|
+
const result = UnsubscribeBodySchema.safeParse(body);
|
|
16
|
+
if (!result.success) {
|
|
17
|
+
const firstError = result.error.issues[0]?.message || 'Invalid request body';
|
|
18
|
+
return {
|
|
19
|
+
error: firstError,
|
|
20
|
+
success: false
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
data: result.data,
|
|
25
|
+
success: true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a token has expired by comparing the expiration date to now.
|
|
30
|
+
*/ function isTokenExpired(expiresAt) {
|
|
31
|
+
return new Date(expiresAt) < new Date();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fetches an unsubscribe token record by ID.
|
|
35
|
+
* Returns null if the record doesn't exist.
|
|
36
|
+
*/ async function findUnsubscribeRecord(payload, tokenId) {
|
|
37
|
+
try {
|
|
38
|
+
const record = await payload.findByID({
|
|
39
|
+
id: tokenId,
|
|
40
|
+
collection: 'emailUnsubscribeTokens'
|
|
41
|
+
});
|
|
42
|
+
return record;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fetches an email document by ID.
|
|
49
|
+
* Returns null if the email doesn't exist.
|
|
50
|
+
*/ async function findEmailById(payload, emailId) {
|
|
51
|
+
try {
|
|
52
|
+
const email = await payload.findByID({
|
|
53
|
+
id: emailId,
|
|
54
|
+
collection: 'emails'
|
|
55
|
+
});
|
|
56
|
+
return email;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Finds a contact by their email address.
|
|
63
|
+
* Returns null if no matching contact exists.
|
|
64
|
+
*/ async function findContactByEmail(payload, emailAddress) {
|
|
65
|
+
const result = await payload.find({
|
|
66
|
+
collection: 'contacts',
|
|
67
|
+
limit: 1,
|
|
68
|
+
where: {
|
|
69
|
+
email: {
|
|
70
|
+
equals: emailAddress
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return result.docs[0] || null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Sets a contact's emailOptIn to false to unsubscribe them from emails.
|
|
78
|
+
*/ async function unsubscribeContact(payload, contactId) {
|
|
79
|
+
await payload.update({
|
|
80
|
+
id: contactId,
|
|
81
|
+
collection: 'contacts',
|
|
82
|
+
data: {
|
|
83
|
+
emailOptIn: false
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates the unsubscribe endpoint handler.
|
|
89
|
+
*
|
|
90
|
+
* Processes unsubscribe requests by validating a signed token, looking up
|
|
91
|
+
* the associated email and contact, then setting the contact's emailOptIn
|
|
92
|
+
* to false. Handles already-unsubscribed contacts gracefully.
|
|
93
|
+
*/ export const unsubscribeHandler = ()=>{
|
|
94
|
+
return async (req)=>{
|
|
95
|
+
const { payload } = req;
|
|
96
|
+
const logger = payload.logger;
|
|
97
|
+
if (!req.json) {
|
|
98
|
+
return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const body = await req.json();
|
|
102
|
+
const validation = validateRequestBody(body);
|
|
103
|
+
if (!validation.success) {
|
|
104
|
+
return errorResponse(ErrorCodes.VALIDATION_ERROR, validation.error, 400);
|
|
105
|
+
}
|
|
106
|
+
const { token } = validation.data;
|
|
107
|
+
const verificationResult = verifyUnsubscribeToken({
|
|
108
|
+
token
|
|
109
|
+
});
|
|
110
|
+
if (!verificationResult) {
|
|
111
|
+
return errorResponse(ErrorCodes.TOKEN_INVALID, 'Invalid or expired token', 400);
|
|
112
|
+
}
|
|
113
|
+
const { tokenId } = verificationResult;
|
|
114
|
+
const unsubscribeRecord = await findUnsubscribeRecord(payload, tokenId);
|
|
115
|
+
if (!unsubscribeRecord) {
|
|
116
|
+
return errorResponse(ErrorCodes.TOKEN_INVALID, 'Invalid token', 400);
|
|
117
|
+
}
|
|
118
|
+
if (unsubscribeRecord.expiresAt && isTokenExpired(unsubscribeRecord.expiresAt)) {
|
|
119
|
+
return errorResponse(ErrorCodes.TOKEN_EXPIRED, 'Token has expired', 400);
|
|
120
|
+
}
|
|
121
|
+
if (!unsubscribeRecord.emailId) {
|
|
122
|
+
logger.error(`Unsubscribe record ${tokenId} has no emailId`);
|
|
123
|
+
return errorResponse(ErrorCodes.VALIDATION_ERROR, 'Invalid unsubscribe record', 400);
|
|
124
|
+
}
|
|
125
|
+
const email = await findEmailById(payload, unsubscribeRecord.emailId);
|
|
126
|
+
if (!email) {
|
|
127
|
+
logger.error(`Email ${unsubscribeRecord.emailId} not found for unsubscribe record ${tokenId}`);
|
|
128
|
+
return errorResponse(ErrorCodes.NOT_FOUND, 'Associated email not found', 404);
|
|
129
|
+
}
|
|
130
|
+
const contact = await findContactByEmail(payload, email.to);
|
|
131
|
+
if (!contact) {
|
|
132
|
+
logger.error(`Contact not found for email address ${email.to}`);
|
|
133
|
+
return errorResponse(ErrorCodes.CONTACT_NOT_FOUND, 'Associated contact not found', 404);
|
|
134
|
+
}
|
|
135
|
+
if (contact.emailOptIn === false) {
|
|
136
|
+
logger.info(`Contact ${contact.id} (${email.to}) is already unsubscribed`);
|
|
137
|
+
return successResponse({
|
|
138
|
+
message: 'Already unsubscribed'
|
|
139
|
+
}, 200);
|
|
140
|
+
}
|
|
141
|
+
await unsubscribeContact(payload, contact.id);
|
|
142
|
+
logger.info(`Contact ${contact.id} (${email.to}) successfully unsubscribed`);
|
|
143
|
+
return successResponse({
|
|
144
|
+
message: 'Successfully unsubscribed'
|
|
145
|
+
}, 200);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.error(error, 'Error processing unsubscribe request');
|
|
148
|
+
return errorResponse(ErrorCodes.INTERNAL_ERROR, 'Internal Server Error', 500);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=unsubscribeHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/unsubscribeHandler.ts"],"sourcesContent":["import type { Payload, PayloadHandler } from 'payload'\n\nimport z from 'zod'\n\nimport type { Contact, UnsubscribeTokenRecord } from '../types/index.js'\n\nimport { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js'\nimport { verifyUnsubscribeToken } from '../utils/unsubscribe-token.js'\n\n/**\n * Schema for validating unsubscribe request body.\n */\nconst UnsubscribeBodySchema = z.object({\n token: z.string().min(1, { message: 'Token is required' }),\n})\n\n/**\n * Validates the request body against the schema.\n * Returns typed data on success, or error message on failure.\n */\nfunction validateRequestBody(body: unknown) {\n const result = UnsubscribeBodySchema.safeParse(body)\n\n if (!result.success) {\n const firstError = result.error.issues[0]?.message || 'Invalid request body'\n return { error: firstError, success: false as const }\n }\n\n return { data: result.data, success: true as const }\n}\n\n/**\n * Checks if a token has expired by comparing the expiration date to now.\n */\nfunction isTokenExpired(expiresAt: string): boolean {\n return new Date(expiresAt) < new Date()\n}\n\n/**\n * Fetches an unsubscribe token record by ID.\n * Returns null if the record doesn't exist.\n */\nasync function findUnsubscribeRecord(\n payload: Payload,\n tokenId: string,\n): Promise<null | UnsubscribeTokenRecord> {\n try {\n const record = await payload.findByID({\n id: tokenId,\n collection: 'emailUnsubscribeTokens',\n })\n return record as null | UnsubscribeTokenRecord\n } catch {\n return null\n }\n}\n\n/**\n * Fetches an email document by ID.\n * Returns null if the email doesn't exist.\n */\nasync function findEmailById(\n payload: Payload,\n emailId: number | string,\n): Promise<{ to: string } | null> {\n try {\n const email = await payload.findByID({\n id: emailId,\n collection: 'emails',\n })\n return email as unknown as { to: string } | null\n } catch {\n return null\n }\n}\n\n/**\n * Finds a contact by their email address.\n * Returns null if no matching contact exists.\n */\nasync function findContactByEmail(payload: Payload, emailAddress: string): Promise<Contact | null> {\n const result = await payload.find({\n collection: 'contacts',\n limit: 1,\n where: {\n email: { equals: emailAddress },\n },\n })\n\n return (result.docs[0] as Contact) || null\n}\n\n/**\n * Sets a contact's emailOptIn to false to unsubscribe them from emails.\n */\nasync function unsubscribeContact(payload: Payload, contactId: number | string): Promise<void> {\n await payload.update({\n id: contactId,\n collection: 'contacts',\n data: {\n emailOptIn: false,\n },\n })\n}\n\n/**\n * Creates the unsubscribe endpoint handler.\n *\n * Processes unsubscribe requests by validating a signed token, looking up\n * the associated email and contact, then setting the contact's emailOptIn\n * to false. Handles already-unsubscribed contacts gracefully.\n */\nexport const unsubscribeHandler = (): PayloadHandler => {\n return async (req) => {\n const { payload } = req\n const logger = payload.logger\n\n if (!req.json) {\n return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400)\n }\n\n try {\n const body = await req.json()\n\n const validation = validateRequestBody(body)\n if (!validation.success) {\n return errorResponse(ErrorCodes.VALIDATION_ERROR, validation.error, 400)\n }\n\n const { token } = validation.data\n\n const verificationResult = verifyUnsubscribeToken({ token })\n\n if (!verificationResult) {\n return errorResponse(ErrorCodes.TOKEN_INVALID, 'Invalid or expired token', 400)\n }\n\n const { tokenId } = verificationResult\n\n const unsubscribeRecord = await findUnsubscribeRecord(payload, tokenId)\n\n if (!unsubscribeRecord) {\n return errorResponse(ErrorCodes.TOKEN_INVALID, 'Invalid token', 400)\n }\n\n if (unsubscribeRecord.expiresAt && isTokenExpired(unsubscribeRecord.expiresAt)) {\n return errorResponse(ErrorCodes.TOKEN_EXPIRED, 'Token has expired', 400)\n }\n\n if (!unsubscribeRecord.emailId) {\n logger.error(`Unsubscribe record ${tokenId} has no emailId`)\n return errorResponse(ErrorCodes.VALIDATION_ERROR, 'Invalid unsubscribe record', 400)\n }\n\n const email = await findEmailById(payload, unsubscribeRecord.emailId)\n\n if (!email) {\n logger.error(\n `Email ${unsubscribeRecord.emailId} not found for unsubscribe record ${tokenId}`,\n )\n return errorResponse(ErrorCodes.NOT_FOUND, 'Associated email not found', 404)\n }\n\n const contact = await findContactByEmail(payload, email.to)\n\n if (!contact) {\n logger.error(`Contact not found for email address ${email.to}`)\n return errorResponse(ErrorCodes.CONTACT_NOT_FOUND, 'Associated contact not found', 404)\n }\n\n if (contact.emailOptIn === false) {\n logger.info(`Contact ${contact.id} (${email.to}) is already unsubscribed`)\n return successResponse({ message: 'Already unsubscribed' }, 200)\n }\n\n await unsubscribeContact(payload, contact.id)\n\n logger.info(`Contact ${contact.id} (${email.to}) successfully unsubscribed`)\n\n return successResponse({ message: 'Successfully unsubscribed' }, 200)\n } catch (error) {\n logger.error(error as Error, 'Error processing unsubscribe request')\n return errorResponse(ErrorCodes.INTERNAL_ERROR, 'Internal Server Error', 500)\n }\n }\n}\n"],"names":["z","ErrorCodes","errorResponse","successResponse","verifyUnsubscribeToken","UnsubscribeBodySchema","object","token","string","min","message","validateRequestBody","body","result","safeParse","success","firstError","error","issues","data","isTokenExpired","expiresAt","Date","findUnsubscribeRecord","payload","tokenId","record","findByID","id","collection","findEmailById","emailId","email","findContactByEmail","emailAddress","find","limit","where","equals","docs","unsubscribeContact","contactId","update","emailOptIn","unsubscribeHandler","req","logger","json","BAD_REQUEST","validation","VALIDATION_ERROR","verificationResult","TOKEN_INVALID","unsubscribeRecord","TOKEN_EXPIRED","NOT_FOUND","contact","to","CONTACT_NOT_FOUND","info","INTERNAL_ERROR"],"mappings":"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"}
|
package/dist/exports/client.d.ts
CHANGED
package/dist/exports/client.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
+
export { SendBroadcastModal } from '../components/broadcast-send-modal.js';
|
|
2
|
+
export { SendTestBroadcastDrawer } from '../components/broadcast-send-test-drawer.js';
|
|
3
|
+
export { EmailPreviewField } from '../components/email-preview.js';
|
|
1
4
|
|
|
2
5
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/client.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { SendBroadcastModal } from '../components/broadcast-send-modal.js'\nexport { SendTestBroadcastDrawer } from '../components/broadcast-send-test-drawer.js'\nexport { EmailPreviewField } from '../components/email-preview.js'\n"],"names":["SendBroadcastModal","SendTestBroadcastDrawer","EmailPreviewField"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,wCAAuC;AAC1E,SAASC,uBAAuB,QAAQ,8CAA6C;AACrF,SAASC,iBAAiB,QAAQ,iCAAgC"}
|
package/dist/exports/rsc.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { MetricsCards } from '../components/broadcast-metrics-card.js';
|
|
2
|
+
export { EmailActivityField } from '../components/email-activity.js';
|
package/dist/exports/rsc.js
CHANGED
package/dist/exports/rsc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/rsc.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["export { MetricsCards } from '../components/broadcast-metrics-card.js'\nexport { EmailActivityField } from '../components/email-activity.js'\n"],"names":["MetricsCards","EmailActivityField"],"mappings":"AAAA,SAASA,YAAY,QAAQ,0CAAyC;AACtE,SAASC,kBAAkB,QAAQ,kCAAiC"}
|