@actuate-media/cms-core 0.22.0 → 0.24.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/collections-discovery.test.d.ts +2 -0
- package/dist/__tests__/api/collections-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/api/collections-discovery.test.js +129 -0
- package/dist/__tests__/api/collections-discovery.test.js.map +1 -0
- package/dist/__tests__/api/notifications-routes.test.js +45 -0
- package/dist/__tests__/api/notifications-routes.test.js.map +1 -1
- package/dist/__tests__/realtime/notification-bus.test.d.ts +2 -0
- package/dist/__tests__/realtime/notification-bus.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/notification-bus.test.js +79 -0
- package/dist/__tests__/realtime/notification-bus.test.js.map +1 -0
- package/dist/__tests__/realtime/notification-stream.test.d.ts +2 -0
- package/dist/__tests__/realtime/notification-stream.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/notification-stream.test.js +157 -0
- package/dist/__tests__/realtime/notification-stream.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +79 -3
- package/dist/api/handlers.js.map +1 -1
- package/dist/preview/sse-stream.d.ts +2 -5
- package/dist/preview/sse-stream.d.ts.map +1 -1
- package/dist/preview/sse-stream.js +7 -16
- package/dist/preview/sse-stream.js.map +1 -1
- package/dist/realtime/index.d.ts +3 -0
- package/dist/realtime/index.d.ts.map +1 -1
- package/dist/realtime/index.js +9 -0
- package/dist/realtime/index.js.map +1 -1
- package/dist/realtime/notification-bus.d.ts +60 -0
- package/dist/realtime/notification-bus.d.ts.map +1 -0
- package/dist/realtime/notification-bus.js +77 -0
- package/dist/realtime/notification-bus.js.map +1 -0
- package/dist/realtime/notification-stream.d.ts +50 -0
- package/dist/realtime/notification-stream.d.ts.map +1 -0
- package/dist/realtime/notification-stream.js +148 -0
- package/dist/realtime/notification-stream.js.map +1 -0
- package/dist/realtime/sse.d.ts +20 -0
- package/dist/realtime/sse.d.ts.map +1 -0
- package/dist/realtime/sse.js +31 -0
- package/dist/realtime/sse.js.map +1 -0
- package/package.json +1 -1
package/dist/api/handlers.js
CHANGED
|
@@ -17,6 +17,8 @@ import { createPreviewStreamResponse } from '../preview/sse-stream.js';
|
|
|
17
17
|
import { notifyPreviewUpdate } from '../preview/event-bus.js';
|
|
18
18
|
import { createComment as createCommentService, deleteComment as deleteCommentService, listComments as listCommentsService, reopenComment as reopenCommentService, resolveComment as resolveCommentService, updateComment as updateCommentService, } from '../realtime/comments.js';
|
|
19
19
|
import { createNotification, excerptForNotification, extractMentionedUserIds, listForUser as listNotificationsForUser, markAllRead as markAllNotificationsRead, markRead as markNotificationRead, unreadCount as notificationsUnreadCount, } from '../realtime/notifications.js';
|
|
20
|
+
import { publishNotification } from '../realtime/notification-bus.js';
|
|
21
|
+
import { createNotificationStreamResponse } from '../realtime/notification-stream.js';
|
|
20
22
|
import { schedulingCronHandler } from '../scheduling/index.js';
|
|
21
23
|
import { isAuthorizedCronRequest, processCleanup, processSeoScan } from '../cron/index.js';
|
|
22
24
|
import { verifyCaptcha, getCaptchaConfig } from '../security/captcha.js';
|
|
@@ -776,6 +778,44 @@ export function registerCMSRoutes(router) {
|
|
|
776
778
|
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
|
|
777
779
|
});
|
|
778
780
|
});
|
|
781
|
+
// ---------------------------------------------------------------------------
|
|
782
|
+
// Collection discovery (Phase 7 PR F)
|
|
783
|
+
//
|
|
784
|
+
// Lightweight enumeration sourced directly from `getActuateConfig()` so
|
|
785
|
+
// agents and the MCP server's `list_collections` tool don't depend on
|
|
786
|
+
// OpenAPI spec generation succeeding. OpenAPI is still the source of
|
|
787
|
+
// truth for full per-field JSON Schemas; this endpoint trades depth
|
|
788
|
+
// for reliability and surfaces just enough metadata for the agent to
|
|
789
|
+
// plan a follow-up call (create_document, list_documents, etc.).
|
|
790
|
+
//
|
|
791
|
+
// Auth: unauthenticated, like /openapi.json — the response contains
|
|
792
|
+
// only schema-shape metadata that is already public via OpenAPI.
|
|
793
|
+
// ---------------------------------------------------------------------------
|
|
794
|
+
router.get('/collections', async () => {
|
|
795
|
+
const config = getActuateConfig();
|
|
796
|
+
if (!config)
|
|
797
|
+
return errorResponse('CMS not configured', 500);
|
|
798
|
+
const collections = Object.entries(config.collections ?? {}).map(([slug, def]) => {
|
|
799
|
+
const fields = (def?.fields ?? {});
|
|
800
|
+
// Reduce each field to `{ name, type, required }` — enough for the
|
|
801
|
+
// agent to plan a create/update without sending the full validator.
|
|
802
|
+
const fieldSummary = Object.entries(fields).map(([name, fieldDef]) => ({
|
|
803
|
+
name,
|
|
804
|
+
type: typeof fieldDef?.type === 'string' ? fieldDef.type : 'unknown',
|
|
805
|
+
required: fieldDef?.required === true,
|
|
806
|
+
}));
|
|
807
|
+
return {
|
|
808
|
+
slug,
|
|
809
|
+
labels: def?.labels ?? { singular: slug, plural: slug },
|
|
810
|
+
type: def?.type ?? 'page',
|
|
811
|
+
urlPrefix: def?.urlPrefix ?? slug,
|
|
812
|
+
hidden: def?.admin?.hidden === true,
|
|
813
|
+
fieldCount: fieldSummary.length,
|
|
814
|
+
fields: fieldSummary,
|
|
815
|
+
};
|
|
816
|
+
});
|
|
817
|
+
return json({ data: collections });
|
|
818
|
+
});
|
|
779
819
|
router.get('/docs', async () => {
|
|
780
820
|
const html = `<!DOCTYPE html>
|
|
781
821
|
<html>
|
|
@@ -4672,7 +4712,7 @@ export function registerCMSRoutes(router) {
|
|
|
4672
4712
|
if (opts.parentId) {
|
|
4673
4713
|
const root = await findCommentRecipient(opts.parentId);
|
|
4674
4714
|
if (root && root.userId && root.userId !== opts.actorId) {
|
|
4675
|
-
await createNotification(ndb, {
|
|
4715
|
+
const result = await createNotification(ndb, {
|
|
4676
4716
|
userId: root.userId,
|
|
4677
4717
|
documentId: opts.documentId,
|
|
4678
4718
|
commentId: opts.commentId,
|
|
@@ -4683,12 +4723,21 @@ export function registerCMSRoutes(router) {
|
|
|
4683
4723
|
excerpt: excerptForNotification(opts.body),
|
|
4684
4724
|
},
|
|
4685
4725
|
});
|
|
4726
|
+
// Push the new DTO to any active SSE subscribers for this user
|
|
4727
|
+
// so the bell badge bumps in real time. We only publish AFTER
|
|
4728
|
+
// the DB insert succeeds — otherwise an SSE client would see a
|
|
4729
|
+
// ghost notification that doesn't exist on a subsequent
|
|
4730
|
+
// refresh. Failure to publish (no subscribers, or bus error)
|
|
4731
|
+
// is non-fatal: the poll fallback in the admin UI still
|
|
4732
|
+
// converges.
|
|
4733
|
+
if (result.ok)
|
|
4734
|
+
publishNotification(result.value);
|
|
4686
4735
|
}
|
|
4687
4736
|
}
|
|
4688
4737
|
for (const userId of extractMentionedUserIds(opts.body)) {
|
|
4689
4738
|
if (userId === opts.actorId)
|
|
4690
4739
|
continue;
|
|
4691
|
-
await createNotification(ndb, {
|
|
4740
|
+
const result = await createNotification(ndb, {
|
|
4692
4741
|
userId,
|
|
4693
4742
|
documentId: opts.documentId,
|
|
4694
4743
|
commentId: opts.commentId,
|
|
@@ -4699,6 +4748,8 @@ export function registerCMSRoutes(router) {
|
|
|
4699
4748
|
excerpt: excerptForNotification(opts.body),
|
|
4700
4749
|
},
|
|
4701
4750
|
});
|
|
4751
|
+
if (result.ok)
|
|
4752
|
+
publishNotification(result.value);
|
|
4702
4753
|
}
|
|
4703
4754
|
}
|
|
4704
4755
|
/** Notify the comment's author when someone else resolves their thread. */
|
|
@@ -4706,7 +4757,7 @@ export function registerCMSRoutes(router) {
|
|
|
4706
4757
|
const comment = await findCommentRecipient(opts.commentId);
|
|
4707
4758
|
if (!comment || !comment.userId || comment.userId === opts.actorId)
|
|
4708
4759
|
return;
|
|
4709
|
-
await createNotification(notificationsDb(), {
|
|
4760
|
+
const result = await createNotification(notificationsDb(), {
|
|
4710
4761
|
userId: comment.userId,
|
|
4711
4762
|
commentId: opts.commentId,
|
|
4712
4763
|
payload: {
|
|
@@ -4716,6 +4767,8 @@ export function registerCMSRoutes(router) {
|
|
|
4716
4767
|
rootExcerpt: excerptForNotification(comment.body),
|
|
4717
4768
|
},
|
|
4718
4769
|
});
|
|
4770
|
+
if (result.ok)
|
|
4771
|
+
publishNotification(result.value);
|
|
4719
4772
|
}
|
|
4720
4773
|
router.post('/documents/:documentId/comments', async (request, params) => {
|
|
4721
4774
|
try {
|
|
@@ -4967,6 +5020,29 @@ export function registerCMSRoutes(router) {
|
|
|
4967
5020
|
return internalError(err, 'mark all notifications read');
|
|
4968
5021
|
}
|
|
4969
5022
|
});
|
|
5023
|
+
// Per-user notification SSE push channel (Phase 7 carry-over). The
|
|
5024
|
+
// admin's NotificationBell subscribes via EventSource for real-time
|
|
5025
|
+
// delivery of comment_reply / comment_mention / comment_resolved
|
|
5026
|
+
// events. The existing GET /notifications and
|
|
5027
|
+
// /notifications/unread-count REST endpoints are kept as a
|
|
5028
|
+
// poll-fallback path for multi-node deployments where a write on
|
|
5029
|
+
// node A doesn't reach SSE subscribers on node B (the in-process
|
|
5030
|
+
// bus is single-instance by design — see notification-bus.ts).
|
|
5031
|
+
//
|
|
5032
|
+
// Auth: same scope as every other /notifications route — the
|
|
5033
|
+
// session userId determines which channel the stream subscribes to;
|
|
5034
|
+
// there is no admin override.
|
|
5035
|
+
router.get('/notifications/stream', async (request) => {
|
|
5036
|
+
try {
|
|
5037
|
+
const auth = await requireAuth(request);
|
|
5038
|
+
if (auth.error)
|
|
5039
|
+
return auth.error;
|
|
5040
|
+
return createNotificationStreamResponse(auth.session.userId);
|
|
5041
|
+
}
|
|
5042
|
+
catch (err) {
|
|
5043
|
+
return internalError(err, 'notifications stream');
|
|
5044
|
+
}
|
|
5045
|
+
});
|
|
4970
5046
|
// ---------------------------------------------------------------------------
|
|
4971
5047
|
// Workflow routes
|
|
4972
5048
|
// ---------------------------------------------------------------------------
|