@anthusai/papyrus 2.0.0-next.8 → 2.0.0-next.81

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 (213) hide show
  1. package/amplify/auth/resource.js +16 -1
  2. package/amplify/auth/resource.ts +19 -1
  3. package/amplify/data/function-handler.js +20 -0
  4. package/amplify/data/function-handler.ts +40 -0
  5. package/amplify/data/resource.js +151 -61
  6. package/amplify/data/resource.ts +157 -61
  7. package/amplify/data/schema.js +2096 -1980
  8. package/amplify/data/schema.ts +198 -69
  9. package/amplify/functions/assignment-action/data-access.js +14 -15
  10. package/amplify/functions/assignment-action/data-access.ts +13 -14
  11. package/amplify/functions/assignment-action/handler.ts +1 -1
  12. package/amplify/functions/automation-view/handler.py +17 -0
  13. package/amplify/functions/automation-view/resource.js +24 -0
  14. package/amplify/functions/automation-view/resource.ts +34 -0
  15. package/amplify/functions/automation-view/schema.js +26 -0
  16. package/amplify/functions/automation-view/schema.ts +28 -0
  17. package/amplify/functions/automation-view/wire.js +56 -0
  18. package/amplify/functions/automation-view/wire.ts +76 -0
  19. package/amplify/functions/content-actions/handler.py +61 -2
  20. package/amplify/functions/content-actions/requirements.txt +1 -0
  21. package/amplify/functions/content-actions/resource.js +1 -1
  22. package/amplify/functions/content-actions/resource.ts +1 -1
  23. package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/handler.py +3 -3
  24. package/amplify/functions/editorial-checks/requirements.txt +1 -0
  25. package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.js +7 -7
  26. package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.ts +7 -7
  27. package/amplify/functions/editorial-checks/schema.js +122 -0
  28. package/amplify/{plugins → functions}/editorial-checks/schema.ts +40 -12
  29. package/amplify/functions/editorial-checks/wire.js +53 -0
  30. package/amplify/functions/editorial-checks/wire.ts +61 -0
  31. package/amplify/functions/monitoring-view/handler.py +11 -0
  32. package/amplify/functions/monitoring-view/resource.js +24 -0
  33. package/amplify/functions/monitoring-view/resource.ts +34 -0
  34. package/amplify/functions/monitoring-view/schema.js +17 -0
  35. package/amplify/functions/monitoring-view/schema.ts +19 -0
  36. package/amplify/functions/monitoring-view/wire.js +37 -0
  37. package/amplify/functions/monitoring-view/wire.ts +47 -0
  38. package/amplify/functions/public-forms/core.js +247 -0
  39. package/amplify/functions/public-forms/core.ts +310 -0
  40. package/amplify/functions/public-forms/handler.ts +41 -0
  41. package/amplify/functions/public-forms/origins.js +35 -0
  42. package/amplify/functions/public-forms/origins.ts +43 -0
  43. package/amplify/functions/public-forms/resource.js +9 -0
  44. package/amplify/functions/public-forms/resource.ts +10 -0
  45. package/amplify/functions/public-forms/wire.js +25 -0
  46. package/amplify/functions/public-forms/wire.ts +32 -0
  47. package/amplify/functions/shared/editorial-context.ts +63 -4
  48. package/amplify/functions/shared/python-bundle.js +20 -14
  49. package/amplify/functions/shared/python-bundle.ts +25 -18
  50. package/amplify/plugins/newsletter/config.js +245 -0
  51. package/amplify/plugins/newsletter/config.ts +305 -0
  52. package/amplify/plugins/newsletter/function/handler.py +6 -0
  53. package/amplify/plugins/newsletter/function/requirements.txt +5 -0
  54. package/amplify/plugins/newsletter/ops/handler.py +12 -0
  55. package/amplify/plugins/newsletter/ops/resource.js +27 -0
  56. package/amplify/plugins/newsletter/ops/resource.ts +37 -0
  57. package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
  58. package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
  59. package/amplify/plugins/newsletter/schema.js +57 -0
  60. package/amplify/plugins/newsletter/schema.ts +62 -0
  61. package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
  62. package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
  63. package/amplify/plugins/newsletter/sending.js +140 -0
  64. package/amplify/plugins/newsletter/sending.ts +186 -0
  65. package/amplify/plugins/reader-steering/config.js +182 -0
  66. package/amplify/plugins/reader-steering/config.ts +290 -0
  67. package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
  68. package/amplify/plugins/reader-steering/function/handler.py +1554 -0
  69. package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
  70. package/amplify/plugins/reader-steering/function/moderation.py +316 -0
  71. package/amplify/plugins/reader-steering/function/proposals.py +255 -0
  72. package/amplify/plugins/reader-steering/function/resource.js +27 -0
  73. package/amplify/plugins/reader-steering/function/resource.ts +35 -0
  74. package/amplify/plugins/reader-steering/papyrus.plugin.js +186 -0
  75. package/amplify/plugins/reader-steering/papyrus.plugin.ts +212 -0
  76. package/amplify/plugins/reader-steering/schema.js +205 -0
  77. package/amplify/plugins/reader-steering/schema.ts +207 -0
  78. package/amplify/plugins/registry.js +4 -2
  79. package/amplify/plugins/registry.ts +4 -2
  80. package/amplify/plugins/signup/schema.js +2 -1
  81. package/amplify/plugins/signup/schema.ts +2 -1
  82. package/amplify/plugins/types.ts +5 -0
  83. package/amplify/plugins/wire.js +1 -0
  84. package/amplify/plugins/wire.ts +1 -0
  85. package/amplify/publication/projection-lease.js +30 -0
  86. package/amplify/publication/projection-lease.ts +39 -0
  87. package/amplify/publication/redirect-and-index.js +105 -0
  88. package/amplify/publication/redirect-rules.js +73 -0
  89. package/amplify/publication/redirect-rules.ts +93 -0
  90. package/amplify/publication/s3-projection.js +7 -1
  91. package/amplify/publication/s3-projection.ts +9 -1
  92. package/amplify/site-backend-config.ts +18 -1
  93. package/amplify/site-backend-names.js +4 -2
  94. package/amplify/site-backend-names.ts +4 -2
  95. package/amplify/site-backend.js +45 -3
  96. package/amplify/site-backend.ts +51 -3
  97. package/app/api/auth/prospectus/sign-in/route.ts +4 -0
  98. package/app/api/newsletter/confirm/route.ts +13 -0
  99. package/app/api/newsletter/preferences/route.ts +13 -0
  100. package/app/api/newsletter/state/route.ts +9 -0
  101. package/app/api/newsletter/subscribe/route.ts +9 -0
  102. package/app/api/newsletter/unsubscribe/route.ts +13 -0
  103. package/app/api/newsroom/curation/context/route.ts +9 -0
  104. package/app/api/newsroom/curation/priming/route.ts +9 -0
  105. package/app/api/newsroom/curation/publication/route.ts +9 -0
  106. package/app/api/reader/comment/delete/route.ts +9 -0
  107. package/app/api/reader/comment/edit/route.ts +9 -0
  108. package/app/api/reader/comment/route.ts +9 -0
  109. package/app/api/reader/flag/route.ts +9 -0
  110. package/app/api/reader/handle/route.ts +9 -0
  111. package/app/api/reader/me/route.ts +9 -0
  112. package/app/api/reader/rating/route.ts +9 -0
  113. package/app/api/reader/report/route.ts +9 -0
  114. package/app/api/reader/retired/route.ts +9 -0
  115. package/app/api/reader/submit/route.ts +9 -0
  116. package/app/api/reader/vote/route.ts +9 -0
  117. package/bin/papyrus-infra.mjs +20 -1
  118. package/components/automation-panel.tsx +49 -0
  119. package/components/automation-run-now.tsx +97 -0
  120. package/components/automation-view.tsx +192 -0
  121. package/components/editorial-checks-admin-panel.tsx +36 -11
  122. package/components/moderation-panel.tsx +248 -0
  123. package/components/monitoring-overview-view.tsx +241 -0
  124. package/components/monitoring-panel.tsx +40 -0
  125. package/components/news-desk-client-provider.tsx +18 -18
  126. package/components/news-desk-taxonomy-client.ts +28 -8
  127. package/components/newsletter-panel.tsx +474 -0
  128. package/components/newsletter-sections.tsx +163 -0
  129. package/components/newsroom-access-gate.tsx +15 -1
  130. package/components/newsroom-article-editor.tsx +6 -6
  131. package/components/newsroom-articles-view.tsx +6 -6
  132. package/components/newsroom-board-view.tsx +8 -3
  133. package/components/newsroom-curation-bootstrap.tsx +45 -0
  134. package/components/newsroom-ops-overview.tsx +22 -92
  135. package/components/newsroom-ops-shell.tsx +2 -2
  136. package/components/newsroom-organizer.css +33 -0
  137. package/components/newsroom-overview-skeleton.tsx +25 -0
  138. package/components/newsroom-references-view.tsx +192 -49
  139. package/components/notification-center.tsx +273 -0
  140. package/components/publication-cyclotron-panel.tsx +15 -10
  141. package/components/reader-auth-state.ts +1 -1
  142. package/components/reference-add-entry.tsx +75 -0
  143. package/components/reference-curation-controls.tsx +31 -22
  144. package/components/reference-cyclotron-priming.tsx +58 -19
  145. package/components/reference-cyclotron-suggestion.tsx +10 -6
  146. package/components/reference-priming.tsx +14 -10
  147. package/components/reference-relevance-review.tsx +3 -3
  148. package/components/reference-section-suggestion.tsx +1 -1
  149. package/components/topic-steering-workspace.tsx +223 -144
  150. package/components/use-editorial-checks.ts +12 -10
  151. package/infra/amplify-app-shell.js +94 -16
  152. package/infra/amplify-app-shell.ts +106 -18
  153. package/infra/cloud-email-config.js +32 -0
  154. package/infra/cloud-email-config.ts +39 -0
  155. package/infra/cloud-email.js +57 -0
  156. package/infra/cloud-email.test.js +69 -0
  157. package/infra/cloud-email.test.ts +66 -0
  158. package/infra/cloud-email.ts +60 -0
  159. package/infra/index.js +1 -0
  160. package/infra/secure-string-parameter-handler.js +41 -0
  161. package/infra/secure-string-parameter-handler.ts +41 -0
  162. package/infra/secure-string-parameter.js +79 -0
  163. package/infra/secure-string-parameter.ts +91 -0
  164. package/infra/site-config.js +88 -6
  165. package/infra/site-config.ts +134 -6
  166. package/lib/automation-client.ts +54 -0
  167. package/lib/automation-fixture.ts +93 -0
  168. package/lib/automation-overview.ts +232 -0
  169. package/lib/category-repository.ts +11 -9
  170. package/lib/curation-routes.ts +345 -0
  171. package/lib/cyclotron-suggestions.ts +4 -4
  172. package/lib/editorial-checks-backend.ts +1 -1
  173. package/lib/editorial-checks-client.ts +19 -1
  174. package/lib/editorial-checks-demo.ts +2 -2
  175. package/lib/editorial-checks-settings.ts +3 -0
  176. package/lib/editorial-checks.ts +8 -11
  177. package/lib/editorial-preview.ts +5 -5
  178. package/lib/monitoring-client.ts +29 -0
  179. package/lib/monitoring-overview-fixture.ts +51 -0
  180. package/lib/monitoring-overview.ts +145 -0
  181. package/lib/news-desk-session.ts +12 -21
  182. package/lib/newsletter-api.ts +418 -0
  183. package/lib/newsletter-client.ts +46 -0
  184. package/lib/newsletter-fixture.ts +184 -0
  185. package/lib/newsletter-model.ts +202 -0
  186. package/lib/newsroom-organizer-nav.ts +2 -1
  187. package/lib/prospectus-session.ts +15 -14
  188. package/lib/prospectus-sign-in-routes.ts +84 -19
  189. package/lib/prospectus-site-identity.ts +24 -1
  190. package/lib/reader-api.ts +145 -0
  191. package/lib/reader-moderation.ts +220 -0
  192. package/lib/reader-retirement-route.ts +89 -0
  193. package/lib/reader-subject.md +29 -0
  194. package/lib/reader-subject.ts +53 -0
  195. package/lib/reference-review-details.ts +71 -0
  196. package/lib/reference-review-request.ts +14 -0
  197. package/lib/relevance-decisions.ts +42 -10
  198. package/lib/relevance-status.ts +2 -2
  199. package/lib/review-client.ts +99 -0
  200. package/lib/review-fixture.ts +45 -0
  201. package/lib/review-requests.ts +238 -0
  202. package/lib/semantic-graph.ts +2 -0
  203. package/lib/site-brand-tenant.ts +11 -6
  204. package/lib/site-brand.ts +6 -3
  205. package/lib/site-origin.ts +67 -0
  206. package/lib/use-reference-review-data.ts +44 -0
  207. package/middleware.ts +14 -3
  208. package/package.json +56 -3
  209. package/routes.manifest.json +215 -2
  210. package/amplify/plugins/editorial-checks/function/requirements.txt +0 -1
  211. package/amplify/plugins/editorial-checks/papyrus.plugin.js +0 -71
  212. package/amplify/plugins/editorial-checks/papyrus.plugin.ts +0 -87
  213. package/amplify/plugins/editorial-checks/schema.js +0 -94
@@ -7,11 +7,13 @@ import { SqsDestination } from "aws-cdk-lib/aws-lambda-destinations";
7
7
  import * as route53 from "aws-cdk-lib/aws-route53";
8
8
  import * as s3 from "aws-cdk-lib/aws-s3";
9
9
  import * as sqs from "aws-cdk-lib/aws-sqs";
10
+ import { readFileSync } from "node:fs";
10
11
  import { dirname, resolve } from "node:path";
11
12
  import { fileURLToPath } from "node:url";
12
13
  import { assertValidPublicationConfig } from "../site-backend-config.js";
13
14
  import { addRenditionsFunction } from "./renditions-function.js";
14
15
  import { RENDITIONS_FUNCTION_NAME_ENV } from "./renditions-settings.js";
16
+ import { compileRedirectFunctionSource, readEdgeConfig } from "./redirect-rules.js";
15
17
  import { addInvalidationPathAlarm } from "./invalidation-alarm.js";
16
18
  import { distributionIdFromArn, s3ProjectionEnvironment, s3ProjectionPolicyStatements } from "./s3-projection-settings.js";
17
19
  const redirectFunctionSourcePath = resolve(dirname(fileURLToPath(import.meta.url)), "redirect-and-index.js");
@@ -68,9 +70,13 @@ export function addS3ProjectionPublication(scope, props, media) {
68
70
  assertValidPublicationConfig({ kind: "s3-projection", ...props });
69
71
  const bucket = createSiteBucket(scope, props.retainBucketOnDelete !== false);
70
72
  const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
73
+ const edgeConfig = readEdgeConfig(props.staticDir);
71
74
  const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
72
75
  runtime: cloudfront.FunctionRuntime.JS_2_0,
73
- code: cloudfront.FunctionCode.fromFile({ filePath: redirectFunctionSourcePath }),
76
+ code: cloudfront.FunctionCode.fromInline(compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), edgeConfig.rules, {
77
+ negotiateMarkdown: edgeConfig.negotiateMarkdown,
78
+ directoryTwins: edgeConfig.directoryTwins,
79
+ })),
74
80
  keyValueStore,
75
81
  });
76
82
  const deadLetterQueue = new sqs.Queue(scope, "ProjectionDeadLetterQueue", {
@@ -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. */
@@ -159,7 +166,7 @@ export type PapyrusSiteBackendConfig = {
159
166
  /**
160
167
  * SES receipt options for a site that is not the legacy p.apyr.us backend.
161
168
  * By default the site gets its own brand-named rule set that is never
162
- * activated by CloudFormation (SES allows one active rule set per region).
169
+ * activated by CloudFormation (SES allows one active rule set per account/region).
163
170
  */
164
171
  inboundEmailSes?: {
165
172
  /** Add this site's rule to a rule set another stack owns instead of creating one. */
@@ -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;
@@ -68,8 +68,10 @@ export function deriveStorageBackupVaultName(identity, explicitName) {
68
68
  /**
69
69
  * The `papyrus-site-inbound-` prefix keeps a brand named `p-apyr-us` from
70
70
  * colliding with the legacy `papyrus-inbound-p-apyr-us` set.
71
- * SES allows one ACTIVE receipt rule set per region. A non-legacy site creates
72
- * its own brand-named rule set and never activates it from CloudFormation.
71
+ * SES allows one ACTIVE receipt rule set per account and region. A non-legacy
72
+ * site creates its own brand-named rule set and never activates it from
73
+ * CloudFormation. SES accounts that receive the same domain process their
74
+ * matching rules independently; the limit does not span accounts.
73
75
  */
74
76
  export function deriveReceiptRuleSetName(identity, inboundEmailDomain) {
75
77
  const name = isLegacyProductionApp(identity)
@@ -109,8 +109,10 @@ export function deriveStorageBackupVaultName(identity: SiteBackendIdentity, expl
109
109
  /**
110
110
  * The `papyrus-site-inbound-` prefix keeps a brand named `p-apyr-us` from
111
111
  * colliding with the legacy `papyrus-inbound-p-apyr-us` set.
112
- * SES allows one ACTIVE receipt rule set per region. A non-legacy site creates
113
- * its own brand-named rule set and never activates it from CloudFormation.
112
+ * SES allows one ACTIVE receipt rule set per account and region. A non-legacy
113
+ * site creates its own brand-named rule set and never activates it from
114
+ * CloudFormation. SES accounts that receive the same domain process their
115
+ * matching rules independently; the limit does not span accounts.
114
116
  */
115
117
  export function deriveReceiptRuleSetName(identity: SiteBackendIdentity, inboundEmailDomain: string): string {
116
118
  const name = isLegacyProductionApp(identity)
@@ -11,15 +11,22 @@ 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";
17
18
  import { revalidateSecretReadStatement } from "./revalidate-secret-policy.js";
18
19
  import { revalidateDistributionEnvironment, revalidateInvalidationStatement, validateRevalidateDistributionIds, } from "./revalidate-invalidation-settings.js";
19
20
  import { addInvalidationPathAlarm } from "./publication/invalidation-alarm.js";
20
- import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource.js";
21
+ import { applyCognitoDomainPrefix, applyGoogleEmailVerifiedMapping, 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 { automationView } from "./functions/automation-view/resource.js";
26
+ import { wireAutomationView } from "./functions/automation-view/wire.js";
27
+ import { monitoringView } from "./functions/monitoring-view/resource.js";
28
+ import { wireMonitoringView } from "./functions/monitoring-view/wire.js";
29
+ import { wireEditorialChecks } from "./functions/editorial-checks/wire.js";
23
30
  import { resolvePlugins } from "./plugins/registry.js";
24
31
  import { wirePlugins } from "./plugins/wire.js";
25
32
  import { assignmentAction } from "./functions/assignment-action/resource.js";
@@ -31,6 +38,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource.js";
31
38
  import { manageUserRole } from "./functions/manage-user-role/resource.js";
32
39
  import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource.js";
33
40
  import { newsroomSummary } from "./functions/newsroom-summary/resource.js";
41
+ import { derivePublicFormOrigins } from "./functions/public-forms/origins.js";
42
+ import { publicForms } from "./functions/public-forms/resource.js";
43
+ import { wirePublicForms } from "./functions/public-forms/wire.js";
34
44
  import { procedureAction } from "./functions/procedure-action/resource.js";
35
45
  import { readerSettings } from "./functions/reader-settings/resource.js";
36
46
  import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource.js";
@@ -86,15 +96,19 @@ export function defineSiteBackend(papyrusSite) {
86
96
  const backend = defineBackend({
87
97
  ...pluginFunctions,
88
98
  assignmentAction,
99
+ automationView,
89
100
  auth: defineSiteAuth(authConfig),
90
101
  categoryAction,
91
102
  contentActions,
92
- data: defineSiteData(pluginDefinitions),
103
+ editorialChecks,
104
+ data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
93
105
  knowledgeQuery,
94
106
  manageUserRole,
107
+ monitoringView,
95
108
  modelAttachmentUpload,
96
109
  newsroomSummary,
97
110
  procedureAction,
111
+ publicForms,
98
112
  readerSettings,
99
113
  emailSubmissionProcessor,
100
114
  sesInboundReceive,
@@ -102,6 +116,9 @@ export function defineSiteBackend(papyrusSite) {
102
116
  slackDelivery,
103
117
  storage,
104
118
  });
119
+ if (authConfig.disableGoogleOAuth !== true) {
120
+ applyGoogleEmailVerifiedMapping(backend.auth.stack);
121
+ }
105
122
  orderModelStacksInWaves(backend.data.resources.graphqlApi.node.scope, Object.keys(backend.data.resources.tables), site.resolverWaveSize ?? DEFAULT_RESOLVER_WAVE_SIZE);
106
123
  const requestedCognitoDomainPrefix = (authConfig.cognitoDomainPrefix ?? "").trim();
107
124
  if (authConfig.applyCognitoDomainPrefix === true
@@ -310,6 +327,14 @@ export function defineSiteBackend(papyrusSite) {
310
327
  grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
311
328
  const contentActionsLambda = backend.contentActions.resources.lambda;
312
329
  grantNewsroomReadWriteDelete(contentActionsLambda);
330
+ if ((activeBrand?.corpusKey ?? "").trim() !== "") {
331
+ // A deploy creates the publication's canonical corpus from the brand when the backend has none.
332
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
333
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
334
+ }
335
+ // AppSync validates the token; pending intake additionally binds its actor
336
+ // and publication to this deployment's pool and existing API endpoint.
337
+ contentActionsLambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
313
338
  contentActionsLambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
314
339
  contentActionsLambda.addToRolePolicy(new PolicyStatement({
315
340
  actions: ["appsync:GraphQL"],
@@ -342,6 +367,7 @@ export function defineSiteBackend(papyrusSite) {
342
367
  }
343
368
  if (s3ProjectionPublication !== undefined) {
344
369
  addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
370
+ addProjectionLeaseAccess(contentActionsLambda);
345
371
  }
346
372
  const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
347
373
  for (const [name, value] of Object.entries(publicationWiring.environment)) {
@@ -446,7 +472,7 @@ export function defineSiteBackend(papyrusSite) {
446
472
  resources: ["*"],
447
473
  }));
448
474
  const inboundEventStack = Stack.of(receiveLambda);
449
- // SES allows one active receipt rule set per region, so a non-legacy site
475
+ // SES allows one active receipt rule set per account and region, so a non-legacy site
450
476
  // creates its own brand-named rule set and never activates it here.
451
477
  const inboundSesPlan = planInboundEmailSes(siteBackendIdentity, enableInboundEmail, site.inboundEmailSes);
452
478
  if (inboundSesPlan.createReceiptRules) {
@@ -550,8 +576,21 @@ export function defineSiteBackend(papyrusSite) {
550
576
  : []),
551
577
  ],
552
578
  }));
579
+ const publicFormsEndpoint = wirePublicForms(backend, {
580
+ allowedOrigins: derivePublicFormOrigins({
581
+ explicit: site.publicForms?.allowedOrigins,
582
+ hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
583
+ urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
584
+ }),
585
+ honeypotFields: site.publicForms?.honeypotFields,
586
+ });
553
587
  backend.addOutput({
554
588
  custom: {
589
+ publicForms: { endpoint: publicFormsEndpoint },
590
+ // Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
591
+ publication: s3ProjectionPublication !== undefined
592
+ ? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
593
+ : null,
555
594
  prospectusSignIn: prospectusGoogleOnlyClient
556
595
  ? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
557
596
  : null,
@@ -586,6 +625,9 @@ export function defineSiteBackend(papyrusSite) {
586
625
  : null,
587
626
  },
588
627
  });
628
+ wireEditorialChecks(backend);
629
+ wireMonitoringView(backend);
630
+ wireAutomationView(backend);
589
631
  wirePlugins(backend, plugins, contentActionsLambda);
590
632
  return backend;
591
633
  }
@@ -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";
@@ -24,9 +25,15 @@ import {
24
25
  validateRevalidateDistributionIds,
25
26
  } from "./revalidate-invalidation-settings";
26
27
  import { addInvalidationPathAlarm } from "./publication/invalidation-alarm";
27
- import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource";
28
+ import { applyCognitoDomainPrefix, applyGoogleEmailVerifiedMapping, 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 { automationView } from "./functions/automation-view/resource";
33
+ import { wireAutomationView } from "./functions/automation-view/wire";
34
+ import { monitoringView } from "./functions/monitoring-view/resource";
35
+ import { wireMonitoringView } from "./functions/monitoring-view/wire";
36
+ import { wireEditorialChecks } from "./functions/editorial-checks/wire";
30
37
  import { resolvePlugins } from "./plugins/registry";
31
38
  import { wirePlugins } from "./plugins/wire";
32
39
  import { assignmentAction } from "./functions/assignment-action/resource";
@@ -38,6 +45,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource";
38
45
  import { manageUserRole } from "./functions/manage-user-role/resource";
39
46
  import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
40
47
  import { newsroomSummary } from "./functions/newsroom-summary/resource";
48
+ import { derivePublicFormOrigins } from "./functions/public-forms/origins";
49
+ import { publicForms } from "./functions/public-forms/resource";
50
+ import { wirePublicForms } from "./functions/public-forms/wire";
41
51
  import { procedureAction } from "./functions/procedure-action/resource";
42
52
  import { readerSettings } from "./functions/reader-settings/resource";
43
53
  import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource";
@@ -107,15 +117,19 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
107
117
  const backend = defineBackend({
108
118
  ...pluginFunctions,
109
119
  assignmentAction,
120
+ automationView,
110
121
  auth: defineSiteAuth(authConfig),
111
122
  categoryAction,
112
123
  contentActions,
113
- data: defineSiteData(pluginDefinitions),
124
+ editorialChecks,
125
+ data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
114
126
  knowledgeQuery,
115
127
  manageUserRole,
128
+ monitoringView,
116
129
  modelAttachmentUpload,
117
130
  newsroomSummary,
118
131
  procedureAction,
132
+ publicForms,
119
133
  readerSettings,
120
134
  emailSubmissionProcessor,
121
135
  sesInboundReceive,
@@ -124,6 +138,10 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
124
138
  storage,
125
139
  });
126
140
 
141
+ if (authConfig.disableGoogleOAuth !== true) {
142
+ applyGoogleEmailVerifiedMapping(backend.auth.stack);
143
+ }
144
+
127
145
  orderModelStacksInWaves(
128
146
  backend.data.resources.graphqlApi.node.scope as Construct,
129
147
  Object.keys(backend.data.resources.tables),
@@ -396,6 +414,17 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
396
414
  grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
397
415
  const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
398
416
  grantNewsroomReadWriteDelete(contentActionsLambda);
417
+ if ((activeBrand?.corpusKey ?? "").trim() !== "") {
418
+ // A deploy creates the publication's canonical corpus from the brand when the backend has none.
419
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
420
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
421
+ }
422
+ // AppSync validates the token; pending intake additionally binds its actor
423
+ // and publication to this deployment's pool and existing API endpoint.
424
+ contentActionsLambda.addEnvironment(
425
+ "PAPYRUS_USER_POOL_ISSUER",
426
+ `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`,
427
+ );
399
428
  contentActionsLambda.addEnvironment(
400
429
  "PAPYRUS_GRAPHQL_ENDPOINT",
401
430
  backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
@@ -435,6 +464,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
435
464
  }
436
465
  if (s3ProjectionPublication !== undefined) {
437
466
  addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
467
+ addProjectionLeaseAccess(contentActionsLambda);
438
468
  }
439
469
  const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
440
470
  for (const [name, value] of Object.entries(publicationWiring.environment)) {
@@ -575,7 +605,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
575
605
 
576
606
  const inboundEventStack = Stack.of(receiveLambda);
577
607
 
578
- // SES allows one active receipt rule set per region, so a non-legacy site
608
+ // SES allows one active receipt rule set per account and region, so a non-legacy site
579
609
  // creates its own brand-named rule set and never activates it here.
580
610
  const inboundSesPlan = planInboundEmailSes(siteBackendIdentity, enableInboundEmail, site.inboundEmailSes);
581
611
  if (inboundSesPlan.createReceiptRules) {
@@ -690,8 +720,23 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
690
720
  }),
691
721
  );
692
722
 
723
+ const publicFormsEndpoint = wirePublicForms(backend, {
724
+ allowedOrigins: derivePublicFormOrigins({
725
+ explicit: site.publicForms?.allowedOrigins,
726
+ hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
727
+ urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
728
+ }),
729
+ honeypotFields: site.publicForms?.honeypotFields,
730
+ });
731
+
693
732
  backend.addOutput({
694
733
  custom: {
734
+ publicForms: { endpoint: publicFormsEndpoint },
735
+ // Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
736
+ publication:
737
+ s3ProjectionPublication !== undefined
738
+ ? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
739
+ : null,
695
740
  prospectusSignIn: prospectusGoogleOnlyClient
696
741
  ? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
697
742
  : null,
@@ -726,6 +771,9 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
726
771
  : null,
727
772
  },
728
773
  });
774
+ wireEditorialChecks(backend);
775
+ wireMonitoringView(backend);
776
+ wireAutomationView(backend);
729
777
  wirePlugins(backend, plugins, contentActionsLambda);
730
778
 
731
779
  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
+ }