@anthusai/papyrus 2.0.0-next.7 → 2.0.0-next.71

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.
Files changed (165) hide show
  1. package/amplify/data/resource.js +23 -3
  2. package/amplify/data/resource.ts +25 -3
  3. package/amplify/data/schema.js +2028 -1979
  4. package/amplify/data/schema.ts +72 -14
  5. package/amplify/functions/assignment-action/data-access.js +90 -0
  6. package/amplify/functions/assignment-action/data-access.ts +89 -0
  7. package/amplify/functions/assignment-action/handler.ts +1 -1
  8. package/amplify/functions/content-actions/handler.py +61 -2
  9. package/amplify/functions/content-actions/requirements.txt +1 -0
  10. package/amplify/functions/content-actions/resource.js +1 -1
  11. package/amplify/functions/content-actions/resource.ts +1 -1
  12. package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/handler.py +3 -3
  13. package/amplify/functions/editorial-checks/requirements.txt +1 -0
  14. package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.js +7 -7
  15. package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.ts +7 -7
  16. package/amplify/functions/editorial-checks/schema.js +121 -0
  17. package/amplify/{plugins → functions}/editorial-checks/schema.ts +33 -6
  18. package/amplify/functions/editorial-checks/wire.js +53 -0
  19. package/amplify/functions/editorial-checks/wire.ts +61 -0
  20. package/amplify/functions/monitoring-view/handler.py +11 -0
  21. package/amplify/functions/monitoring-view/resource.js +24 -0
  22. package/amplify/functions/monitoring-view/resource.ts +34 -0
  23. package/amplify/functions/monitoring-view/schema.js +16 -0
  24. package/amplify/functions/monitoring-view/schema.ts +18 -0
  25. package/amplify/functions/monitoring-view/wire.js +37 -0
  26. package/amplify/functions/monitoring-view/wire.ts +47 -0
  27. package/amplify/functions/public-forms/core.js +247 -0
  28. package/amplify/functions/public-forms/core.ts +310 -0
  29. package/amplify/functions/public-forms/handler.ts +41 -0
  30. package/amplify/functions/public-forms/origins.js +35 -0
  31. package/amplify/functions/public-forms/origins.ts +43 -0
  32. package/amplify/functions/public-forms/resource.js +9 -0
  33. package/amplify/functions/public-forms/resource.ts +10 -0
  34. package/amplify/functions/public-forms/wire.js +25 -0
  35. package/amplify/functions/public-forms/wire.ts +32 -0
  36. package/amplify/functions/shared/editorial-context.ts +63 -4
  37. package/amplify/functions/shared/python-bundle.js +20 -14
  38. package/amplify/functions/shared/python-bundle.ts +25 -18
  39. package/amplify/plugins/newsletter/config.js +245 -0
  40. package/amplify/plugins/newsletter/config.ts +305 -0
  41. package/amplify/plugins/newsletter/function/handler.py +6 -0
  42. package/amplify/plugins/newsletter/function/requirements.txt +5 -0
  43. package/amplify/plugins/newsletter/ops/handler.py +12 -0
  44. package/amplify/plugins/newsletter/ops/resource.js +27 -0
  45. package/amplify/plugins/newsletter/ops/resource.ts +37 -0
  46. package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
  47. package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
  48. package/amplify/plugins/newsletter/schema.js +56 -0
  49. package/amplify/plugins/newsletter/schema.ts +61 -0
  50. package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
  51. package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
  52. package/amplify/plugins/newsletter/sending.js +140 -0
  53. package/amplify/plugins/newsletter/sending.ts +186 -0
  54. package/amplify/plugins/reader-steering/config.js +180 -0
  55. package/amplify/plugins/reader-steering/config.ts +280 -0
  56. package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
  57. package/amplify/plugins/reader-steering/function/handler.py +1554 -0
  58. package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
  59. package/amplify/plugins/reader-steering/function/moderation.py +316 -0
  60. package/amplify/plugins/reader-steering/function/proposals.py +255 -0
  61. package/amplify/plugins/reader-steering/function/resource.js +27 -0
  62. package/amplify/plugins/reader-steering/function/resource.ts +35 -0
  63. package/amplify/plugins/reader-steering/papyrus.plugin.js +184 -0
  64. package/amplify/plugins/reader-steering/papyrus.plugin.ts +210 -0
  65. package/amplify/plugins/reader-steering/schema.js +204 -0
  66. package/amplify/plugins/reader-steering/schema.ts +206 -0
  67. package/amplify/plugins/registry.js +4 -2
  68. package/amplify/plugins/registry.ts +4 -2
  69. package/amplify/plugins/types.ts +5 -0
  70. package/amplify/plugins/wire.js +1 -0
  71. package/amplify/plugins/wire.ts +1 -0
  72. package/amplify/publication/projection-lease.js +30 -0
  73. package/amplify/publication/projection-lease.ts +39 -0
  74. package/amplify/publication/redirect-and-index.js +105 -0
  75. package/amplify/publication/redirect-rules.js +73 -0
  76. package/amplify/publication/redirect-rules.ts +93 -0
  77. package/amplify/publication/s3-projection.js +7 -1
  78. package/amplify/publication/s3-projection.ts +9 -1
  79. package/amplify/site-backend-config.ts +17 -0
  80. package/amplify/site-backend.js +38 -1
  81. package/amplify/site-backend.ts +46 -1
  82. package/app/api/auth/prospectus/sign-in/route.ts +4 -0
  83. package/app/api/newsletter/confirm/route.ts +13 -0
  84. package/app/api/newsletter/preferences/route.ts +13 -0
  85. package/app/api/newsletter/state/route.ts +9 -0
  86. package/app/api/newsletter/subscribe/route.ts +9 -0
  87. package/app/api/newsletter/unsubscribe/route.ts +13 -0
  88. package/app/api/newsroom/curation/context/route.ts +9 -0
  89. package/app/api/newsroom/curation/priming/route.ts +9 -0
  90. package/app/api/newsroom/curation/publication/route.ts +9 -0
  91. package/app/api/reader/comment/delete/route.ts +9 -0
  92. package/app/api/reader/comment/edit/route.ts +9 -0
  93. package/app/api/reader/comment/route.ts +9 -0
  94. package/app/api/reader/flag/route.ts +9 -0
  95. package/app/api/reader/handle/route.ts +9 -0
  96. package/app/api/reader/me/route.ts +9 -0
  97. package/app/api/reader/rating/route.ts +9 -0
  98. package/app/api/reader/report/route.ts +9 -0
  99. package/app/api/reader/retired/route.ts +9 -0
  100. package/app/api/reader/submit/route.ts +9 -0
  101. package/app/api/reader/vote/route.ts +9 -0
  102. package/bin/papyrus-infra.mjs +20 -1
  103. package/components/editorial-checks-admin-panel.tsx +36 -11
  104. package/components/moderation-panel.tsx +248 -0
  105. package/components/monitoring-overview-view.tsx +241 -0
  106. package/components/monitoring-panel.tsx +40 -0
  107. package/components/newsletter-panel.tsx +474 -0
  108. package/components/newsletter-sections.tsx +163 -0
  109. package/components/newsroom-article-editor.tsx +2 -4
  110. package/components/newsroom-curation-bootstrap.tsx +45 -0
  111. package/components/newsroom-organizer.css +31 -0
  112. package/components/newsroom-references-view.tsx +14 -14
  113. package/components/publication-cyclotron-panel.tsx +15 -10
  114. package/components/reference-add-entry.tsx +75 -0
  115. package/components/reference-curation-controls.tsx +31 -22
  116. package/components/reference-cyclotron-priming.tsx +58 -19
  117. package/components/reference-cyclotron-suggestion.tsx +1 -1
  118. package/components/reference-priming.tsx +14 -10
  119. package/components/reference-section-suggestion.tsx +1 -1
  120. package/components/topic-steering-workspace.tsx +64 -20
  121. package/components/use-editorial-checks.ts +12 -10
  122. package/infra/amplify-app-shell.js +90 -16
  123. package/infra/amplify-app-shell.ts +102 -18
  124. package/infra/index.js +1 -0
  125. package/infra/secure-string-parameter-handler.js +41 -0
  126. package/infra/secure-string-parameter-handler.ts +41 -0
  127. package/infra/secure-string-parameter.js +79 -0
  128. package/infra/secure-string-parameter.ts +91 -0
  129. package/infra/site-config.js +86 -6
  130. package/infra/site-config.ts +129 -6
  131. package/lib/category-repository.ts +11 -9
  132. package/lib/curation-routes.ts +345 -0
  133. package/lib/editorial-checks-backend.ts +1 -1
  134. package/lib/editorial-checks-client.ts +19 -1
  135. package/lib/editorial-checks-demo.ts +2 -2
  136. package/lib/editorial-checks-settings.ts +3 -0
  137. package/lib/editorial-checks.ts +8 -11
  138. package/lib/editorial-preview.ts +5 -5
  139. package/lib/monitoring-client.ts +29 -0
  140. package/lib/monitoring-overview-fixture.ts +51 -0
  141. package/lib/monitoring-overview.ts +145 -0
  142. package/lib/newsletter-api.ts +418 -0
  143. package/lib/newsletter-client.ts +46 -0
  144. package/lib/newsletter-fixture.ts +184 -0
  145. package/lib/newsletter-model.ts +202 -0
  146. package/lib/prospectus-session.ts +15 -14
  147. package/lib/prospectus-sign-in-routes.ts +84 -19
  148. package/lib/prospectus-site-identity.ts +24 -1
  149. package/lib/reader-api.ts +145 -0
  150. package/lib/reader-moderation.ts +220 -0
  151. package/lib/reader-retirement-route.ts +89 -0
  152. package/lib/reader-subject.md +29 -0
  153. package/lib/reader-subject.ts +53 -0
  154. package/lib/relevance-status.ts +2 -2
  155. package/lib/semantic-graph.ts +2 -0
  156. package/lib/site-brand-tenant.ts +11 -6
  157. package/lib/site-brand.ts +6 -3
  158. package/lib/site-origin.ts +67 -0
  159. package/middleware.ts +14 -3
  160. package/package.json +42 -3
  161. package/routes.manifest.json +215 -2
  162. package/amplify/plugins/editorial-checks/function/requirements.txt +0 -1
  163. package/amplify/plugins/editorial-checks/papyrus.plugin.js +0 -69
  164. package/amplify/plugins/editorial-checks/papyrus.plugin.ts +0 -85
  165. package/amplify/plugins/editorial-checks/schema.js +0 -94
@@ -10,11 +10,13 @@ import * as route53 from "aws-cdk-lib/aws-route53";
10
10
  import * as s3 from "aws-cdk-lib/aws-s3";
11
11
  import * as sqs from "aws-cdk-lib/aws-sqs";
12
12
  import type { Construct } from "constructs";
13
+ import { readFileSync } from "node:fs";
13
14
  import { dirname, resolve } from "node:path";
14
15
  import { fileURLToPath } from "node:url";
15
16
  import { assertValidPublicationConfig, type PapyrusPublicationConfig } from "../site-backend-config";
16
17
  import { addRenditionsFunction, type RenditionsFunction } from "./renditions-function";
17
18
  import { RENDITIONS_FUNCTION_NAME_ENV, type RenditionsPlan } from "./renditions-settings";
19
+ import { compileRedirectFunctionSource, readEdgeConfig } from "./redirect-rules";
18
20
  import { addInvalidationPathAlarm } from "./invalidation-alarm";
19
21
  import { distributionIdFromArn, s3ProjectionEnvironment, s3ProjectionPolicyStatements } from "./s3-projection-settings";
20
22
 
@@ -110,9 +112,15 @@ export function addS3ProjectionPublication(
110
112
  const bucket = createSiteBucket(scope, props.retainBucketOnDelete !== false);
111
113
 
112
114
  const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
115
+ const edgeConfig = readEdgeConfig(props.staticDir);
113
116
  const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
114
117
  runtime: cloudfront.FunctionRuntime.JS_2_0,
115
- code: cloudfront.FunctionCode.fromFile({ filePath: redirectFunctionSourcePath }),
118
+ code: cloudfront.FunctionCode.fromInline(
119
+ compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), edgeConfig.rules, {
120
+ negotiateMarkdown: edgeConfig.negotiateMarkdown,
121
+ directoryTwins: edgeConfig.directoryTwins,
122
+ }),
123
+ ),
116
124
  keyValueStore,
117
125
  });
118
126
  const deadLetterQueue = new sqs.Queue(scope, "ProjectionDeadLetterQueue", {
@@ -145,6 +145,13 @@ export type PapyrusSiteBackendConfig = {
145
145
  * DependsOn ordering; no resource changes. Default 12; 0 turns it off.
146
146
  */
147
147
  resolverWaveSize?: number;
148
+ /**
149
+ * Optional model groups to leave out of the data API (see `OPTIONAL_MODEL_GROUPS`; today `editions`).
150
+ * CloudFormation allows 2,500 resources per stack operation, nested stacks included, and a fresh create of the full
151
+ * model set exceeds it. Deploy a new site once with a group excluded, then remove the exclusion and deploy again:
152
+ * the second deploy is an ordinary update that adds the models.
153
+ */
154
+ excludeModelGroups?: string[];
148
155
  /** Static reader app (Markus-static sites) that a publish rebuilds through IAM `amplify:StartJob`. */
149
156
  reader?: { amplifyAppId: string; branchName?: string };
150
157
  /** Lets the newsroom start the `staging` branch build of this CMS app. */
@@ -178,6 +185,16 @@ export type PapyrusSiteBackendConfig = {
178
185
  * Unset: no plugin, and the synthesized backend is exactly what it was before plugins existed.
179
186
  */
180
187
  plugins?: PluginRequest[];
188
+ /**
189
+ * The public form receiving endpoint every site gets (docs/public-forms.md). It accepts posts only from the site's
190
+ * own public hosts (distribution aliases, reader domain, revalidate URL, sign-in redirect URLs) plus these.
191
+ */
192
+ publicForms?: {
193
+ /** Extra https origins (or http://localhost) allowed to post, for example `https://www.example.org`. */
194
+ allowedOrigins?: string[];
195
+ /** Extra honeypot field names besides `website`. */
196
+ honeypotFields?: string[];
197
+ };
181
198
  features?: {
182
199
  consoleResponder?: boolean;
183
200
  inboundEmail?: boolean;
@@ -11,6 +11,7 @@ import { resolveSitePublication } from "./publication/default-publication.js";
11
11
  import { REPROJECT_ON_DEPLOY_TIMEOUT_SECONDS, addReprojectOnDeploy, reprojectDeployToken } from "./publication/reproject-on-deploy.js";
12
12
  import { addProjectionRetries, addS3ProjectionPublication } from "./publication/s3-projection.js";
13
13
  import { planRenditions } from "./publication/renditions-settings.js";
14
+ import { addProjectionLeaseAccess } from "./publication/projection-lease.js";
14
15
  import { contentActionsPublicationWiring, planS3ProjectionBackend } from "./publication/s3-projection-settings.js";
15
16
  import { rebuildTriggerSettings } from "./rebuild-trigger-settings.js";
16
17
  import { assertValidPublicationConfig } from "./site-backend-config.js";
@@ -20,9 +21,14 @@ import { addInvalidationPathAlarm } from "./publication/invalidation-alarm.js";
20
21
  import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource.js";
21
22
  import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client.js";
22
23
  import { defineSiteData } from "./data/resource.js";
24
+ import { editorialChecks } from "./functions/editorial-checks/resource.js";
25
+ import { monitoringView } from "./functions/monitoring-view/resource.js";
26
+ import { wireMonitoringView } from "./functions/monitoring-view/wire.js";
27
+ import { wireEditorialChecks } from "./functions/editorial-checks/wire.js";
23
28
  import { resolvePlugins } from "./plugins/registry.js";
24
29
  import { wirePlugins } from "./plugins/wire.js";
25
30
  import { assignmentAction } from "./functions/assignment-action/resource.js";
31
+ import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access.js";
26
32
  import { categoryAction } from "./functions/category-action/resource.js";
27
33
  import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
28
34
  import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource.js";
@@ -30,6 +36,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource.js";
30
36
  import { manageUserRole } from "./functions/manage-user-role/resource.js";
31
37
  import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource.js";
32
38
  import { newsroomSummary } from "./functions/newsroom-summary/resource.js";
39
+ import { derivePublicFormOrigins } from "./functions/public-forms/origins.js";
40
+ import { publicForms } from "./functions/public-forms/resource.js";
41
+ import { wirePublicForms } from "./functions/public-forms/wire.js";
33
42
  import { procedureAction } from "./functions/procedure-action/resource.js";
34
43
  import { readerSettings } from "./functions/reader-settings/resource.js";
35
44
  import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource.js";
@@ -88,12 +97,15 @@ export function defineSiteBackend(papyrusSite) {
88
97
  auth: defineSiteAuth(authConfig),
89
98
  categoryAction,
90
99
  contentActions,
91
- data: defineSiteData(pluginDefinitions),
100
+ editorialChecks,
101
+ data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
92
102
  knowledgeQuery,
93
103
  manageUserRole,
104
+ monitoringView,
94
105
  modelAttachmentUpload,
95
106
  newsroomSummary,
96
107
  procedureAction,
108
+ publicForms,
97
109
  readerSettings,
98
110
  emailSubmissionProcessor,
99
111
  sesInboundReceive,
@@ -304,10 +316,19 @@ export function defineSiteBackend(papyrusSite) {
304
316
  }
305
317
  grantCorporaRead(backend.knowledgeQuery.resources.lambda);
306
318
  grantNewsroomReadWrite(backend.assignmentAction.resources.lambda);
319
+ scopeAssignmentDataAccess(backend.assignmentAction.resources.lambda, backend.data.resources.graphqlApi.arn);
307
320
  grantNewsroomReadWrite(backend.categoryAction.resources.lambda);
308
321
  grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
309
322
  const contentActionsLambda = backend.contentActions.resources.lambda;
310
323
  grantNewsroomReadWriteDelete(contentActionsLambda);
324
+ if ((activeBrand?.corpusKey ?? "").trim() !== "") {
325
+ // A deploy creates the publication's canonical corpus from the brand when the backend has none.
326
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
327
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
328
+ }
329
+ // AppSync validates the token; pending intake additionally binds its actor
330
+ // and publication to this deployment's pool and existing API endpoint.
331
+ contentActionsLambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
311
332
  contentActionsLambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
312
333
  contentActionsLambda.addToRolePolicy(new PolicyStatement({
313
334
  actions: ["appsync:GraphQL"],
@@ -340,6 +361,7 @@ export function defineSiteBackend(papyrusSite) {
340
361
  }
341
362
  if (s3ProjectionPublication !== undefined) {
342
363
  addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
364
+ addProjectionLeaseAccess(contentActionsLambda);
343
365
  }
344
366
  const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
345
367
  for (const [name, value] of Object.entries(publicationWiring.environment)) {
@@ -548,8 +570,21 @@ export function defineSiteBackend(papyrusSite) {
548
570
  : []),
549
571
  ],
550
572
  }));
573
+ const publicFormsEndpoint = wirePublicForms(backend, {
574
+ allowedOrigins: derivePublicFormOrigins({
575
+ explicit: site.publicForms?.allowedOrigins,
576
+ hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
577
+ urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
578
+ }),
579
+ honeypotFields: site.publicForms?.honeypotFields,
580
+ });
551
581
  backend.addOutput({
552
582
  custom: {
583
+ publicForms: { endpoint: publicFormsEndpoint },
584
+ // Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
585
+ publication: s3ProjectionPublication !== undefined
586
+ ? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
587
+ : null,
553
588
  prospectusSignIn: prospectusGoogleOnlyClient
554
589
  ? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
555
590
  : null,
@@ -584,6 +619,8 @@ export function defineSiteBackend(papyrusSite) {
584
619
  : null,
585
620
  },
586
621
  });
622
+ wireEditorialChecks(backend);
623
+ wireMonitoringView(backend);
587
624
  wirePlugins(backend, plugins, contentActionsLambda);
588
625
  return backend;
589
626
  }
@@ -14,6 +14,7 @@ import { resolveSitePublication } from "./publication/default-publication";
14
14
  import { REPROJECT_ON_DEPLOY_TIMEOUT_SECONDS, addReprojectOnDeploy, reprojectDeployToken } from "./publication/reproject-on-deploy";
15
15
  import { addProjectionRetries, addS3ProjectionPublication, type S3ProjectionPublication } from "./publication/s3-projection";
16
16
  import { planRenditions } from "./publication/renditions-settings";
17
+ import { addProjectionLeaseAccess } from "./publication/projection-lease";
17
18
  import { contentActionsPublicationWiring, planS3ProjectionBackend } from "./publication/s3-projection-settings";
18
19
  import { rebuildTriggerSettings } from "./rebuild-trigger-settings";
19
20
  import { assertValidPublicationConfig } from "./site-backend-config";
@@ -27,9 +28,14 @@ import { addInvalidationPathAlarm } from "./publication/invalidation-alarm";
27
28
  import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource";
28
29
  import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client";
29
30
  import { defineSiteData } from "./data/resource";
31
+ import { editorialChecks } from "./functions/editorial-checks/resource";
32
+ import { monitoringView } from "./functions/monitoring-view/resource";
33
+ import { wireMonitoringView } from "./functions/monitoring-view/wire";
34
+ import { wireEditorialChecks } from "./functions/editorial-checks/wire";
30
35
  import { resolvePlugins } from "./plugins/registry";
31
36
  import { wirePlugins } from "./plugins/wire";
32
37
  import { assignmentAction } from "./functions/assignment-action/resource";
38
+ import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access";
33
39
  import { categoryAction } from "./functions/category-action/resource";
34
40
  import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
35
41
  import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
@@ -37,6 +43,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource";
37
43
  import { manageUserRole } from "./functions/manage-user-role/resource";
38
44
  import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
39
45
  import { newsroomSummary } from "./functions/newsroom-summary/resource";
46
+ import { derivePublicFormOrigins } from "./functions/public-forms/origins";
47
+ import { publicForms } from "./functions/public-forms/resource";
48
+ import { wirePublicForms } from "./functions/public-forms/wire";
40
49
  import { procedureAction } from "./functions/procedure-action/resource";
41
50
  import { readerSettings } from "./functions/reader-settings/resource";
42
51
  import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource";
@@ -109,12 +118,15 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
109
118
  auth: defineSiteAuth(authConfig),
110
119
  categoryAction,
111
120
  contentActions,
112
- data: defineSiteData(pluginDefinitions),
121
+ editorialChecks,
122
+ data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
113
123
  knowledgeQuery,
114
124
  manageUserRole,
125
+ monitoringView,
115
126
  modelAttachmentUpload,
116
127
  newsroomSummary,
117
128
  procedureAction,
129
+ publicForms,
118
130
  readerSettings,
119
131
  emailSubmissionProcessor,
120
132
  sesInboundReceive,
@@ -387,10 +399,25 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
387
399
  }
388
400
  grantCorporaRead(backend.knowledgeQuery.resources.lambda as LambdaFunction);
389
401
  grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
402
+ scopeAssignmentDataAccess(
403
+ backend.assignmentAction.resources.lambda as LambdaFunction,
404
+ backend.data.resources.graphqlApi.arn,
405
+ );
390
406
  grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
391
407
  grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
392
408
  const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
393
409
  grantNewsroomReadWriteDelete(contentActionsLambda);
410
+ if ((activeBrand?.corpusKey ?? "").trim() !== "") {
411
+ // A deploy creates the publication's canonical corpus from the brand when the backend has none.
412
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
413
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
414
+ }
415
+ // AppSync validates the token; pending intake additionally binds its actor
416
+ // and publication to this deployment's pool and existing API endpoint.
417
+ contentActionsLambda.addEnvironment(
418
+ "PAPYRUS_USER_POOL_ISSUER",
419
+ `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`,
420
+ );
394
421
  contentActionsLambda.addEnvironment(
395
422
  "PAPYRUS_GRAPHQL_ENDPOINT",
396
423
  backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
@@ -430,6 +457,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
430
457
  }
431
458
  if (s3ProjectionPublication !== undefined) {
432
459
  addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
460
+ addProjectionLeaseAccess(contentActionsLambda);
433
461
  }
434
462
  const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
435
463
  for (const [name, value] of Object.entries(publicationWiring.environment)) {
@@ -685,8 +713,23 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
685
713
  }),
686
714
  );
687
715
 
716
+ const publicFormsEndpoint = wirePublicForms(backend, {
717
+ allowedOrigins: derivePublicFormOrigins({
718
+ explicit: site.publicForms?.allowedOrigins,
719
+ hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
720
+ urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
721
+ }),
722
+ honeypotFields: site.publicForms?.honeypotFields,
723
+ });
724
+
688
725
  backend.addOutput({
689
726
  custom: {
727
+ publicForms: { endpoint: publicFormsEndpoint },
728
+ // Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
729
+ publication:
730
+ s3ProjectionPublication !== undefined
731
+ ? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
732
+ : null,
690
733
  prospectusSignIn: prospectusGoogleOnlyClient
691
734
  ? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
692
735
  : null,
@@ -721,6 +764,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
721
764
  : null,
722
765
  },
723
766
  });
767
+ wireEditorialChecks(backend);
768
+ wireMonitoringView(backend);
724
769
  wirePlugins(backend, plugins, contentActionsLambda);
725
770
 
726
771
  return backend;
@@ -7,3 +7,7 @@ export const runtime = "nodejs";
7
7
  export function GET(request: NextRequest) {
8
8
  return handleProspectusSignInStart(request);
9
9
  }
10
+
11
+ export function POST(request: NextRequest) {
12
+ return handleProspectusSignInStart(request);
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleNewsletterConfirm } from "../../../../lib/newsletter-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleNewsletterConfirm(request);
9
+ }
10
+
11
+ export function POST(request: NextRequest) {
12
+ return handleNewsletterConfirm(request);
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleNewsletterPreferences } from "../../../../lib/newsletter-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleNewsletterPreferences(request);
9
+ }
10
+
11
+ export function POST(request: NextRequest) {
12
+ return handleNewsletterPreferences(request);
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleNewsletterState } from "../../../../lib/newsletter-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleNewsletterState(request);
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleNewsletterSubscribe } from "../../../../lib/newsletter-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleNewsletterSubscribe(request);
9
+ }
@@ -0,0 +1,13 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleNewsletterUnsubscribe } from "../../../../lib/newsletter-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleNewsletterUnsubscribe(request);
9
+ }
10
+
11
+ export function POST(request: NextRequest) {
12
+ return handleNewsletterUnsubscribe(request);
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleCurationRoute } from "../../../../../lib/curation-routes";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleCurationRoute(request, "context");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleCurationRoute } from "../../../../../lib/curation-routes";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleCurationRoute(request, "priming");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleCurationRoute } from "../../../../../lib/curation-routes";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleCurationRoute(request, "publication");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "comment-delete");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "comment-edit");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "comment");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "flag");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "handle");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleReaderRequest(request, "me");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "rating");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "report");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRetiredSubjects } from "../../../../lib/reader-retirement-route";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleReaderRetiredSubjects(request);
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "submit");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleReaderRequest } from "../../../../lib/reader-api";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleReaderRequest(request, "vote");
9
+ }
@@ -19,6 +19,7 @@ const USAGE = [
19
19
  " papyrus-infra synth --account-stack github-oidc [--out cdk.out] [--account A] [--region R]",
20
20
  " papyrus-infra print-buildspec --site infra/site.json --app cms-production|cms-staging|reader",
21
21
  " papyrus-infra deploy-edge --stack NAME --template FILE [--adoption-template FILE --adopt-resources FILE] [--parameter KEY=VALUE]... [--delete-function NAME] [--only-branch main] [--only-app-id ID] [--region R]",
22
+ " papyrus-infra compile-edge-function --static-dir web [--out FILE]",
22
23
  " papyrus-infra deploy --site infra/site.json [--only-branch main] [--only-app-id ID] [--out cdk.out] [--account A] [--region R]",
23
24
  ].join("\n");
24
25
 
@@ -36,11 +37,29 @@ const resolveEnvironmentOrExit = () => {
36
37
  }
37
38
  };
38
39
  const command = args[0];
39
- if (command !== "synth" && command !== "print-buildspec" && command !== "deploy" && command !== "deploy-edge") {
40
+ if (command !== "synth" && command !== "print-buildspec" && command !== "deploy" && command !== "deploy-edge" && command !== "compile-edge-function") {
40
41
  console.error(USAGE);
41
42
  process.exit(command ? 1 : 0);
42
43
  }
43
44
 
45
+ if (command === "compile-edge-function") {
46
+ // The viewer-request CloudFront Function with the site's projection.json rules compiled in: for an edge stack that is
47
+ // not deployed by the CMS backend, so that no site writes its own redirect function.
48
+ const staticDir = opt("static-dir");
49
+ if (!staticDir) {
50
+ console.error(USAGE);
51
+ process.exit(1);
52
+ }
53
+ const { compileRedirectFunctionSource, readEdgeConfig } = await import("../amplify/publication/redirect-rules.js");
54
+ const functionSource = fs.readFileSync(new URL("../amplify/publication/redirect-and-index.js", import.meta.url), "utf8");
55
+ const config = readEdgeConfig(staticDir);
56
+ const compiled = compileRedirectFunctionSource(functionSource, config.rules, { negotiateMarkdown: config.negotiateMarkdown, directoryTwins: config.directoryTwins });
57
+ const out = opt("out");
58
+ if (out) fs.writeFileSync(path.resolve(out), compiled);
59
+ else process.stdout.write(compiled);
60
+ process.exit(0);
61
+ }
62
+
44
63
  if (command === "deploy-edge") {
45
64
  const buildAppId = process.env.AWS_APP_ID;
46
65
  const buildBranch = process.env.AWS_BRANCH;