@growth-labs/cms 0.1.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/README.md +165 -0
- package/dist/engine/activity-log.d.ts +17 -0
- package/dist/engine/activity-log.d.ts.map +1 -0
- package/dist/engine/activity-log.js +17 -0
- package/dist/engine/activity-log.js.map +1 -0
- package/dist/engine/ai-prompts.d.ts +57 -0
- package/dist/engine/ai-prompts.d.ts.map +1 -0
- package/dist/engine/ai-prompts.js +90 -0
- package/dist/engine/ai-prompts.js.map +1 -0
- package/dist/engine/ai-writeback.d.ts +36 -0
- package/dist/engine/ai-writeback.d.ts.map +1 -0
- package/dist/engine/ai-writeback.js +45 -0
- package/dist/engine/ai-writeback.js.map +1 -0
- package/dist/engine/api-keys.d.ts +76 -0
- package/dist/engine/api-keys.d.ts.map +1 -0
- package/dist/engine/api-keys.js +165 -0
- package/dist/engine/api-keys.js.map +1 -0
- package/dist/engine/content-insights.d.ts +36 -0
- package/dist/engine/content-insights.d.ts.map +1 -0
- package/dist/engine/content-insights.js +114 -0
- package/dist/engine/content-insights.js.map +1 -0
- package/dist/engine/contributors.d.ts +25 -0
- package/dist/engine/contributors.d.ts.map +1 -0
- package/dist/engine/contributors.js +59 -0
- package/dist/engine/contributors.js.map +1 -0
- package/dist/engine/cron.d.ts +15 -0
- package/dist/engine/cron.d.ts.map +1 -0
- package/dist/engine/cron.js +33 -0
- package/dist/engine/cron.js.map +1 -0
- package/dist/engine/d1.d.ts +16 -0
- package/dist/engine/d1.d.ts.map +1 -0
- package/dist/engine/d1.js +13 -0
- package/dist/engine/d1.js.map +1 -0
- package/dist/engine/foundry-dispatch.d.ts +52 -0
- package/dist/engine/foundry-dispatch.d.ts.map +1 -0
- package/dist/engine/foundry-dispatch.js +290 -0
- package/dist/engine/foundry-dispatch.js.map +1 -0
- package/dist/engine/import-parsers.d.ts +11 -0
- package/dist/engine/import-parsers.d.ts.map +1 -0
- package/dist/engine/import-parsers.js +373 -0
- package/dist/engine/import-parsers.js.map +1 -0
- package/dist/engine/index.d.ts +28 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +57 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/invites.d.ts +78 -0
- package/dist/engine/invites.d.ts.map +1 -0
- package/dist/engine/invites.js +158 -0
- package/dist/engine/invites.js.map +1 -0
- package/dist/engine/members.d.ts +59 -0
- package/dist/engine/members.d.ts.map +1 -0
- package/dist/engine/members.js +124 -0
- package/dist/engine/members.js.map +1 -0
- package/dist/engine/membership-rules.d.ts +25 -0
- package/dist/engine/membership-rules.d.ts.map +1 -0
- package/dist/engine/membership-rules.js +44 -0
- package/dist/engine/membership-rules.js.map +1 -0
- package/dist/engine/og-render.d.ts +40 -0
- package/dist/engine/og-render.d.ts.map +1 -0
- package/dist/engine/og-render.js +26 -0
- package/dist/engine/og-render.js.map +1 -0
- package/dist/engine/publish-guard.d.ts +58 -0
- package/dist/engine/publish-guard.d.ts.map +1 -0
- package/dist/engine/publish-guard.js +80 -0
- package/dist/engine/publish-guard.js.map +1 -0
- package/dist/engine/publisher.d.ts +171 -0
- package/dist/engine/publisher.d.ts.map +1 -0
- package/dist/engine/publisher.js +597 -0
- package/dist/engine/publisher.js.map +1 -0
- package/dist/engine/revisions.d.ts +39 -0
- package/dist/engine/revisions.d.ts.map +1 -0
- package/dist/engine/revisions.js +203 -0
- package/dist/engine/revisions.js.map +1 -0
- package/dist/engine/sanitize.d.ts +52 -0
- package/dist/engine/sanitize.d.ts.map +1 -0
- package/dist/engine/sanitize.js +155 -0
- package/dist/engine/sanitize.js.map +1 -0
- package/dist/engine/seed-membership.d.ts +29 -0
- package/dist/engine/seed-membership.d.ts.map +1 -0
- package/dist/engine/seed-membership.js +65 -0
- package/dist/engine/seed-membership.js.map +1 -0
- package/dist/engine/seo.d.ts +20 -0
- package/dist/engine/seo.d.ts.map +1 -0
- package/dist/engine/seo.js +50 -0
- package/dist/engine/seo.js.map +1 -0
- package/dist/engine/slug-redirects.d.ts +8 -0
- package/dist/engine/slug-redirects.d.ts.map +1 -0
- package/dist/engine/slug-redirects.js +26 -0
- package/dist/engine/slug-redirects.js.map +1 -0
- package/dist/engine/slug.d.ts +6 -0
- package/dist/engine/slug.d.ts.map +1 -0
- package/dist/engine/slug.js +28 -0
- package/dist/engine/slug.js.map +1 -0
- package/dist/engine/soft-delete.d.ts +8 -0
- package/dist/engine/soft-delete.d.ts.map +1 -0
- package/dist/engine/soft-delete.js +28 -0
- package/dist/engine/soft-delete.js.map +1 -0
- package/dist/engine/tags.d.ts +14 -0
- package/dist/engine/tags.d.ts.map +1 -0
- package/dist/engine/tags.js +79 -0
- package/dist/engine/tags.js.map +1 -0
- package/dist/engine/topics.d.ts +10 -0
- package/dist/engine/topics.d.ts.map +1 -0
- package/dist/engine/topics.js +140 -0
- package/dist/engine/topics.js.map +1 -0
- package/dist/engine/url-guard.d.ts +12 -0
- package/dist/engine/url-guard.d.ts.map +1 -0
- package/dist/engine/url-guard.js +129 -0
- package/dist/engine/url-guard.js.map +1 -0
- package/dist/engine/validator/checks/bare-url-not-autolinked.d.ts +20 -0
- package/dist/engine/validator/checks/bare-url-not-autolinked.d.ts.map +1 -0
- package/dist/engine/validator/checks/bare-url-not-autolinked.js +54 -0
- package/dist/engine/validator/checks/bare-url-not-autolinked.js.map +1 -0
- package/dist/engine/validator/checks/broken-footnote-label.d.ts +16 -0
- package/dist/engine/validator/checks/broken-footnote-label.d.ts.map +1 -0
- package/dist/engine/validator/checks/broken-footnote-label.js +17 -0
- package/dist/engine/validator/checks/broken-footnote-label.js.map +1 -0
- package/dist/engine/validator/checks/double-encoded-entities.d.ts +18 -0
- package/dist/engine/validator/checks/double-encoded-entities.d.ts.map +1 -0
- package/dist/engine/validator/checks/double-encoded-entities.js +23 -0
- package/dist/engine/validator/checks/double-encoded-entities.js.map +1 -0
- package/dist/engine/validator/checks/empty-alt-text.d.ts +14 -0
- package/dist/engine/validator/checks/empty-alt-text.d.ts.map +1 -0
- package/dist/engine/validator/checks/empty-alt-text.js +23 -0
- package/dist/engine/validator/checks/empty-alt-text.js.map +1 -0
- package/dist/engine/validator/checks/heading-hierarchy-skip.d.ts +11 -0
- package/dist/engine/validator/checks/heading-hierarchy-skip.d.ts.map +1 -0
- package/dist/engine/validator/checks/heading-hierarchy-skip.js +20 -0
- package/dist/engine/validator/checks/heading-hierarchy-skip.js.map +1 -0
- package/dist/engine/validator/checks/html-comment-leak.d.ts +20 -0
- package/dist/engine/validator/checks/html-comment-leak.d.ts.map +1 -0
- package/dist/engine/validator/checks/html-comment-leak.js +30 -0
- package/dist/engine/validator/checks/html-comment-leak.js.map +1 -0
- package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.d.ts +12 -0
- package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.d.ts.map +1 -0
- package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.js +17 -0
- package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.js.map +1 -0
- package/dist/engine/validator/checks/invisible-control-chars.d.ts +24 -0
- package/dist/engine/validator/checks/invisible-control-chars.d.ts.map +1 -0
- package/dist/engine/validator/checks/invisible-control-chars.js +30 -0
- package/dist/engine/validator/checks/invisible-control-chars.js.map +1 -0
- package/dist/engine/validator/checks/paywall-marker-leak.d.ts +17 -0
- package/dist/engine/validator/checks/paywall-marker-leak.d.ts.map +1 -0
- package/dist/engine/validator/checks/paywall-marker-leak.js +22 -0
- package/dist/engine/validator/checks/paywall-marker-leak.js.map +1 -0
- package/dist/engine/validator/checks/raw-block-html.d.ts +28 -0
- package/dist/engine/validator/checks/raw-block-html.d.ts.map +1 -0
- package/dist/engine/validator/checks/raw-block-html.js +38 -0
- package/dist/engine/validator/checks/raw-block-html.js.map +1 -0
- package/dist/engine/validator/checks/stale-body-html.d.ts +28 -0
- package/dist/engine/validator/checks/stale-body-html.d.ts.map +1 -0
- package/dist/engine/validator/checks/stale-body-html.js +15 -0
- package/dist/engine/validator/checks/stale-body-html.js.map +1 -0
- package/dist/engine/validator/checks/unresolved-footnote-anchor.d.ts +11 -0
- package/dist/engine/validator/checks/unresolved-footnote-anchor.d.ts.map +1 -0
- package/dist/engine/validator/checks/unresolved-footnote-anchor.js +48 -0
- package/dist/engine/validator/checks/unresolved-footnote-anchor.js.map +1 -0
- package/dist/engine/validator/checks/word-gdocs-paste-artifacts.d.ts +23 -0
- package/dist/engine/validator/checks/word-gdocs-paste-artifacts.d.ts.map +1 -0
- package/dist/engine/validator/checks/word-gdocs-paste-artifacts.js +47 -0
- package/dist/engine/validator/checks/word-gdocs-paste-artifacts.js.map +1 -0
- package/dist/engine/validator/index.d.ts +75 -0
- package/dist/engine/validator/index.d.ts.map +1 -0
- package/dist/engine/validator/index.js +313 -0
- package/dist/engine/validator/index.js.map +1 -0
- package/dist/engine/validator/scan.d.ts +28 -0
- package/dist/engine/validator/scan.d.ts.map +1 -0
- package/dist/engine/validator/scan.js +97 -0
- package/dist/engine/validator/scan.js.map +1 -0
- package/dist/engine/validator/types.d.ts +50 -0
- package/dist/engine/validator/types.d.ts.map +1 -0
- package/dist/engine/validator/types.js +51 -0
- package/dist/engine/validator/types.js.map +1 -0
- package/dist/engine/webhook-signer.d.ts +39 -0
- package/dist/engine/webhook-signer.d.ts.map +1 -0
- package/dist/engine/webhook-signer.js +117 -0
- package/dist/engine/webhook-signer.js.map +1 -0
- package/dist/engine/webhooks.d.ts +75 -0
- package/dist/engine/webhooks.d.ts.map +1 -0
- package/dist/engine/webhooks.js +139 -0
- package/dist/engine/webhooks.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.d.ts +6 -0
- package/dist/integration/index.d.ts.map +1 -0
- package/dist/integration/index.js +294 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/options.d.ts +105 -0
- package/dist/integration/options.d.ts.map +1 -0
- package/dist/integration/options.js +25 -0
- package/dist/integration/options.js.map +1 -0
- package/dist/integration/vite-plugin.d.ts +4 -0
- package/dist/integration/vite-plugin.d.ts.map +1 -0
- package/dist/integration/vite-plugin.js +37 -0
- package/dist/integration/vite-plugin.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/null.d.ts +9 -0
- package/dist/providers/null.d.ts.map +1 -0
- package/dist/providers/null.js +144 -0
- package/dist/providers/null.js.map +1 -0
- package/dist/providers/types.d.ts +277 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/routes/ai.d.ts +25 -0
- package/dist/routes/ai.d.ts.map +1 -0
- package/dist/routes/ai.js +381 -0
- package/dist/routes/ai.js.map +1 -0
- package/dist/routes/analytics.d.ts +15 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +61 -0
- package/dist/routes/analytics.js.map +1 -0
- package/dist/routes/api-keys.d.ts +13 -0
- package/dist/routes/api-keys.d.ts.map +1 -0
- package/dist/routes/api-keys.js +109 -0
- package/dist/routes/api-keys.js.map +1 -0
- package/dist/routes/authors.d.ts +19 -0
- package/dist/routes/authors.d.ts.map +1 -0
- package/dist/routes/authors.js +202 -0
- package/dist/routes/authors.js.map +1 -0
- package/dist/routes/authz-matrix.d.ts +78 -0
- package/dist/routes/authz-matrix.d.ts.map +1 -0
- package/dist/routes/authz-matrix.js +170 -0
- package/dist/routes/authz-matrix.js.map +1 -0
- package/dist/routes/calendar.d.ts +19 -0
- package/dist/routes/calendar.d.ts.map +1 -0
- package/dist/routes/calendar.js +89 -0
- package/dist/routes/calendar.js.map +1 -0
- package/dist/routes/config.d.ts +70 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +23 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/content-insights.d.ts +18 -0
- package/dist/routes/content-insights.d.ts.map +1 -0
- package/dist/routes/content-insights.js +137 -0
- package/dist/routes/content-insights.js.map +1 -0
- package/dist/routes/content.d.ts +145 -0
- package/dist/routes/content.d.ts.map +1 -0
- package/dist/routes/content.js +1374 -0
- package/dist/routes/content.js.map +1 -0
- package/dist/routes/context.d.ts +104 -0
- package/dist/routes/context.d.ts.map +1 -0
- package/dist/routes/context.js +26 -0
- package/dist/routes/context.js.map +1 -0
- package/dist/routes/cron.d.ts +8 -0
- package/dist/routes/cron.d.ts.map +1 -0
- package/dist/routes/cron.js +20 -0
- package/dist/routes/cron.js.map +1 -0
- package/dist/routes/dashboard.d.ts +12 -0
- package/dist/routes/dashboard.d.ts.map +1 -0
- package/dist/routes/dashboard.js +113 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/imports.d.ts +10 -0
- package/dist/routes/imports.d.ts.map +1 -0
- package/dist/routes/imports.js +149 -0
- package/dist/routes/imports.js.map +1 -0
- package/dist/routes/index.d.ts +75 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +141 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/media-lib.d.ts +75 -0
- package/dist/routes/media-lib.d.ts.map +1 -0
- package/dist/routes/media-lib.js +305 -0
- package/dist/routes/media-lib.js.map +1 -0
- package/dist/routes/media.d.ts +32 -0
- package/dist/routes/media.d.ts.map +1 -0
- package/dist/routes/media.js +756 -0
- package/dist/routes/media.js.map +1 -0
- package/dist/routes/preview.d.ts +19 -0
- package/dist/routes/preview.d.ts.map +1 -0
- package/dist/routes/preview.js +150 -0
- package/dist/routes/preview.js.map +1 -0
- package/dist/routes/rbac-invites.d.ts +31 -0
- package/dist/routes/rbac-invites.d.ts.map +1 -0
- package/dist/routes/rbac-invites.js +174 -0
- package/dist/routes/rbac-invites.js.map +1 -0
- package/dist/routes/rbac.d.ts +12 -0
- package/dist/routes/rbac.d.ts.map +1 -0
- package/dist/routes/rbac.js +126 -0
- package/dist/routes/rbac.js.map +1 -0
- package/dist/routes/shell.d.ts +22 -0
- package/dist/routes/shell.d.ts.map +1 -0
- package/dist/routes/shell.js +123 -0
- package/dist/routes/shell.js.map +1 -0
- package/dist/routes/subscriptions.d.ts +21 -0
- package/dist/routes/subscriptions.d.ts.map +1 -0
- package/dist/routes/subscriptions.js +127 -0
- package/dist/routes/subscriptions.js.map +1 -0
- package/dist/routes/tags.d.ts +23 -0
- package/dist/routes/tags.d.ts.map +1 -0
- package/dist/routes/tags.js +68 -0
- package/dist/routes/tags.js.map +1 -0
- package/dist/routes/topics.d.ts +12 -0
- package/dist/routes/topics.d.ts.map +1 -0
- package/dist/routes/topics.js +49 -0
- package/dist/routes/topics.js.map +1 -0
- package/dist/routes/webhooks.d.ts +31 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +173 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +6 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/insights-ingest.d.ts +959 -0
- package/dist/schema/insights-ingest.d.ts.map +1 -0
- package/dist/schema/insights-ingest.js +112 -0
- package/dist/schema/insights-ingest.js.map +1 -0
- package/dist/schema/migrations.d.ts +63 -0
- package/dist/schema/migrations.d.ts.map +1 -0
- package/dist/schema/migrations.js +589 -0
- package/dist/schema/migrations.js.map +1 -0
- package/dist/schema/tables.d.ts +11 -0
- package/dist/schema/tables.d.ts.map +1 -0
- package/dist/schema/tables.js +56 -0
- package/dist/schema/tables.js.map +1 -0
- package/dist/schema/types.d.ts +476 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +37 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/ui/api/_authz.d.ts +6 -0
- package/dist/ui/api/_authz.d.ts.map +1 -0
- package/dist/ui/api/_authz.js +74 -0
- package/dist/ui/api/_authz.js.map +1 -0
- package/dist/ui/api/_content-config.d.ts +22 -0
- package/dist/ui/api/_content-config.d.ts.map +1 -0
- package/dist/ui/api/_content-config.js +50 -0
- package/dist/ui/api/_content-config.js.map +1 -0
- package/dist/ui/api/activity.d.ts +3 -0
- package/dist/ui/api/activity.d.ts.map +1 -0
- package/dist/ui/api/activity.js +28 -0
- package/dist/ui/api/activity.js.map +1 -0
- package/dist/ui/api/analytics.d.ts +3 -0
- package/dist/ui/api/analytics.d.ts.map +1 -0
- package/dist/ui/api/analytics.js +36 -0
- package/dist/ui/api/analytics.js.map +1 -0
- package/dist/ui/api/authors/[id].d.ts +4 -0
- package/dist/ui/api/authors/[id].d.ts.map +1 -0
- package/dist/ui/api/authors/[id].js +17 -0
- package/dist/ui/api/authors/[id].js.map +1 -0
- package/dist/ui/api/authors.d.ts +4 -0
- package/dist/ui/api/authors.d.ts.map +1 -0
- package/dist/ui/api/authors.js +12 -0
- package/dist/ui/api/authors.js.map +1 -0
- package/dist/ui/api/calendar.d.ts +3 -0
- package/dist/ui/api/calendar.d.ts.map +1 -0
- package/dist/ui/api/calendar.js +16 -0
- package/dist/ui/api/calendar.js.map +1 -0
- package/dist/ui/api/content/[id]/ai/headlines.d.ts +3 -0
- package/dist/ui/api/content/[id]/ai/headlines.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/ai/headlines.js +7 -0
- package/dist/ui/api/content/[id]/ai/headlines.js.map +1 -0
- package/dist/ui/api/content/[id]/ai/meta-description.d.ts +3 -0
- package/dist/ui/api/content/[id]/ai/meta-description.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/ai/meta-description.js +6 -0
- package/dist/ui/api/content/[id]/ai/meta-description.js.map +1 -0
- package/dist/ui/api/content/[id]/ai/og-image.d.ts +3 -0
- package/dist/ui/api/content/[id]/ai/og-image.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/ai/og-image.js +7 -0
- package/dist/ui/api/content/[id]/ai/og-image.js.map +1 -0
- package/dist/ui/api/content/[id]/ai/proofread.d.ts +3 -0
- package/dist/ui/api/content/[id]/ai/proofread.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/ai/proofread.js +7 -0
- package/dist/ui/api/content/[id]/ai/proofread.js.map +1 -0
- package/dist/ui/api/content/[id]/ai/takeaways.d.ts +3 -0
- package/dist/ui/api/content/[id]/ai/takeaways.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/ai/takeaways.js +6 -0
- package/dist/ui/api/content/[id]/ai/takeaways.js.map +1 -0
- package/dist/ui/api/content/[id]/contributors.d.ts +4 -0
- package/dist/ui/api/content/[id]/contributors.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/contributors.js +8 -0
- package/dist/ui/api/content/[id]/contributors.js.map +1 -0
- package/dist/ui/api/content/[id]/preview-token.d.ts +3 -0
- package/dist/ui/api/content/[id]/preview-token.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/preview-token.js +16 -0
- package/dist/ui/api/content/[id]/preview-token.js.map +1 -0
- package/dist/ui/api/content/[id]/revisions/[rev]/restore.d.ts +3 -0
- package/dist/ui/api/content/[id]/revisions/[rev]/restore.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/revisions/[rev]/restore.js +7 -0
- package/dist/ui/api/content/[id]/revisions/[rev]/restore.js.map +1 -0
- package/dist/ui/api/content/[id]/revisions/[rev].d.ts +3 -0
- package/dist/ui/api/content/[id]/revisions/[rev].d.ts.map +1 -0
- package/dist/ui/api/content/[id]/revisions/[rev].js +6 -0
- package/dist/ui/api/content/[id]/revisions/[rev].js.map +1 -0
- package/dist/ui/api/content/[id]/revisions.d.ts +3 -0
- package/dist/ui/api/content/[id]/revisions.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/revisions.js +6 -0
- package/dist/ui/api/content/[id]/revisions.js.map +1 -0
- package/dist/ui/api/content/[id]/seo-score.d.ts +3 -0
- package/dist/ui/api/content/[id]/seo-score.d.ts.map +1 -0
- package/dist/ui/api/content/[id]/seo-score.js +7 -0
- package/dist/ui/api/content/[id]/seo-score.js.map +1 -0
- package/dist/ui/api/content/[id].d.ts +5 -0
- package/dist/ui/api/content/[id].d.ts.map +1 -0
- package/dist/ui/api/content/[id].js +17 -0
- package/dist/ui/api/content/[id].js.map +1 -0
- package/dist/ui/api/content/bulk.d.ts +3 -0
- package/dist/ui/api/content/bulk.d.ts.map +1 -0
- package/dist/ui/api/content/bulk.js +7 -0
- package/dist/ui/api/content/bulk.js.map +1 -0
- package/dist/ui/api/content/counts.d.ts +3 -0
- package/dist/ui/api/content/counts.d.ts.map +1 -0
- package/dist/ui/api/content/counts.js +8 -0
- package/dist/ui/api/content/counts.js.map +1 -0
- package/dist/ui/api/content/foundry-callback.d.ts +3 -0
- package/dist/ui/api/content/foundry-callback.d.ts.map +1 -0
- package/dist/ui/api/content/foundry-callback.js +8 -0
- package/dist/ui/api/content/foundry-callback.js.map +1 -0
- package/dist/ui/api/content/import/confirm.d.ts +3 -0
- package/dist/ui/api/content/import/confirm.d.ts.map +1 -0
- package/dist/ui/api/content/import/confirm.js +11 -0
- package/dist/ui/api/content/import/confirm.js.map +1 -0
- package/dist/ui/api/content/import/parse.d.ts +3 -0
- package/dist/ui/api/content/import/parse.d.ts.map +1 -0
- package/dist/ui/api/content/import/parse.js +11 -0
- package/dist/ui/api/content/import/parse.js.map +1 -0
- package/dist/ui/api/content/insights-ingest.d.ts +3 -0
- package/dist/ui/api/content/insights-ingest.d.ts.map +1 -0
- package/dist/ui/api/content/insights-ingest.js +15 -0
- package/dist/ui/api/content/insights-ingest.js.map +1 -0
- package/dist/ui/api/content-insights/dismiss.d.ts +3 -0
- package/dist/ui/api/content-insights/dismiss.d.ts.map +1 -0
- package/dist/ui/api/content-insights/dismiss.js +16 -0
- package/dist/ui/api/content-insights/dismiss.js.map +1 -0
- package/dist/ui/api/content-insights/index.d.ts +3 -0
- package/dist/ui/api/content-insights/index.d.ts.map +1 -0
- package/dist/ui/api/content-insights/index.js +12 -0
- package/dist/ui/api/content-insights/index.js.map +1 -0
- package/dist/ui/api/content-insights/undismiss.d.ts +3 -0
- package/dist/ui/api/content-insights/undismiss.d.ts.map +1 -0
- package/dist/ui/api/content-insights/undismiss.js +16 -0
- package/dist/ui/api/content-insights/undismiss.js.map +1 -0
- package/dist/ui/api/content.d.ts +4 -0
- package/dist/ui/api/content.d.ts.map +1 -0
- package/dist/ui/api/content.js +16 -0
- package/dist/ui/api/content.js.map +1 -0
- package/dist/ui/api/dashboard.d.ts +3 -0
- package/dist/ui/api/dashboard.d.ts.map +1 -0
- package/dist/ui/api/dashboard.js +22 -0
- package/dist/ui/api/dashboard.js.map +1 -0
- package/dist/ui/api/me.d.ts +3 -0
- package/dist/ui/api/me.d.ts.map +1 -0
- package/dist/ui/api/me.js +18 -0
- package/dist/ui/api/me.js.map +1 -0
- package/dist/ui/api/media/[id].d.ts +3 -0
- package/dist/ui/api/media/[id].d.ts.map +1 -0
- package/dist/ui/api/media/[id].js +17 -0
- package/dist/ui/api/media/[id].js.map +1 -0
- package/dist/ui/api/media/images.d.ts +3 -0
- package/dist/ui/api/media/images.d.ts.map +1 -0
- package/dist/ui/api/media/images.js +6 -0
- package/dist/ui/api/media/images.js.map +1 -0
- package/dist/ui/api/media/library/[id].d.ts +3 -0
- package/dist/ui/api/media/library/[id].d.ts.map +1 -0
- package/dist/ui/api/media/library/[id].js +6 -0
- package/dist/ui/api/media/library/[id].js.map +1 -0
- package/dist/ui/api/media/library.d.ts +3 -0
- package/dist/ui/api/media/library.d.ts.map +1 -0
- package/dist/ui/api/media/library.js +17 -0
- package/dist/ui/api/media/library.js.map +1 -0
- package/dist/ui/api/media/podcast/abort.d.ts +3 -0
- package/dist/ui/api/media/podcast/abort.d.ts.map +1 -0
- package/dist/ui/api/media/podcast/abort.js +4 -0
- package/dist/ui/api/media/podcast/abort.js.map +1 -0
- package/dist/ui/api/media/podcast/complete.d.ts +3 -0
- package/dist/ui/api/media/podcast/complete.d.ts.map +1 -0
- package/dist/ui/api/media/podcast/complete.js +4 -0
- package/dist/ui/api/media/podcast/complete.js.map +1 -0
- package/dist/ui/api/media/podcast/init.d.ts +3 -0
- package/dist/ui/api/media/podcast/init.d.ts.map +1 -0
- package/dist/ui/api/media/podcast/init.js +4 -0
- package/dist/ui/api/media/podcast/init.js.map +1 -0
- package/dist/ui/api/media/podcast/part.d.ts +3 -0
- package/dist/ui/api/media/podcast/part.d.ts.map +1 -0
- package/dist/ui/api/media/podcast/part.js +4 -0
- package/dist/ui/api/media/podcast/part.js.map +1 -0
- package/dist/ui/api/media/podcast.d.ts +3 -0
- package/dist/ui/api/media/podcast.d.ts.map +1 -0
- package/dist/ui/api/media/podcast.js +6 -0
- package/dist/ui/api/media/podcast.js.map +1 -0
- package/dist/ui/api/media/videos/abort.d.ts +3 -0
- package/dist/ui/api/media/videos/abort.d.ts.map +1 -0
- package/dist/ui/api/media/videos/abort.js +6 -0
- package/dist/ui/api/media/videos/abort.js.map +1 -0
- package/dist/ui/api/media/videos/complete.d.ts +3 -0
- package/dist/ui/api/media/videos/complete.d.ts.map +1 -0
- package/dist/ui/api/media/videos/complete.js +6 -0
- package/dist/ui/api/media/videos/complete.js.map +1 -0
- package/dist/ui/api/media/videos/init.d.ts +3 -0
- package/dist/ui/api/media/videos/init.d.ts.map +1 -0
- package/dist/ui/api/media/videos/init.js +6 -0
- package/dist/ui/api/media/videos/init.js.map +1 -0
- package/dist/ui/api/media/videos/part.d.ts +3 -0
- package/dist/ui/api/media/videos/part.d.ts.map +1 -0
- package/dist/ui/api/media/videos/part.js +6 -0
- package/dist/ui/api/media/videos/part.js.map +1 -0
- package/dist/ui/api/notifications.d.ts +4 -0
- package/dist/ui/api/notifications.d.ts.map +1 -0
- package/dist/ui/api/notifications.js +20 -0
- package/dist/ui/api/notifications.js.map +1 -0
- package/dist/ui/api/search.d.ts +3 -0
- package/dist/ui/api/search.d.ts.map +1 -0
- package/dist/ui/api/search.js +18 -0
- package/dist/ui/api/search.js.map +1 -0
- package/dist/ui/api/settings/api-keys/[id].d.ts +3 -0
- package/dist/ui/api/settings/api-keys/[id].d.ts.map +1 -0
- package/dist/ui/api/settings/api-keys/[id].js +22 -0
- package/dist/ui/api/settings/api-keys/[id].js.map +1 -0
- package/dist/ui/api/settings/api-keys.d.ts +4 -0
- package/dist/ui/api/settings/api-keys.d.ts.map +1 -0
- package/dist/ui/api/settings/api-keys.js +19 -0
- package/dist/ui/api/settings/api-keys.js.map +1 -0
- package/dist/ui/api/settings/domains.d.ts +3 -0
- package/dist/ui/api/settings/domains.d.ts.map +1 -0
- package/dist/ui/api/settings/domains.js +32 -0
- package/dist/ui/api/settings/domains.js.map +1 -0
- package/dist/ui/api/settings/integrations.d.ts +3 -0
- package/dist/ui/api/settings/integrations.d.ts.map +1 -0
- package/dist/ui/api/settings/integrations.js +32 -0
- package/dist/ui/api/settings/integrations.js.map +1 -0
- package/dist/ui/api/settings/members/[userId].d.ts +4 -0
- package/dist/ui/api/settings/members/[userId].d.ts.map +1 -0
- package/dist/ui/api/settings/members/[userId].js +26 -0
- package/dist/ui/api/settings/members/[userId].js.map +1 -0
- package/dist/ui/api/settings/members/invite.d.ts +3 -0
- package/dist/ui/api/settings/members/invite.d.ts.map +1 -0
- package/dist/ui/api/settings/members/invite.js +21 -0
- package/dist/ui/api/settings/members/invite.js.map +1 -0
- package/dist/ui/api/settings/members.d.ts +3 -0
- package/dist/ui/api/settings/members.d.ts.map +1 -0
- package/dist/ui/api/settings/members.js +17 -0
- package/dist/ui/api/settings/members.js.map +1 -0
- package/dist/ui/api/settings/webhooks/[id]/test.d.ts +3 -0
- package/dist/ui/api/settings/webhooks/[id]/test.d.ts.map +1 -0
- package/dist/ui/api/settings/webhooks/[id]/test.js +24 -0
- package/dist/ui/api/settings/webhooks/[id]/test.js.map +1 -0
- package/dist/ui/api/settings/webhooks/[id].d.ts +3 -0
- package/dist/ui/api/settings/webhooks/[id].d.ts.map +1 -0
- package/dist/ui/api/settings/webhooks/[id].js +23 -0
- package/dist/ui/api/settings/webhooks/[id].js.map +1 -0
- package/dist/ui/api/settings/webhooks.d.ts +4 -0
- package/dist/ui/api/settings/webhooks.d.ts.map +1 -0
- package/dist/ui/api/settings/webhooks.js +21 -0
- package/dist/ui/api/settings/webhooks.js.map +1 -0
- package/dist/ui/api/subscriptions.d.ts +4 -0
- package/dist/ui/api/subscriptions.d.ts.map +1 -0
- package/dist/ui/api/subscriptions.js +47 -0
- package/dist/ui/api/subscriptions.js.map +1 -0
- package/dist/ui/api/tags/[id].d.ts +4 -0
- package/dist/ui/api/tags/[id].d.ts.map +1 -0
- package/dist/ui/api/tags/[id].js +18 -0
- package/dist/ui/api/tags/[id].js.map +1 -0
- package/dist/ui/api/tags/index.d.ts +4 -0
- package/dist/ui/api/tags/index.d.ts.map +1 -0
- package/dist/ui/api/tags/index.js +13 -0
- package/dist/ui/api/tags/index.js.map +1 -0
- package/dist/ui/api/topics/[id].d.ts +3 -0
- package/dist/ui/api/topics/[id].d.ts.map +1 -0
- package/dist/ui/api/topics/[id].js +15 -0
- package/dist/ui/api/topics/[id].js.map +1 -0
- package/dist/ui/api/topics/index.d.ts +4 -0
- package/dist/ui/api/topics/index.d.ts.map +1 -0
- package/dist/ui/api/topics/index.js +12 -0
- package/dist/ui/api/topics/index.js.map +1 -0
- package/dist/ui/api/workspace-settings.d.ts +4 -0
- package/dist/ui/api/workspace-settings.d.ts.map +1 -0
- package/dist/ui/api/workspace-settings.js +20 -0
- package/dist/ui/api/workspace-settings.js.map +1 -0
- package/dist/ui/client/boot-state.d.ts +15 -0
- package/dist/ui/client/boot-state.d.ts.map +1 -0
- package/dist/ui/client/boot-state.js +36 -0
- package/dist/ui/client/boot-state.js.map +1 -0
- package/dist/ui/client/mount.d.ts +3 -0
- package/dist/ui/client/mount.d.ts.map +1 -0
- package/dist/ui/client/mount.js +37 -0
- package/dist/ui/client/mount.js.map +1 -0
- package/dist/ui/commands.d.ts +23 -0
- package/dist/ui/commands.d.ts.map +1 -0
- package/dist/ui/commands.js +48 -0
- package/dist/ui/commands.js.map +1 -0
- package/dist/ui/components/CmsApp.d.ts +16 -0
- package/dist/ui/components/CmsApp.d.ts.map +1 -0
- package/dist/ui/components/CmsApp.js +74 -0
- package/dist/ui/components/CmsApp.js.map +1 -0
- package/dist/ui/components/CommandPalette.d.ts +7 -0
- package/dist/ui/components/CommandPalette.d.ts.map +1 -0
- package/dist/ui/components/CommandPalette.js +61 -0
- package/dist/ui/components/CommandPalette.js.map +1 -0
- package/dist/ui/components/NoAccessScreen.d.ts +2 -0
- package/dist/ui/components/NoAccessScreen.d.ts.map +1 -0
- package/dist/ui/components/NoAccessScreen.js +42 -0
- package/dist/ui/components/NoAccessScreen.js.map +1 -0
- package/dist/ui/components/ShareModal.d.ts +27 -0
- package/dist/ui/components/ShareModal.d.ts.map +1 -0
- package/dist/ui/components/ShareModal.js +208 -0
- package/dist/ui/components/ShareModal.js.map +1 -0
- package/dist/ui/components/SharePickers.d.ts +39 -0
- package/dist/ui/components/SharePickers.d.ts.map +1 -0
- package/dist/ui/components/SharePickers.js +352 -0
- package/dist/ui/components/SharePickers.js.map +1 -0
- package/dist/ui/components/ShareStatsPanel.d.ts +22 -0
- package/dist/ui/components/ShareStatsPanel.d.ts.map +1 -0
- package/dist/ui/components/ShareStatsPanel.js +317 -0
- package/dist/ui/components/ShareStatsPanel.js.map +1 -0
- package/dist/ui/components/Sidebar.d.ts +7 -0
- package/dist/ui/components/Sidebar.d.ts.map +1 -0
- package/dist/ui/components/Sidebar.js +20 -0
- package/dist/ui/components/Sidebar.js.map +1 -0
- package/dist/ui/components/SiteSwitcher.d.ts +4 -0
- package/dist/ui/components/SiteSwitcher.d.ts.map +1 -0
- package/dist/ui/components/SiteSwitcher.js +35 -0
- package/dist/ui/components/SiteSwitcher.js.map +1 -0
- package/dist/ui/components/Topbar.d.ts +9 -0
- package/dist/ui/components/Topbar.d.ts.map +1 -0
- package/dist/ui/components/Topbar.js +20 -0
- package/dist/ui/components/Topbar.js.map +1 -0
- package/dist/ui/editor/AiAssistPanel.d.ts +8 -0
- package/dist/ui/editor/AiAssistPanel.d.ts.map +1 -0
- package/dist/ui/editor/AiAssistPanel.js +221 -0
- package/dist/ui/editor/AiAssistPanel.js.map +1 -0
- package/dist/ui/editor/ContentForm.d.ts +46 -0
- package/dist/ui/editor/ContentForm.d.ts.map +1 -0
- package/dist/ui/editor/ContentForm.js +821 -0
- package/dist/ui/editor/ContentForm.js.map +1 -0
- package/dist/ui/editor/Rte.d.ts +16 -0
- package/dist/ui/editor/Rte.d.ts.map +1 -0
- package/dist/ui/editor/Rte.js +272 -0
- package/dist/ui/editor/Rte.js.map +1 -0
- package/dist/ui/editor/ai-assist.d.ts +43 -0
- package/dist/ui/editor/ai-assist.d.ts.map +1 -0
- package/dist/ui/editor/ai-assist.js +114 -0
- package/dist/ui/editor/ai-assist.js.map +1 -0
- package/dist/ui/editor/autosave.d.ts +18 -0
- package/dist/ui/editor/autosave.d.ts.map +1 -0
- package/dist/ui/editor/autosave.js +23 -0
- package/dist/ui/editor/autosave.js.map +1 -0
- package/dist/ui/editor/content-payload.d.ts +19 -0
- package/dist/ui/editor/content-payload.d.ts.map +1 -0
- package/dist/ui/editor/content-payload.js +97 -0
- package/dist/ui/editor/content-payload.js.map +1 -0
- package/dist/ui/editor/editor-media-upload.d.ts +6 -0
- package/dist/ui/editor/editor-media-upload.d.ts.map +1 -0
- package/dist/ui/editor/editor-media-upload.js +20 -0
- package/dist/ui/editor/editor-media-upload.js.map +1 -0
- package/dist/ui/editor/serialize.d.ts +6 -0
- package/dist/ui/editor/serialize.d.ts.map +1 -0
- package/dist/ui/editor/serialize.js +479 -0
- package/dist/ui/editor/serialize.js.map +1 -0
- package/dist/ui/editor/tweet-embed.d.ts +4 -0
- package/dist/ui/editor/tweet-embed.d.ts.map +1 -0
- package/dist/ui/editor/tweet-embed.js +49 -0
- package/dist/ui/editor/tweet-embed.js.map +1 -0
- package/dist/ui/hash-router.d.ts +5 -0
- package/dist/ui/hash-router.d.ts.map +1 -0
- package/dist/ui/hash-router.js +25 -0
- package/dist/ui/hash-router.js.map +1 -0
- package/dist/ui/icons.d.ts +32 -0
- package/dist/ui/icons.d.ts.map +1 -0
- package/dist/ui/icons.js +86 -0
- package/dist/ui/icons.js.map +1 -0
- package/dist/ui/inspector/Field.d.ts +12 -0
- package/dist/ui/inspector/Field.d.ts.map +1 -0
- package/dist/ui/inspector/Field.js +8 -0
- package/dist/ui/inspector/Field.js.map +1 -0
- package/dist/ui/inspector/FoundryTab.d.ts +9 -0
- package/dist/ui/inspector/FoundryTab.d.ts.map +1 -0
- package/dist/ui/inspector/FoundryTab.js +362 -0
- package/dist/ui/inspector/FoundryTab.js.map +1 -0
- package/dist/ui/inspector/HistoryTab.d.ts +7 -0
- package/dist/ui/inspector/HistoryTab.d.ts.map +1 -0
- package/dist/ui/inspector/HistoryTab.js +289 -0
- package/dist/ui/inspector/HistoryTab.js.map +1 -0
- package/dist/ui/inspector/Inspector.d.ts +13 -0
- package/dist/ui/inspector/Inspector.d.ts.map +1 -0
- package/dist/ui/inspector/Inspector.js +163 -0
- package/dist/ui/inspector/Inspector.js.map +1 -0
- package/dist/ui/inspector/OrganizeTab.d.ts +15 -0
- package/dist/ui/inspector/OrganizeTab.d.ts.map +1 -0
- package/dist/ui/inspector/OrganizeTab.js +319 -0
- package/dist/ui/inspector/OrganizeTab.js.map +1 -0
- package/dist/ui/inspector/PublishTab.d.ts +18 -0
- package/dist/ui/inspector/PublishTab.d.ts.map +1 -0
- package/dist/ui/inspector/PublishTab.js +339 -0
- package/dist/ui/inspector/PublishTab.js.map +1 -0
- package/dist/ui/inspector/Section.d.ts +10 -0
- package/dist/ui/inspector/Section.d.ts.map +1 -0
- package/dist/ui/inspector/Section.js +40 -0
- package/dist/ui/inspector/Section.js.map +1 -0
- package/dist/ui/inspector/SeoTab.d.ts +19 -0
- package/dist/ui/inspector/SeoTab.d.ts.map +1 -0
- package/dist/ui/inspector/SeoTab.js +328 -0
- package/dist/ui/inspector/SeoTab.js.map +1 -0
- package/dist/ui/inspector/foundry-stages.d.ts +36 -0
- package/dist/ui/inspector/foundry-stages.d.ts.map +1 -0
- package/dist/ui/inspector/foundry-stages.js +101 -0
- package/dist/ui/inspector/foundry-stages.js.map +1 -0
- package/dist/ui/inspector/inspector-data.d.ts +80 -0
- package/dist/ui/inspector/inspector-data.d.ts.map +1 -0
- package/dist/ui/inspector/inspector-data.js +172 -0
- package/dist/ui/inspector/inspector-data.js.map +1 -0
- package/dist/ui/inspector/organize-data.d.ts +23 -0
- package/dist/ui/inspector/organize-data.d.ts.map +1 -0
- package/dist/ui/inspector/organize-data.js +28 -0
- package/dist/ui/inspector/organize-data.js.map +1 -0
- package/dist/ui/inspector/revision-diff.d.ts +49 -0
- package/dist/ui/inspector/revision-diff.d.ts.map +1 -0
- package/dist/ui/inspector/revision-diff.js +166 -0
- package/dist/ui/inspector/revision-diff.js.map +1 -0
- package/dist/ui/inspector/seo-helpers.d.ts +37 -0
- package/dist/ui/inspector/seo-helpers.d.ts.map +1 -0
- package/dist/ui/inspector/seo-helpers.js +37 -0
- package/dist/ui/inspector/seo-helpers.js.map +1 -0
- package/dist/ui/inspector/tab-visibility.d.ts +14 -0
- package/dist/ui/inspector/tab-visibility.d.ts.map +1 -0
- package/dist/ui/inspector/tab-visibility.js +28 -0
- package/dist/ui/inspector/tab-visibility.js.map +1 -0
- package/dist/ui/nav.d.ts +16 -0
- package/dist/ui/nav.d.ts.map +1 -0
- package/dist/ui/nav.js +33 -0
- package/dist/ui/nav.js.map +1 -0
- package/dist/ui/pages/admin.astro +32 -0
- package/dist/ui/preview/draft-page.d.ts +37 -0
- package/dist/ui/preview/draft-page.d.ts.map +1 -0
- package/dist/ui/preview/draft-page.js +212 -0
- package/dist/ui/preview/draft-page.js.map +1 -0
- package/dist/ui/preview/preview-layout.d.ts +23 -0
- package/dist/ui/preview/preview-layout.d.ts.map +1 -0
- package/dist/ui/preview/preview-layout.js +30 -0
- package/dist/ui/preview/preview-layout.js.map +1 -0
- package/dist/ui/screens/AnalyticsScreen.d.ts +2 -0
- package/dist/ui/screens/AnalyticsScreen.d.ts.map +1 -0
- package/dist/ui/screens/AnalyticsScreen.js +408 -0
- package/dist/ui/screens/AnalyticsScreen.js.map +1 -0
- package/dist/ui/screens/AuthorsScreen.d.ts +2 -0
- package/dist/ui/screens/AuthorsScreen.d.ts.map +1 -0
- package/dist/ui/screens/AuthorsScreen.js +225 -0
- package/dist/ui/screens/AuthorsScreen.js.map +1 -0
- package/dist/ui/screens/CalendarScreen.d.ts +6 -0
- package/dist/ui/screens/CalendarScreen.d.ts.map +1 -0
- package/dist/ui/screens/CalendarScreen.js +327 -0
- package/dist/ui/screens/CalendarScreen.js.map +1 -0
- package/dist/ui/screens/ContentInsightsScreen.d.ts +2 -0
- package/dist/ui/screens/ContentInsightsScreen.d.ts.map +1 -0
- package/dist/ui/screens/ContentInsightsScreen.js +129 -0
- package/dist/ui/screens/ContentInsightsScreen.js.map +1 -0
- package/dist/ui/screens/ContentRoute.d.ts +2 -0
- package/dist/ui/screens/ContentRoute.d.ts.map +1 -0
- package/dist/ui/screens/ContentRoute.js +32 -0
- package/dist/ui/screens/ContentRoute.js.map +1 -0
- package/dist/ui/screens/DashboardScreen.d.ts +6 -0
- package/dist/ui/screens/DashboardScreen.d.ts.map +1 -0
- package/dist/ui/screens/DashboardScreen.js +273 -0
- package/dist/ui/screens/DashboardScreen.js.map +1 -0
- package/dist/ui/screens/EditorScreen.d.ts +7 -0
- package/dist/ui/screens/EditorScreen.d.ts.map +1 -0
- package/dist/ui/screens/EditorScreen.js +426 -0
- package/dist/ui/screens/EditorScreen.js.map +1 -0
- package/dist/ui/screens/LibraryScreen.d.ts +6 -0
- package/dist/ui/screens/LibraryScreen.d.ts.map +1 -0
- package/dist/ui/screens/LibraryScreen.js +580 -0
- package/dist/ui/screens/LibraryScreen.js.map +1 -0
- package/dist/ui/screens/MediaScreen.d.ts +2 -0
- package/dist/ui/screens/MediaScreen.d.ts.map +1 -0
- package/dist/ui/screens/MediaScreen.js +173 -0
- package/dist/ui/screens/MediaScreen.js.map +1 -0
- package/dist/ui/screens/SettingsScreen.d.ts +4 -0
- package/dist/ui/screens/SettingsScreen.d.ts.map +1 -0
- package/dist/ui/screens/SettingsScreen.js +751 -0
- package/dist/ui/screens/SettingsScreen.js.map +1 -0
- package/dist/ui/screens/SocialShareScreen.d.ts +2 -0
- package/dist/ui/screens/SocialShareScreen.d.ts.map +1 -0
- package/dist/ui/screens/SocialShareScreen.js +224 -0
- package/dist/ui/screens/SocialShareScreen.js.map +1 -0
- package/dist/ui/screens/SubscriptionsScreen.d.ts +2 -0
- package/dist/ui/screens/SubscriptionsScreen.d.ts.map +1 -0
- package/dist/ui/screens/SubscriptionsScreen.js +441 -0
- package/dist/ui/screens/SubscriptionsScreen.js.map +1 -0
- package/dist/ui/screens/TopicsScreen.d.ts +2 -0
- package/dist/ui/screens/TopicsScreen.d.ts.map +1 -0
- package/dist/ui/screens/TopicsScreen.js +360 -0
- package/dist/ui/screens/TopicsScreen.js.map +1 -0
- package/dist/ui/screens/analytics-data.d.ts +19 -0
- package/dist/ui/screens/analytics-data.d.ts.map +1 -0
- package/dist/ui/screens/analytics-data.js +42 -0
- package/dist/ui/screens/analytics-data.js.map +1 -0
- package/dist/ui/screens/calendar-data.d.ts +45 -0
- package/dist/ui/screens/calendar-data.d.ts.map +1 -0
- package/dist/ui/screens/calendar-data.js +70 -0
- package/dist/ui/screens/calendar-data.js.map +1 -0
- package/dist/ui/screens/content-insights-data.d.ts +54 -0
- package/dist/ui/screens/content-insights-data.d.ts.map +1 -0
- package/dist/ui/screens/content-insights-data.js +82 -0
- package/dist/ui/screens/content-insights-data.js.map +1 -0
- package/dist/ui/screens/content-view.d.ts +21 -0
- package/dist/ui/screens/content-view.d.ts.map +1 -0
- package/dist/ui/screens/content-view.js +17 -0
- package/dist/ui/screens/content-view.js.map +1 -0
- package/dist/ui/screens/library-data.d.ts +76 -0
- package/dist/ui/screens/library-data.d.ts.map +1 -0
- package/dist/ui/screens/library-data.js +116 -0
- package/dist/ui/screens/library-data.js.map +1 -0
- package/dist/ui/screens/media-upload.d.ts +19 -0
- package/dist/ui/screens/media-upload.d.ts.map +1 -0
- package/dist/ui/screens/media-upload.js +187 -0
- package/dist/ui/screens/media-upload.js.map +1 -0
- package/dist/ui/screens/public-url.d.ts +24 -0
- package/dist/ui/screens/public-url.d.ts.map +1 -0
- package/dist/ui/screens/public-url.js +74 -0
- package/dist/ui/screens/public-url.js.map +1 -0
- package/dist/ui/screens/registry.d.ts +3 -0
- package/dist/ui/screens/registry.d.ts.map +1 -0
- package/dist/ui/screens/registry.js +38 -0
- package/dist/ui/screens/registry.js.map +1 -0
- package/dist/ui/screens/render-state.d.ts +105 -0
- package/dist/ui/screens/render-state.d.ts.map +1 -0
- package/dist/ui/screens/render-state.js +127 -0
- package/dist/ui/screens/render-state.js.map +1 -0
- package/dist/ui/screens/settings-data.d.ts +55 -0
- package/dist/ui/screens/settings-data.d.ts.map +1 -0
- package/dist/ui/screens/settings-data.js +89 -0
- package/dist/ui/screens/settings-data.js.map +1 -0
- package/dist/ui/screens/settings-panels-data.d.ts +58 -0
- package/dist/ui/screens/settings-panels-data.d.ts.map +1 -0
- package/dist/ui/screens/settings-panels-data.js +88 -0
- package/dist/ui/screens/settings-panels-data.js.map +1 -0
- package/dist/ui/screens/social-share-data.d.ts +307 -0
- package/dist/ui/screens/social-share-data.d.ts.map +1 -0
- package/dist/ui/screens/social-share-data.js +447 -0
- package/dist/ui/screens/social-share-data.js.map +1 -0
- package/dist/ui/screens/topics-data.d.ts +38 -0
- package/dist/ui/screens/topics-data.d.ts.map +1 -0
- package/dist/ui/screens/topics-data.js +50 -0
- package/dist/ui/screens/topics-data.js.map +1 -0
- package/dist/ui/styles/broadsheet.css +394 -0
- package/dist/ui/theme.d.ts +23 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +87 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/tweaks.d.ts +7 -0
- package/dist/ui/tweaks.d.ts.map +1 -0
- package/dist/ui/tweaks.js +31 -0
- package/dist/ui/tweaks.js.map +1 -0
- package/dist/ui/use-hash-router.d.ts +6 -0
- package/dist/ui/use-hash-router.d.ts.map +1 -0
- package/dist/ui/use-hash-router.js +15 -0
- package/dist/ui/use-hash-router.js.map +1 -0
- package/dist/ui/use-tweaks.d.ts +6 -0
- package/dist/ui/use-tweaks.d.ts.map +1 -0
- package/dist/ui/use-tweaks.js +24 -0
- package/dist/ui/use-tweaks.js.map +1 -0
- package/dist/ui/workspace-context.d.ts +34 -0
- package/dist/ui/workspace-context.d.ts.map +1 -0
- package/dist/ui/workspace-context.js +32 -0
- package/dist/ui/workspace-context.js.map +1 -0
- package/migrations/0001_create_cms_tables.sql +200 -0
- package/migrations/0002_review_softdelete_board.sql +112 -0
- package/migrations/0003_content_contributors.sql +16 -0
- package/migrations/0004_seo_faq_columns.sql +6 -0
- package/migrations/0005_revision_delta_columns.sql +5 -0
- package/migrations/0006_processing_trigger_token.sql +4 -0
- package/migrations/0007_content_slug_redirects.sql +9 -0
- package/migrations/0008_workspace_settings.sql +17 -0
- package/migrations/0009_workspace_memberships.sql +36 -0
- package/migrations/0010_api_keys_webhooks.sql +21 -0
- package/migrations/0011_notifications_activity.sql +22 -0
- package/migrations/0012_content_imports.sql +10 -0
- package/migrations/0013_media_normalization.sql +4 -0
- package/migrations/0014_api_key_prefix.sql +3 -0
- package/migrations/0015_cms_topics.sql +7 -0
- package/migrations/0016_content_insights.sql +53 -0
- package/package.json +82 -0
- package/src/engine/activity-log.ts +39 -0
- package/src/engine/ai-prompts.ts +124 -0
- package/src/engine/ai-writeback.ts +62 -0
- package/src/engine/api-keys.ts +239 -0
- package/src/engine/content-insights.ts +198 -0
- package/src/engine/contributors.ts +95 -0
- package/src/engine/cron.ts +62 -0
- package/src/engine/d1.ts +29 -0
- package/src/engine/foundry-dispatch.ts +417 -0
- package/src/engine/import-parsers.ts +478 -0
- package/src/engine/index.ts +230 -0
- package/src/engine/invites.ts +271 -0
- package/src/engine/members.ts +216 -0
- package/src/engine/membership-rules.ts +63 -0
- package/src/engine/og-render.ts +59 -0
- package/src/engine/publish-guard.ts +123 -0
- package/src/engine/publisher.ts +1032 -0
- package/src/engine/revisions.ts +292 -0
- package/src/engine/sanitize.ts +183 -0
- package/src/engine/seed-membership.ts +92 -0
- package/src/engine/seo.ts +72 -0
- package/src/engine/slug-redirects.ts +34 -0
- package/src/engine/slug.ts +33 -0
- package/src/engine/soft-delete.ts +42 -0
- package/src/engine/tags.ts +95 -0
- package/src/engine/topics.ts +158 -0
- package/src/engine/url-guard.ts +136 -0
- package/src/engine/validator/checks/bare-url-not-autolinked.ts +78 -0
- package/src/engine/validator/checks/broken-footnote-label.ts +33 -0
- package/src/engine/validator/checks/double-encoded-entities.ts +46 -0
- package/src/engine/validator/checks/empty-alt-text.ts +35 -0
- package/src/engine/validator/checks/heading-hierarchy-skip.ts +33 -0
- package/src/engine/validator/checks/html-comment-leak.ts +58 -0
- package/src/engine/validator/checks/iframe-missing-dims-and-wrapper.ts +34 -0
- package/src/engine/validator/checks/invisible-control-chars.ts +58 -0
- package/src/engine/validator/checks/paywall-marker-leak.ts +43 -0
- package/src/engine/validator/checks/raw-block-html.ts +65 -0
- package/src/engine/validator/checks/stale-body-html.ts +39 -0
- package/src/engine/validator/checks/unresolved-footnote-anchor.ts +61 -0
- package/src/engine/validator/checks/word-gdocs-paste-artifacts.ts +72 -0
- package/src/engine/validator/index.ts +385 -0
- package/src/engine/validator/scan.ts +103 -0
- package/src/engine/validator/types.ts +114 -0
- package/src/engine/webhook-signer.ts +139 -0
- package/src/engine/webhooks.ts +224 -0
- package/src/index.ts +79 -0
- package/src/integration/index.ts +298 -0
- package/src/integration/options.ts +30 -0
- package/src/integration/vite-plugin.ts +37 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/null.ts +160 -0
- package/src/providers/types.ts +284 -0
- package/src/routes/ai.ts +461 -0
- package/src/routes/analytics.ts +78 -0
- package/src/routes/api-keys.ts +133 -0
- package/src/routes/authors.ts +282 -0
- package/src/routes/authz-matrix.ts +239 -0
- package/src/routes/calendar.ts +127 -0
- package/src/routes/config.ts +99 -0
- package/src/routes/content-insights.ts +159 -0
- package/src/routes/content.ts +1753 -0
- package/src/routes/context.ts +146 -0
- package/src/routes/cron.ts +27 -0
- package/src/routes/dashboard.ts +174 -0
- package/src/routes/imports.ts +190 -0
- package/src/routes/index.ts +295 -0
- package/src/routes/media-lib.ts +405 -0
- package/src/routes/media.ts +944 -0
- package/src/routes/preview.ts +182 -0
- package/src/routes/rbac-invites.ts +220 -0
- package/src/routes/rbac.ts +155 -0
- package/src/routes/shell.ts +163 -0
- package/src/routes/subscriptions.ts +167 -0
- package/src/routes/tags.ts +93 -0
- package/src/routes/topics.ts +58 -0
- package/src/routes/webhooks.ts +233 -0
- package/src/schema/index.ts +45 -0
- package/src/schema/insights-ingest.ts +126 -0
- package/src/schema/migrations.ts +599 -0
- package/src/schema/tables.ts +59 -0
- package/src/schema/types.ts +576 -0
- package/src/ui/api/_authz.ts +100 -0
- package/src/ui/api/_content-config.ts +75 -0
- package/src/ui/api/activity.ts +33 -0
- package/src/ui/api/analytics.ts +42 -0
- package/src/ui/api/authors/[id].ts +23 -0
- package/src/ui/api/authors.ts +19 -0
- package/src/ui/api/calendar.ts +21 -0
- package/src/ui/api/content/[id]/ai/headlines.ts +10 -0
- package/src/ui/api/content/[id]/ai/meta-description.ts +11 -0
- package/src/ui/api/content/[id]/ai/og-image.ts +10 -0
- package/src/ui/api/content/[id]/ai/proofread.ts +10 -0
- package/src/ui/api/content/[id]/ai/takeaways.ts +11 -0
- package/src/ui/api/content/[id]/contributors.ts +13 -0
- package/src/ui/api/content/[id]/preview-token.ts +21 -0
- package/src/ui/api/content/[id]/revisions/[rev]/restore.ts +12 -0
- package/src/ui/api/content/[id]/revisions/[rev].ts +11 -0
- package/src/ui/api/content/[id]/revisions.ts +9 -0
- package/src/ui/api/content/[id]/seo-score.ts +10 -0
- package/src/ui/api/content/[id].ts +23 -0
- package/src/ui/api/content/bulk.ts +10 -0
- package/src/ui/api/content/counts.ts +11 -0
- package/src/ui/api/content/foundry-callback.ts +11 -0
- package/src/ui/api/content/import/confirm.ts +16 -0
- package/src/ui/api/content/import/parse.ts +16 -0
- package/src/ui/api/content/insights-ingest.ts +24 -0
- package/src/ui/api/content-insights/dismiss.ts +23 -0
- package/src/ui/api/content-insights/index.ts +21 -0
- package/src/ui/api/content-insights/undismiss.ts +23 -0
- package/src/ui/api/content.ts +21 -0
- package/src/ui/api/dashboard.ts +28 -0
- package/src/ui/api/me.ts +23 -0
- package/src/ui/api/media/[id].ts +22 -0
- package/src/ui/api/media/images.ts +9 -0
- package/src/ui/api/media/library/[id].ts +9 -0
- package/src/ui/api/media/library.ts +22 -0
- package/src/ui/api/media/podcast/abort.ts +6 -0
- package/src/ui/api/media/podcast/complete.ts +6 -0
- package/src/ui/api/media/podcast/init.ts +6 -0
- package/src/ui/api/media/podcast/part.ts +6 -0
- package/src/ui/api/media/podcast.ts +9 -0
- package/src/ui/api/media/videos/abort.ts +9 -0
- package/src/ui/api/media/videos/complete.ts +9 -0
- package/src/ui/api/media/videos/init.ts +9 -0
- package/src/ui/api/media/videos/part.ts +9 -0
- package/src/ui/api/notifications.ts +26 -0
- package/src/ui/api/search.ts +23 -0
- package/src/ui/api/settings/api-keys/[id].ts +28 -0
- package/src/ui/api/settings/api-keys.ts +25 -0
- package/src/ui/api/settings/domains.ts +37 -0
- package/src/ui/api/settings/integrations.ts +40 -0
- package/src/ui/api/settings/members/[userId].ts +33 -0
- package/src/ui/api/settings/members/invite.ts +27 -0
- package/src/ui/api/settings/members.ts +23 -0
- package/src/ui/api/settings/webhooks/[id]/test.ts +30 -0
- package/src/ui/api/settings/webhooks/[id].ts +29 -0
- package/src/ui/api/settings/webhooks.ts +27 -0
- package/src/ui/api/subscriptions.ts +56 -0
- package/src/ui/api/tags/[id].ts +24 -0
- package/src/ui/api/tags/index.ts +18 -0
- package/src/ui/api/topics/[id].ts +20 -0
- package/src/ui/api/topics/index.ts +17 -0
- package/src/ui/api/workspace-settings.ts +26 -0
- package/src/ui/client/boot-state.ts +42 -0
- package/src/ui/client/mount.tsx +41 -0
- package/src/ui/commands.ts +62 -0
- package/src/ui/components/CmsApp.tsx +149 -0
- package/src/ui/components/CommandPalette.tsx +118 -0
- package/src/ui/components/NoAccessScreen.tsx +79 -0
- package/src/ui/components/ShareModal.tsx +650 -0
- package/src/ui/components/SharePickers.tsx +790 -0
- package/src/ui/components/ShareStatsPanel.tsx +721 -0
- package/src/ui/components/Sidebar.tsx +86 -0
- package/src/ui/components/SiteSwitcher.tsx +100 -0
- package/src/ui/components/Topbar.tsx +93 -0
- package/src/ui/editor/AiAssistPanel.tsx +407 -0
- package/src/ui/editor/ContentForm.tsx +1462 -0
- package/src/ui/editor/Rte.tsx +382 -0
- package/src/ui/editor/ai-assist.ts +139 -0
- package/src/ui/editor/autosave.ts +36 -0
- package/src/ui/editor/content-payload.ts +125 -0
- package/src/ui/editor/editor-media-upload.ts +26 -0
- package/src/ui/editor/serialize.ts +522 -0
- package/src/ui/editor/tweet-embed.ts +60 -0
- package/src/ui/hash-router.ts +30 -0
- package/src/ui/icons.tsx +208 -0
- package/src/ui/inspector/Field.tsx +30 -0
- package/src/ui/inspector/FoundryTab.tsx +613 -0
- package/src/ui/inspector/HistoryTab.tsx +482 -0
- package/src/ui/inspector/Inspector.tsx +328 -0
- package/src/ui/inspector/OrganizeTab.tsx +534 -0
- package/src/ui/inspector/PublishTab.tsx +626 -0
- package/src/ui/inspector/Section.tsx +81 -0
- package/src/ui/inspector/SeoTab.tsx +573 -0
- package/src/ui/inspector/foundry-stages.ts +140 -0
- package/src/ui/inspector/inspector-data.ts +232 -0
- package/src/ui/inspector/organize-data.ts +51 -0
- package/src/ui/inspector/revision-diff.ts +213 -0
- package/src/ui/inspector/seo-helpers.ts +71 -0
- package/src/ui/inspector/tab-visibility.ts +37 -0
- package/src/ui/nav.ts +48 -0
- package/src/ui/pages/admin.astro +32 -0
- package/src/ui/preview/draft-page.tsx +395 -0
- package/src/ui/preview/preview-layout.ts +49 -0
- package/src/ui/screens/AnalyticsScreen.tsx +938 -0
- package/src/ui/screens/AuthorsScreen.tsx +524 -0
- package/src/ui/screens/CalendarScreen.tsx +694 -0
- package/src/ui/screens/ContentInsightsScreen.tsx +417 -0
- package/src/ui/screens/ContentRoute.tsx +35 -0
- package/src/ui/screens/DashboardScreen.tsx +654 -0
- package/src/ui/screens/EditorScreen.tsx +673 -0
- package/src/ui/screens/LibraryScreen.tsx +1350 -0
- package/src/ui/screens/MediaScreen.tsx +357 -0
- package/src/ui/screens/SettingsScreen.tsx +1841 -0
- package/src/ui/screens/SocialShareScreen.tsx +670 -0
- package/src/ui/screens/SubscriptionsScreen.tsx +1240 -0
- package/src/ui/screens/TopicsScreen.tsx +912 -0
- package/src/ui/screens/analytics-data.ts +68 -0
- package/src/ui/screens/calendar-data.ts +126 -0
- package/src/ui/screens/content-insights-data.ts +127 -0
- package/src/ui/screens/content-view.ts +30 -0
- package/src/ui/screens/library-data.ts +177 -0
- package/src/ui/screens/media-upload.ts +283 -0
- package/src/ui/screens/public-url.ts +81 -0
- package/src/ui/screens/registry.tsx +53 -0
- package/src/ui/screens/render-state.ts +228 -0
- package/src/ui/screens/settings-data.ts +140 -0
- package/src/ui/screens/settings-panels-data.ts +142 -0
- package/src/ui/screens/social-share-data.ts +753 -0
- package/src/ui/screens/topics-data.ts +75 -0
- package/src/ui/styles/broadsheet.css +394 -0
- package/src/ui/theme.ts +104 -0
- package/src/ui/tweaks.ts +37 -0
- package/src/ui/use-hash-router.ts +17 -0
- package/src/ui/use-tweaks.ts +31 -0
- package/src/ui/workspace-context.tsx +62 -0
- package/src/virtual.d.ts +4 -0
package/src/routes/ai.ts
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// AI house-style assist routes (P4 Tasks 3 + 4).
|
|
2
|
+
// createAiRoutes(config) — streaming actions: proofreadAndTighten + suggestHeadlines (SSE)
|
|
3
|
+
// — sync actions: generateKeyTakeaways + draftMetaDescription.
|
|
4
|
+
//
|
|
5
|
+
// Architecture:
|
|
6
|
+
// - Every AI call goes through the injected LlmDispatch hook (config.hooks.llm).
|
|
7
|
+
// Absent hook → 501. This is the §9 hard constraint.
|
|
8
|
+
// - The streaming actions call dispatch({ ...prompt, stream: true }) and return the
|
|
9
|
+
// result as a text/event-stream Response. If the hook returns { text } (a non-
|
|
10
|
+
// streaming implementation), the text is wrapped as a single SSE event (graceful
|
|
11
|
+
// fallback).
|
|
12
|
+
// - Streaming actions are read-only suggestions — they NEVER write back to D1.
|
|
13
|
+
// - Sync actions (takeaways, meta-description) call dispatch({ ...prompt, stream: false }),
|
|
14
|
+
// apply the ai_locked_fields gate, persist when allowed, and return the result.
|
|
15
|
+
// - OG-image generation lands in Task 5.
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
headlinesPrompt,
|
|
19
|
+
metaDescriptionPrompt,
|
|
20
|
+
proofreadPrompt,
|
|
21
|
+
takeawaysPrompt,
|
|
22
|
+
} from '../engine/ai-prompts.js'
|
|
23
|
+
import { applyAiWrite, parseLockedFields } from '../engine/ai-writeback.js'
|
|
24
|
+
import { buildOgCardSpec } from '../engine/og-render.js'
|
|
25
|
+
import { type ContentItemRecord, getContentItem } from '../engine/publisher.js'
|
|
26
|
+
import type { LlmDispatchResult } from '../providers/types.js'
|
|
27
|
+
import type { CmsRouteConfig } from './config.js'
|
|
28
|
+
import { resolveConfig } from './config.js'
|
|
29
|
+
import { type Authz, type Capability, json, type RouteContext, requireId } from './context.js'
|
|
30
|
+
import { buildImageAssetKey, insertMediaAsset, publicUrlFor } from './media-lib.js'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* AI editorial assists are write-adjacent surfaces (proofread/headlines are
|
|
34
|
+
* read-only suggestions; takeaways/meta/og persist). They gate on the
|
|
35
|
+
* edit_own_draft capability via requireRole when the host wires the role seam
|
|
36
|
+
* (any of the 5 roles holds it — no launch-role lockout), falling back to the
|
|
37
|
+
* flat requirePublisher gate otherwise. Actual write-back is additionally gated
|
|
38
|
+
* by the ownership-aware content routes.
|
|
39
|
+
*/
|
|
40
|
+
async function aiGuard(authz: Authz, ctx: RouteContext): Promise<Response | null> {
|
|
41
|
+
if (authz.requireRole) {
|
|
42
|
+
return authz.requireRole('edit_own_draft' satisfies Capability, ctx)
|
|
43
|
+
}
|
|
44
|
+
return authz.requirePublisher(ctx)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface AiRouteHandlers {
|
|
48
|
+
/** POST /content/:id/ai/proofread — SSE streaming proofread & tighten. */
|
|
49
|
+
proofreadAndTighten(ctx: RouteContext): Promise<Response>
|
|
50
|
+
/** POST /content/:id/ai/headlines — SSE streaming 5-headline suggestions. */
|
|
51
|
+
suggestHeadlines(ctx: RouteContext): Promise<Response>
|
|
52
|
+
/** POST /content/:id/ai/takeaways — sync: generate + persist 3 key takeaways. */
|
|
53
|
+
generateKeyTakeaways(ctx: RouteContext): Promise<Response>
|
|
54
|
+
/** POST /content/:id/ai/meta-description — sync: draft + persist meta description (<160 chars). */
|
|
55
|
+
draftMetaDescription(ctx: RouteContext): Promise<Response>
|
|
56
|
+
/** POST /content/:id/ai/og-image — render + persist a social-card OG image via the injected OgImageRenderer. */
|
|
57
|
+
generateOgImage(ctx: RouteContext): Promise<Response>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Clamp a meta description to at most `limit` characters, breaking at a word
|
|
62
|
+
* boundary. Trailing whitespace is trimmed from the result.
|
|
63
|
+
*
|
|
64
|
+
* Algorithm: if the text is already within the limit, return it unchanged.
|
|
65
|
+
* Otherwise find the last space at or before position `limit` and cut there.
|
|
66
|
+
* If there is no space before `limit`, perform a hard cut at `limit`.
|
|
67
|
+
*/
|
|
68
|
+
export function clampMeta(text: string, limit: number): string {
|
|
69
|
+
const trimmed = text.trim()
|
|
70
|
+
if (trimmed.length <= limit) return trimmed
|
|
71
|
+
|
|
72
|
+
// Find the last space at or before `limit`.
|
|
73
|
+
const slice = trimmed.slice(0, limit)
|
|
74
|
+
const lastSpace = slice.lastIndexOf(' ')
|
|
75
|
+
if (lastSpace > 0) {
|
|
76
|
+
return slice.slice(0, lastSpace).trimEnd()
|
|
77
|
+
}
|
|
78
|
+
// No space found — hard cut.
|
|
79
|
+
return slice.trimEnd()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Tolerant JSON parse for the takeaways LLM response.
|
|
84
|
+
* The LLM is instructed to return a JSON array of 3 strings; in practice it may
|
|
85
|
+
* return malformed JSON or wrap the array in markdown. We try JSON.parse first;
|
|
86
|
+
* on failure we wrap the raw text as a single-element array so the caller always
|
|
87
|
+
* gets a string[].
|
|
88
|
+
*/
|
|
89
|
+
function parseTakeawaysText(text: string): string[] {
|
|
90
|
+
const t = text.trim()
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(t)
|
|
93
|
+
if (Array.isArray(parsed)) {
|
|
94
|
+
return parsed.map((item) => String(item))
|
|
95
|
+
}
|
|
96
|
+
// Non-array JSON — wrap it
|
|
97
|
+
return [String(parsed)]
|
|
98
|
+
} catch {
|
|
99
|
+
// Not JSON — return the raw text as a single element
|
|
100
|
+
return [t]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Wrap a ReadableStream or { text } result from LlmDispatch as an SSE Response.
|
|
106
|
+
*
|
|
107
|
+
* - ReadableStream → pass through as text/event-stream (the provider does SSE framing).
|
|
108
|
+
* - { text } → wrap the text as one SSE data event (graceful fallback for sync impls).
|
|
109
|
+
*/
|
|
110
|
+
function isUnavailableLlmResult(
|
|
111
|
+
result: LlmDispatchResult,
|
|
112
|
+
): result is { text: string; status: 'unavailable'; reason?: string } {
|
|
113
|
+
return !(result instanceof ReadableStream) && result.status === 'unavailable'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function llmUnavailableResponse(
|
|
117
|
+
result: { reason?: string },
|
|
118
|
+
extra: Record<string, unknown> = {},
|
|
119
|
+
): Response {
|
|
120
|
+
return json(
|
|
121
|
+
{
|
|
122
|
+
error: 'AI generation is unavailable',
|
|
123
|
+
written: false,
|
|
124
|
+
...(result.reason ? { reason: result.reason } : {}),
|
|
125
|
+
...extra,
|
|
126
|
+
},
|
|
127
|
+
503,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toSseResponse(result: LlmDispatchResult): Response {
|
|
132
|
+
if (isUnavailableLlmResult(result)) return llmUnavailableResponse(result)
|
|
133
|
+
|
|
134
|
+
const headers = {
|
|
135
|
+
'Content-Type': 'text/event-stream',
|
|
136
|
+
'Cache-Control': 'no-cache',
|
|
137
|
+
'X-Content-Type-Options': 'nosniff',
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (result instanceof ReadableStream) {
|
|
141
|
+
return new Response(result, { status: 200, headers })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// { text } fallback: emit a single SSE data event
|
|
145
|
+
const body = `data: ${result.text}\n\n`
|
|
146
|
+
return new Response(body, { status: 200, headers })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Load the article body markdown for a given content item id.
|
|
151
|
+
* Returns null if the item does not exist or has no article_content row.
|
|
152
|
+
*/
|
|
153
|
+
async function loadArticleBody(
|
|
154
|
+
ctx: RouteContext,
|
|
155
|
+
id: string,
|
|
156
|
+
): Promise<{ item: ContentItemRecord; bodyMarkdown: string } | null> {
|
|
157
|
+
const item = await getContentItem(ctx.db, id)
|
|
158
|
+
if (!item) return null
|
|
159
|
+
|
|
160
|
+
// For non-article types we still load the body if available; for article/newsletter
|
|
161
|
+
// the body_markdown column is in article_content.
|
|
162
|
+
const row = await ctx.db
|
|
163
|
+
.prepare('SELECT body_markdown FROM article_content WHERE content_id = ? LIMIT 1')
|
|
164
|
+
.bind(id)
|
|
165
|
+
.first<{ body_markdown: string | null }>()
|
|
166
|
+
|
|
167
|
+
const bodyMarkdown = row?.body_markdown ?? ''
|
|
168
|
+
return { item, bodyMarkdown }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function createAiRoutes(config: CmsRouteConfig): AiRouteHandlers {
|
|
172
|
+
const resolved = resolveConfig(config)
|
|
173
|
+
|
|
174
|
+
async function proofreadAndTighten(ctx: RouteContext): Promise<Response> {
|
|
175
|
+
// Guard: edit_own_draft capability (any member; ownership-aware write-back).
|
|
176
|
+
const authGuard = await aiGuard(resolved.authz, ctx)
|
|
177
|
+
if (authGuard) return authGuard
|
|
178
|
+
|
|
179
|
+
// Guard: llm hook must be configured.
|
|
180
|
+
if (!resolved.hooks.llm) {
|
|
181
|
+
return json({ error: 'AI generation is not configured' }, 501)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Resolve the content id.
|
|
185
|
+
const idResult = requireId(ctx)
|
|
186
|
+
if ('error' in idResult) return idResult.error
|
|
187
|
+
const { id } = idResult
|
|
188
|
+
|
|
189
|
+
// Load the item + body.
|
|
190
|
+
const loaded = await loadArticleBody(ctx, id)
|
|
191
|
+
if (!loaded) return json({ error: 'Not found' }, 404)
|
|
192
|
+
|
|
193
|
+
const { bodyMarkdown } = loaded
|
|
194
|
+
|
|
195
|
+
// Build the prompt and dispatch (streaming).
|
|
196
|
+
const prompt = proofreadPrompt({ body: bodyMarkdown })
|
|
197
|
+
const result = await resolved.hooks.llm.dispatch({ ...prompt, stream: true })
|
|
198
|
+
|
|
199
|
+
return toSseResponse(result)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function suggestHeadlines(ctx: RouteContext): Promise<Response> {
|
|
203
|
+
// Guard: edit_own_draft capability (any member; ownership-aware write-back).
|
|
204
|
+
const authGuard = await aiGuard(resolved.authz, ctx)
|
|
205
|
+
if (authGuard) return authGuard
|
|
206
|
+
|
|
207
|
+
// Guard: llm hook must be configured.
|
|
208
|
+
if (!resolved.hooks.llm) {
|
|
209
|
+
return json({ error: 'AI generation is not configured' }, 501)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Resolve the content id.
|
|
213
|
+
const idResult = requireId(ctx)
|
|
214
|
+
if ('error' in idResult) return idResult.error
|
|
215
|
+
const { id } = idResult
|
|
216
|
+
|
|
217
|
+
// Load the item + body.
|
|
218
|
+
const loaded = await loadArticleBody(ctx, id)
|
|
219
|
+
if (!loaded) return json({ error: 'Not found' }, 404)
|
|
220
|
+
|
|
221
|
+
const { item, bodyMarkdown } = loaded
|
|
222
|
+
|
|
223
|
+
// Build the prompt and dispatch (streaming).
|
|
224
|
+
const prompt = headlinesPrompt({ title: item.title, body: bodyMarkdown })
|
|
225
|
+
const result = await resolved.hooks.llm.dispatch({ ...prompt, stream: true })
|
|
226
|
+
|
|
227
|
+
return toSseResponse(result)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function generateKeyTakeaways(ctx: RouteContext): Promise<Response> {
|
|
231
|
+
// Guard: edit_own_draft capability (any member; ownership-aware write-back).
|
|
232
|
+
const authGuard = await aiGuard(resolved.authz, ctx)
|
|
233
|
+
if (authGuard) return authGuard
|
|
234
|
+
|
|
235
|
+
// Guard: llm hook must be configured.
|
|
236
|
+
if (!resolved.hooks.llm) {
|
|
237
|
+
return json({ error: 'AI generation is not configured' }, 501)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Resolve the content id.
|
|
241
|
+
const idResult = requireId(ctx)
|
|
242
|
+
if ('error' in idResult) return idResult.error
|
|
243
|
+
const { id } = idResult
|
|
244
|
+
|
|
245
|
+
// Load the item.
|
|
246
|
+
const item = await getContentItem(ctx.db, id)
|
|
247
|
+
if (!item) return json({ error: 'Not found' }, 404)
|
|
248
|
+
|
|
249
|
+
// Takeaways only makes sense for articles (mirrors regenerate-takeaways guard).
|
|
250
|
+
if (item.type !== 'article') {
|
|
251
|
+
return json({ error: 'Invalid content type — takeaways require an article' }, 400)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Load the article body.
|
|
255
|
+
const bodyRow = await ctx.db
|
|
256
|
+
.prepare('SELECT body_markdown FROM article_content WHERE content_id = ? LIMIT 1')
|
|
257
|
+
.bind(id)
|
|
258
|
+
.first<{ body_markdown: string | null }>()
|
|
259
|
+
const bodyMarkdown = bodyRow?.body_markdown ?? ''
|
|
260
|
+
|
|
261
|
+
// Build the prompt and dispatch (sync).
|
|
262
|
+
const prompt = takeawaysPrompt({ body: bodyMarkdown })
|
|
263
|
+
const result = await resolved.hooks.llm.dispatch({ ...prompt, stream: false })
|
|
264
|
+
if (isUnavailableLlmResult(result)) {
|
|
265
|
+
return llmUnavailableResponse(result, { takeaways: [] })
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// result should be { text } for a sync (stream:false) call
|
|
269
|
+
const text = 'text' in result ? result.text : ''
|
|
270
|
+
const takeaways = parseTakeawaysText(text)
|
|
271
|
+
|
|
272
|
+
// Apply the ai_locked_fields write-back gate.
|
|
273
|
+
const lockedFields = parseLockedFields(item.ai_locked_fields)
|
|
274
|
+
const writeResult = applyAiWrite({
|
|
275
|
+
locked: lockedFields,
|
|
276
|
+
field: 'ai_takeaways',
|
|
277
|
+
value: takeaways,
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
if (writeResult.written) {
|
|
281
|
+
const now = Math.floor(Date.now() / 1000)
|
|
282
|
+
await ctx.db
|
|
283
|
+
.prepare(
|
|
284
|
+
`UPDATE article_content
|
|
285
|
+
SET ai_takeaways = ?,
|
|
286
|
+
ai_takeaways_at = ?,
|
|
287
|
+
ai_takeaways_model = ?
|
|
288
|
+
WHERE content_id = ?`,
|
|
289
|
+
)
|
|
290
|
+
.bind(JSON.stringify(takeaways), now, 'llm-dispatch', id)
|
|
291
|
+
.run()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return json({
|
|
295
|
+
takeaways: writeResult.value,
|
|
296
|
+
written: writeResult.written,
|
|
297
|
+
...(writeResult.written ? {} : { locked: true }),
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function draftMetaDescription(ctx: RouteContext): Promise<Response> {
|
|
302
|
+
// Guard: edit_own_draft capability (any member; ownership-aware write-back).
|
|
303
|
+
const authGuard = await aiGuard(resolved.authz, ctx)
|
|
304
|
+
if (authGuard) return authGuard
|
|
305
|
+
|
|
306
|
+
// Guard: llm hook must be configured.
|
|
307
|
+
if (!resolved.hooks.llm) {
|
|
308
|
+
return json({ error: 'AI generation is not configured' }, 501)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Resolve the content id.
|
|
312
|
+
const idResult = requireId(ctx)
|
|
313
|
+
if ('error' in idResult) return idResult.error
|
|
314
|
+
const { id } = idResult
|
|
315
|
+
|
|
316
|
+
// Load the item.
|
|
317
|
+
const item = await getContentItem(ctx.db, id)
|
|
318
|
+
if (!item) return json({ error: 'Not found' }, 404)
|
|
319
|
+
|
|
320
|
+
// Load the article body (best-effort — non-article types use empty string).
|
|
321
|
+
let bodyMarkdown = ''
|
|
322
|
+
if (item.type === 'article' || item.type === 'newsletter') {
|
|
323
|
+
const bodyRow = await ctx.db
|
|
324
|
+
.prepare('SELECT body_markdown FROM article_content WHERE content_id = ? LIMIT 1')
|
|
325
|
+
.bind(id)
|
|
326
|
+
.first<{ body_markdown: string | null }>()
|
|
327
|
+
bodyMarkdown = bodyRow?.body_markdown ?? ''
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Build the prompt and dispatch (sync).
|
|
331
|
+
const prompt = metaDescriptionPrompt({ title: item.title, body: bodyMarkdown })
|
|
332
|
+
const result = await resolved.hooks.llm.dispatch({ ...prompt, stream: false })
|
|
333
|
+
if (isUnavailableLlmResult(result)) {
|
|
334
|
+
return llmUnavailableResponse(result, { description: null })
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// result should be { text } for a sync (stream:false) call
|
|
338
|
+
const rawText = 'text' in result ? result.text : ''
|
|
339
|
+
|
|
340
|
+
// Belt-and-braces: clamp to 160 chars at a word boundary.
|
|
341
|
+
const description = clampMeta(rawText, 160)
|
|
342
|
+
|
|
343
|
+
// Apply the ai_locked_fields write-back gate.
|
|
344
|
+
const lockedFields = parseLockedFields(item.ai_locked_fields)
|
|
345
|
+
const writeResult = applyAiWrite({
|
|
346
|
+
locked: lockedFields,
|
|
347
|
+
field: 'description',
|
|
348
|
+
value: description,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
if (writeResult.written) {
|
|
352
|
+
await ctx.db
|
|
353
|
+
.prepare('UPDATE content_items SET description = ?, updated_at = unixepoch() WHERE id = ?')
|
|
354
|
+
.bind(description, id)
|
|
355
|
+
.run()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return json({
|
|
359
|
+
description: writeResult.value,
|
|
360
|
+
written: writeResult.written,
|
|
361
|
+
...(writeResult.written ? {} : { locked: true }),
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function generateOgImage(ctx: RouteContext): Promise<Response> {
|
|
366
|
+
// Guard: edit_own_draft capability (any member; ownership-aware write-back).
|
|
367
|
+
const authGuard = await aiGuard(resolved.authz, ctx)
|
|
368
|
+
if (authGuard) return authGuard
|
|
369
|
+
|
|
370
|
+
// Guard: ogRenderer hook must be configured.
|
|
371
|
+
if (!resolved.hooks.ogRenderer) {
|
|
372
|
+
return json({ error: 'OG image rendering is not configured' }, 501)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Guard: bucket must be available.
|
|
376
|
+
if (!ctx.bucket) {
|
|
377
|
+
return json({ error: 'Missing R2 bucket configuration' }, 500)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Resolve the content id.
|
|
381
|
+
const idResult = requireId(ctx)
|
|
382
|
+
if ('error' in idResult) return idResult.error
|
|
383
|
+
const { id } = idResult
|
|
384
|
+
|
|
385
|
+
// Load the content item.
|
|
386
|
+
const item = await getContentItem(ctx.db, id)
|
|
387
|
+
if (!item) return json({ error: 'Not found' }, 404)
|
|
388
|
+
|
|
389
|
+
// Build the card spec from the content row.
|
|
390
|
+
// Brand defaults to the configured mediaPublicDomain as a publication label
|
|
391
|
+
// (stripped of TLD) or a generic fallback.
|
|
392
|
+
const brand = resolved.mediaPublicDomain
|
|
393
|
+
? resolved.mediaPublicDomain.split('.').slice(0, -1).join('.') || resolved.mediaPublicDomain
|
|
394
|
+
: 'The Publication'
|
|
395
|
+
|
|
396
|
+
const cardSpec = buildOgCardSpec(item, { brand })
|
|
397
|
+
|
|
398
|
+
// Render the OG image via the injected renderer.
|
|
399
|
+
const pngBytes = await resolved.hooks.ogRenderer.render(cardSpec)
|
|
400
|
+
|
|
401
|
+
// Persist the PNG to R2.
|
|
402
|
+
const imageKey = buildImageAssetKey(item.slug, 'og-image.png', crypto.randomUUID())
|
|
403
|
+
await ctx.bucket.put(imageKey, pngBytes, {
|
|
404
|
+
httpMetadata: { contentType: 'image/png' },
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
// Insert a media_assets row.
|
|
408
|
+
const publicUrl = publicUrlFor({
|
|
409
|
+
r2Binding: resolved.mediaR2Binding,
|
|
410
|
+
objectKey: imageKey,
|
|
411
|
+
publicDomain: resolved.mediaPublicDomain,
|
|
412
|
+
})
|
|
413
|
+
await insertMediaAsset(ctx.db, {
|
|
414
|
+
id: imageKey,
|
|
415
|
+
siteId: resolved.mediaSiteId,
|
|
416
|
+
assetType: 'image',
|
|
417
|
+
sourceType: 'ai-og-image',
|
|
418
|
+
originalFilename: 'og-image.png',
|
|
419
|
+
status: 'ready',
|
|
420
|
+
objectKey: imageKey,
|
|
421
|
+
publicUrl,
|
|
422
|
+
url: publicUrl,
|
|
423
|
+
width: null,
|
|
424
|
+
height: null,
|
|
425
|
+
variantsJson: null,
|
|
426
|
+
r2Binding: resolved.mediaR2Binding,
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
// Apply the ai_locked_fields write-back gate for ai_og_image_id.
|
|
430
|
+
const lockedFields = parseLockedFields(item.ai_locked_fields)
|
|
431
|
+
const writeResult = applyAiWrite({
|
|
432
|
+
locked: lockedFields,
|
|
433
|
+
field: 'ai_og_image_id',
|
|
434
|
+
value: imageKey,
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
if (writeResult.written) {
|
|
438
|
+
await ctx.db
|
|
439
|
+
.prepare(
|
|
440
|
+
'UPDATE content_items SET ai_og_image_id = ?, updated_at = unixepoch() WHERE id = ?',
|
|
441
|
+
)
|
|
442
|
+
.bind(imageKey, id)
|
|
443
|
+
.run()
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return json({
|
|
447
|
+
ogImageId: imageKey,
|
|
448
|
+
url: publicUrl,
|
|
449
|
+
written: writeResult.written,
|
|
450
|
+
...(writeResult.written ? {} : { locked: true }),
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
proofreadAndTighten,
|
|
456
|
+
suggestHeadlines,
|
|
457
|
+
generateKeyTakeaways,
|
|
458
|
+
draftMetaDescription,
|
|
459
|
+
generateOgImage,
|
|
460
|
+
}
|
|
461
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/routes/analytics.ts — Analytics route factory (P5 Task 5).
|
|
2
|
+
// All four handlers are AnalyticsProvider passthrough: KPIs, reads-over-time,
|
|
3
|
+
// top content, and traffic sources. Each handler accepts a ?range= param
|
|
4
|
+
// (7d / 30d / 90d, defaulting to 30d) and returns the ProviderResult
|
|
5
|
+
// {status, data} envelope unchanged — the screen branches on status.
|
|
6
|
+
// No D1 analytics query anywhere; reads/sessions/etc are the provider's domain.
|
|
7
|
+
|
|
8
|
+
import { nullProviders } from '../providers/null.js'
|
|
9
|
+
import type { CmsProviders, DateRange } from '../providers/types.js'
|
|
10
|
+
import type { Authz, RouteContext } from './context.js'
|
|
11
|
+
import { json } from './context.js'
|
|
12
|
+
|
|
13
|
+
export interface AnalyticsRouteConfig {
|
|
14
|
+
authz: Authz
|
|
15
|
+
providers?: Partial<CmsProviders>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AnalyticsRouteHandlers {
|
|
19
|
+
kpis(ctx: RouteContext): Promise<Response>
|
|
20
|
+
readsTimeSeries(ctx: RouteContext): Promise<Response>
|
|
21
|
+
topContent(ctx: RouteContext): Promise<Response>
|
|
22
|
+
trafficSources(ctx: RouteContext): Promise<Response>
|
|
23
|
+
articleDetail(ctx: RouteContext): Promise<Response>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Parse ?range=<Nd> from the request URL, defaulting to 30d. */
|
|
27
|
+
function parseRange(url: string): DateRange {
|
|
28
|
+
const u = new URL(url)
|
|
29
|
+
const raw = u.searchParams.get('range') ?? '30d'
|
|
30
|
+
const match = /^(\d+)d$/.exec(raw)
|
|
31
|
+
const days = match ? Number(match[1]) : 30
|
|
32
|
+
const to = Math.floor(Date.now() / 1000)
|
|
33
|
+
const from = to - days * 24 * 60 * 60
|
|
34
|
+
return { from, to }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createAnalyticsRoutes(config: AnalyticsRouteConfig): AnalyticsRouteHandlers {
|
|
38
|
+
const providers: CmsProviders = { ...nullProviders, ...(config.providers ?? {}) }
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
async kpis(ctx: RouteContext): Promise<Response> {
|
|
42
|
+
const denied = await config.authz.requirePublisher(ctx)
|
|
43
|
+
if (denied) return denied
|
|
44
|
+
const range = parseRange(ctx.request.url)
|
|
45
|
+
return json(await providers.analytics.getKpis(range))
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async readsTimeSeries(ctx: RouteContext): Promise<Response> {
|
|
49
|
+
const denied = await config.authz.requirePublisher(ctx)
|
|
50
|
+
if (denied) return denied
|
|
51
|
+
const range = parseRange(ctx.request.url)
|
|
52
|
+
return json(await providers.analytics.getReadsTimeSeries(range))
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async topContent(ctx: RouteContext): Promise<Response> {
|
|
56
|
+
const denied = await config.authz.requirePublisher(ctx)
|
|
57
|
+
if (denied) return denied
|
|
58
|
+
const range = parseRange(ctx.request.url)
|
|
59
|
+
return json(await providers.analytics.getTopContent(range))
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async trafficSources(ctx: RouteContext): Promise<Response> {
|
|
63
|
+
const denied = await config.authz.requirePublisher(ctx)
|
|
64
|
+
if (denied) return denied
|
|
65
|
+
const range = parseRange(ctx.request.url)
|
|
66
|
+
return json(await providers.analytics.getTrafficSources(range))
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async articleDetail(ctx: RouteContext): Promise<Response> {
|
|
70
|
+
const denied = await config.authz.requirePublisher(ctx)
|
|
71
|
+
if (denied) return denied
|
|
72
|
+
const url = new URL(ctx.request.url)
|
|
73
|
+
const range = parseRange(ctx.request.url)
|
|
74
|
+
const slug = url.searchParams.get('slug') ?? ''
|
|
75
|
+
return json(await providers.analytics.getArticleDetail(slug, range))
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// api-keys.ts — API key route factories (P7 Task 7).
|
|
2
|
+
//
|
|
3
|
+
// Routes:
|
|
4
|
+
// POST /settings/api-keys create (returns raw key ONCE)
|
|
5
|
+
// GET /settings/api-keys list (masked, no hash/raw)
|
|
6
|
+
// DELETE /settings/api-keys/:id revoke
|
|
7
|
+
//
|
|
8
|
+
// All routes gated on requireRole('manage_api_keys') — owner + senior_editor only.
|
|
9
|
+
// The create route returns the rawKey ONCE in the response body with an explicit
|
|
10
|
+
// one-time-reveal envelope; it is never retrievable again.
|
|
11
|
+
//
|
|
12
|
+
// Re-exports verifyCmsApiKey for the content-API gateway (consumed by Fronts at cutover).
|
|
13
|
+
|
|
14
|
+
import { createApiKey, listApiKeys, revokeApiKey } from '../engine/api-keys.js'
|
|
15
|
+
import type { Authz, Capability, RouteContext } from './context.js'
|
|
16
|
+
import { json } from './context.js'
|
|
17
|
+
|
|
18
|
+
export { verifyCmsApiKey } from '../engine/api-keys.js'
|
|
19
|
+
|
|
20
|
+
export interface ApiKeyRouteConfig {
|
|
21
|
+
authz: Authz
|
|
22
|
+
workspaceId: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ApiKeyRouteHandlers {
|
|
26
|
+
create(ctx: RouteContext): Promise<Response>
|
|
27
|
+
list(ctx: RouteContext): Promise<Response>
|
|
28
|
+
revoke(ctx: RouteContext): Promise<Response>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createApiKeyRoutes(config: ApiKeyRouteConfig): ApiKeyRouteHandlers {
|
|
32
|
+
function requireWorkspace(): Response | null {
|
|
33
|
+
if (!config.workspaceId) {
|
|
34
|
+
return json(
|
|
35
|
+
{ error: 'Configuration error: workspaceId is required for API key routes.' },
|
|
36
|
+
500,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function gate(ctx: RouteContext): Promise<Response | null> {
|
|
43
|
+
if (config.authz.requireRole) {
|
|
44
|
+
return config.authz.requireRole('manage_api_keys' satisfies Capability, ctx)
|
|
45
|
+
}
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
// -----------------------------------------------------------------------
|
|
51
|
+
// POST /settings/api-keys
|
|
52
|
+
// Creates an API key. Returns the rawKey ONCE — it is never retrievable again.
|
|
53
|
+
// -----------------------------------------------------------------------
|
|
54
|
+
async create(ctx: RouteContext): Promise<Response> {
|
|
55
|
+
const wsErr = requireWorkspace()
|
|
56
|
+
if (wsErr) return wsErr
|
|
57
|
+
const denied = await gate(ctx)
|
|
58
|
+
if (denied) return denied
|
|
59
|
+
|
|
60
|
+
let body: unknown
|
|
61
|
+
try {
|
|
62
|
+
body = await ctx.request.json()
|
|
63
|
+
} catch {
|
|
64
|
+
return json({ error: 'Invalid JSON' }, 400)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const b = body as Record<string, unknown>
|
|
68
|
+
const name = b?.name
|
|
69
|
+
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
70
|
+
return json({ error: 'name is required' }, 400)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rawScopes: unknown = b?.scopes ?? []
|
|
74
|
+
if (!Array.isArray(rawScopes) || rawScopes.some((s) => typeof s !== 'string')) {
|
|
75
|
+
return json({ error: 'scopes must be an array of strings' }, 400)
|
|
76
|
+
}
|
|
77
|
+
const scopes = rawScopes as string[]
|
|
78
|
+
|
|
79
|
+
const result = await createApiKey(ctx.db, {
|
|
80
|
+
workspaceId: config.workspaceId,
|
|
81
|
+
name: name.trim(),
|
|
82
|
+
scopes,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Return the rawKey ONCE with an explicit one-time-reveal envelope.
|
|
86
|
+
// The client MUST copy the key — it cannot be retrieved again.
|
|
87
|
+
return json(
|
|
88
|
+
{
|
|
89
|
+
id: result.id,
|
|
90
|
+
name: name.trim(),
|
|
91
|
+
rawKey: result.rawKey,
|
|
92
|
+
masked: result.masked,
|
|
93
|
+
oneTimeReveal: true,
|
|
94
|
+
notice: 'Copy this key now — it will not be shown again.',
|
|
95
|
+
},
|
|
96
|
+
201,
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// -----------------------------------------------------------------------
|
|
101
|
+
// GET /settings/api-keys
|
|
102
|
+
// Returns masked API key list — no hash, no raw key.
|
|
103
|
+
// -----------------------------------------------------------------------
|
|
104
|
+
async list(ctx: RouteContext): Promise<Response> {
|
|
105
|
+
const wsErr = requireWorkspace()
|
|
106
|
+
if (wsErr) return wsErr
|
|
107
|
+
const denied = await gate(ctx)
|
|
108
|
+
if (denied) return denied
|
|
109
|
+
|
|
110
|
+
const keys = await listApiKeys(ctx.db, config.workspaceId)
|
|
111
|
+
return json({ apiKeys: keys })
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// -----------------------------------------------------------------------
|
|
115
|
+
// DELETE /settings/api-keys/:id
|
|
116
|
+
// Revokes (deletes) an API key.
|
|
117
|
+
// -----------------------------------------------------------------------
|
|
118
|
+
async revoke(ctx: RouteContext): Promise<Response> {
|
|
119
|
+
const wsErr = requireWorkspace()
|
|
120
|
+
if (wsErr) return wsErr
|
|
121
|
+
const denied = await gate(ctx)
|
|
122
|
+
if (denied) return denied
|
|
123
|
+
|
|
124
|
+
const id = ctx.params.id
|
|
125
|
+
if (!id) return json({ error: 'Missing id' }, 400)
|
|
126
|
+
|
|
127
|
+
// Scope the revoke to the authenticated caller's workspace (resolved
|
|
128
|
+
// from config, never the client) — closes a cross-workspace IDOR.
|
|
129
|
+
await revokeApiKey(ctx.db, id, config.workspaceId)
|
|
130
|
+
return json({ ok: true, id })
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
}
|