@actuate-media/cms-core 0.32.0 → 0.33.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/__tests__/api/form-webhooks.test.d.ts +2 -0
- package/dist/__tests__/api/form-webhooks.test.d.ts.map +1 -0
- package/dist/__tests__/api/form-webhooks.test.js +162 -0
- package/dist/__tests__/api/form-webhooks.test.js.map +1 -0
- package/dist/__tests__/forms/notify.test.d.ts +2 -0
- package/dist/__tests__/forms/notify.test.d.ts.map +1 -0
- package/dist/__tests__/forms/notify.test.js +114 -0
- package/dist/__tests__/forms/notify.test.js.map +1 -0
- package/dist/__tests__/forms/webhooks.test.d.ts +2 -0
- package/dist/__tests__/forms/webhooks.test.d.ts.map +1 -0
- package/dist/__tests__/forms/webhooks.test.js +203 -0
- package/dist/__tests__/forms/webhooks.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +145 -0
- package/dist/api/handlers.js.map +1 -1
- package/dist/config/types.d.ts +11 -6
- package/dist/config/types.d.ts.map +1 -1
- package/dist/forms/entries.d.ts.map +1 -1
- package/dist/forms/entries.js +16 -1
- package/dist/forms/entries.js.map +1 -1
- package/dist/forms/index.d.ts +3 -1
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +3 -1
- package/dist/forms/index.js.map +1 -1
- package/dist/forms/notify.d.ts +37 -0
- package/dist/forms/notify.d.ts.map +1 -0
- package/dist/forms/notify.js +198 -0
- package/dist/forms/notify.js.map +1 -0
- package/dist/forms/submission.d.ts +21 -0
- package/dist/forms/submission.d.ts.map +1 -1
- package/dist/forms/submission.js +59 -5
- package/dist/forms/submission.js.map +1 -1
- package/dist/forms/webhooks.d.ts +54 -0
- package/dist/forms/webhooks.d.ts.map +1 -0
- package/dist/forms/webhooks.js +292 -0
- package/dist/forms/webhooks.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api/handlers.js
CHANGED
|
@@ -28,6 +28,8 @@ import { listEntries as listEntriesService, getEntryWithSchema as getEntryWithSc
|
|
|
28
28
|
import { processSubmission, buildPublicForm, SubmissionValidationError, } from '../forms/submission.js';
|
|
29
29
|
import { validateSubmissionFiles, FORM_UPLOAD_ALLOWED_MIME_TYPES, DEFAULT_MAX_FILE_BYTES, } from '../forms/files.js';
|
|
30
30
|
import { resolveSubmissionFields } from '../forms/submission.js';
|
|
31
|
+
import { createFormWebhook, listFormWebhooks, updateFormWebhook, deleteFormWebhook, testFormWebhook, FormWebhookError, } from '../forms/webhooks.js';
|
|
32
|
+
import { sendFormNotifications } from '../forms/notify.js';
|
|
31
33
|
import { checkForUpdates } from '../upgrade/version-check.js';
|
|
32
34
|
import { createUpgradePR } from '../upgrade/upgrade-pr.js';
|
|
33
35
|
import { encryptField, decryptField } from '../security/encrypted-fields.js';
|
|
@@ -290,6 +292,8 @@ function mapFormError(err) {
|
|
|
290
292
|
if (err instanceof FormHasSubmissionsError) {
|
|
291
293
|
return json({ error: err.message, code: 'FORM_HAS_SUBMISSIONS', submissionCount: err.submissionCount }, 409);
|
|
292
294
|
}
|
|
295
|
+
if (err instanceof FormWebhookError)
|
|
296
|
+
return errorResponse(err.message, err.status);
|
|
293
297
|
return null;
|
|
294
298
|
}
|
|
295
299
|
function parseFormsQuery(url) {
|
|
@@ -4720,6 +4724,147 @@ export function registerCMSRoutes(router) {
|
|
|
4720
4724
|
return mapFormError(err) ?? internalError(err);
|
|
4721
4725
|
}
|
|
4722
4726
|
});
|
|
4727
|
+
// ── Per-form webhooks (CRUD + test) ──────────────────────────────────────
|
|
4728
|
+
router.get('/forms/:id/webhooks', async (request, params) => {
|
|
4729
|
+
try {
|
|
4730
|
+
const auth = await requireAuth(request);
|
|
4731
|
+
if (auth.error)
|
|
4732
|
+
return auth.error;
|
|
4733
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
4734
|
+
if (roleErr)
|
|
4735
|
+
return roleErr;
|
|
4736
|
+
const form = await getFormByIdService(params.id);
|
|
4737
|
+
if (!form)
|
|
4738
|
+
return errorResponse('Form not found', 404);
|
|
4739
|
+
const webhooks = await listFormWebhooks(params.id);
|
|
4740
|
+
return json({ data: webhooks });
|
|
4741
|
+
}
|
|
4742
|
+
catch (err) {
|
|
4743
|
+
return mapFormError(err) ?? internalError(err);
|
|
4744
|
+
}
|
|
4745
|
+
});
|
|
4746
|
+
router.post('/forms/:id/webhooks', async (request, params) => {
|
|
4747
|
+
try {
|
|
4748
|
+
const auth = await requireAuth(request);
|
|
4749
|
+
if (auth.error)
|
|
4750
|
+
return auth.error;
|
|
4751
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
4752
|
+
if (roleErr)
|
|
4753
|
+
return roleErr;
|
|
4754
|
+
const form = await getFormByIdService(params.id);
|
|
4755
|
+
if (!form)
|
|
4756
|
+
return errorResponse('Form not found', 404);
|
|
4757
|
+
const body = (await request.json().catch(() => ({})));
|
|
4758
|
+
if (!body.url || typeof body.url !== 'string') {
|
|
4759
|
+
return errorResponse('A webhook url is required', 400);
|
|
4760
|
+
}
|
|
4761
|
+
const ctx = buildActionContext(auth.session, db());
|
|
4762
|
+
const webhook = await createFormWebhook(ctx, params.id, body);
|
|
4763
|
+
// The plaintext secret is returned exactly once here, on creation.
|
|
4764
|
+
return json({ data: webhook }, 201);
|
|
4765
|
+
}
|
|
4766
|
+
catch (err) {
|
|
4767
|
+
return mapFormError(err) ?? internalError(err);
|
|
4768
|
+
}
|
|
4769
|
+
});
|
|
4770
|
+
router.patch('/forms/:id/webhooks/:webhookId', async (request, params) => {
|
|
4771
|
+
try {
|
|
4772
|
+
const auth = await requireAuth(request);
|
|
4773
|
+
if (auth.error)
|
|
4774
|
+
return auth.error;
|
|
4775
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
4776
|
+
if (roleErr)
|
|
4777
|
+
return roleErr;
|
|
4778
|
+
const body = (await request.json().catch(() => ({})));
|
|
4779
|
+
const ctx = buildActionContext(auth.session, db());
|
|
4780
|
+
const webhook = await updateFormWebhook(ctx, params.webhookId, body);
|
|
4781
|
+
return json({ data: webhook });
|
|
4782
|
+
}
|
|
4783
|
+
catch (err) {
|
|
4784
|
+
return mapFormError(err) ?? internalError(err);
|
|
4785
|
+
}
|
|
4786
|
+
});
|
|
4787
|
+
router.delete('/forms/:id/webhooks/:webhookId', async (request, params) => {
|
|
4788
|
+
try {
|
|
4789
|
+
const auth = await requireAuth(request);
|
|
4790
|
+
if (auth.error)
|
|
4791
|
+
return auth.error;
|
|
4792
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
4793
|
+
if (roleErr)
|
|
4794
|
+
return roleErr;
|
|
4795
|
+
const ctx = buildActionContext(auth.session, db());
|
|
4796
|
+
await deleteFormWebhook(ctx, params.webhookId);
|
|
4797
|
+
return json({ data: { success: true } });
|
|
4798
|
+
}
|
|
4799
|
+
catch (err) {
|
|
4800
|
+
return mapFormError(err) ?? internalError(err);
|
|
4801
|
+
}
|
|
4802
|
+
});
|
|
4803
|
+
router.post('/forms/:id/webhooks/:webhookId/test', async (request, params) => {
|
|
4804
|
+
try {
|
|
4805
|
+
const auth = await requireAuth(request);
|
|
4806
|
+
if (auth.error)
|
|
4807
|
+
return auth.error;
|
|
4808
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
4809
|
+
if (roleErr)
|
|
4810
|
+
return roleErr;
|
|
4811
|
+
const ctx = buildActionContext(auth.session, db());
|
|
4812
|
+
const result = await testFormWebhook(ctx, params.webhookId);
|
|
4813
|
+
return json({ data: result });
|
|
4814
|
+
}
|
|
4815
|
+
catch (err) {
|
|
4816
|
+
return mapFormError(err) ?? internalError(err);
|
|
4817
|
+
}
|
|
4818
|
+
});
|
|
4819
|
+
// ── Test notification email ──────────────────────────────────────────────
|
|
4820
|
+
router.post('/forms/:id/notifications/test', async (request, params) => {
|
|
4821
|
+
try {
|
|
4822
|
+
const auth = await requireAuth(request);
|
|
4823
|
+
if (auth.error)
|
|
4824
|
+
return auth.error;
|
|
4825
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
4826
|
+
if (roleErr)
|
|
4827
|
+
return roleErr;
|
|
4828
|
+
const form = await getFormByIdService(params.id);
|
|
4829
|
+
if (!form)
|
|
4830
|
+
return errorResponse('Form not found', 404);
|
|
4831
|
+
const settings = await getFormNotificationSettingsService(params.id);
|
|
4832
|
+
const primaryRecipient = (settings?.recipients ?? [])[0];
|
|
4833
|
+
if (!settings?.enabled || !primaryRecipient) {
|
|
4834
|
+
return errorResponse('Configure at least one recipient before sending a test', 400);
|
|
4835
|
+
}
|
|
4836
|
+
// Sample values so operators see a realistic email. The autoresponder is
|
|
4837
|
+
// suppressed for tests so we never email an arbitrary sample address.
|
|
4838
|
+
const fields = form.fields ?? [];
|
|
4839
|
+
const sampleData = {};
|
|
4840
|
+
for (const field of fields) {
|
|
4841
|
+
if (field.type === 'honeypot' || field.type === 'divider' || field.type === 'richtext') {
|
|
4842
|
+
continue;
|
|
4843
|
+
}
|
|
4844
|
+
if (field.type === 'email')
|
|
4845
|
+
sampleData[field.key] = primaryRecipient;
|
|
4846
|
+
else if (field.type === 'checkbox' || field.type === 'consent')
|
|
4847
|
+
sampleData[field.key] = true;
|
|
4848
|
+
else
|
|
4849
|
+
sampleData[field.key] = `Sample ${field.label || field.key}`;
|
|
4850
|
+
}
|
|
4851
|
+
const ctx = {
|
|
4852
|
+
formId: form.id,
|
|
4853
|
+
formName: form.name,
|
|
4854
|
+
entryNumber: null,
|
|
4855
|
+
submittedAt: new Date().toISOString(),
|
|
4856
|
+
fields,
|
|
4857
|
+
data: sampleData,
|
|
4858
|
+
senderName: 'Test Submission',
|
|
4859
|
+
senderEmail: primaryRecipient,
|
|
4860
|
+
};
|
|
4861
|
+
const result = await sendFormNotifications({ ...settings, autoresponderEnabled: false }, ctx);
|
|
4862
|
+
return json({ data: result });
|
|
4863
|
+
}
|
|
4864
|
+
catch (err) {
|
|
4865
|
+
return mapFormError(err) ?? internalError(err);
|
|
4866
|
+
}
|
|
4867
|
+
});
|
|
4723
4868
|
// ---------------------------------------------------------------------------
|
|
4724
4869
|
// Redirects routes
|
|
4725
4870
|
// ---------------------------------------------------------------------------
|