@anthusai/papyrus 2.0.0-next.1 → 2.0.0-next.100
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/amplify/auth/resource.js +16 -1
- package/amplify/auth/resource.ts +19 -1
- package/amplify/data/function-handler.js +20 -0
- package/amplify/data/function-handler.ts +40 -0
- package/amplify/data/resource.js +158 -61
- package/amplify/data/resource.ts +165 -61
- package/amplify/data/schema.js +2103 -1979
- package/amplify/data/schema.ts +206 -68
- package/amplify/functions/assignment-action/data-access.js +90 -0
- package/amplify/functions/assignment-action/data-access.ts +89 -0
- package/amplify/functions/assignment-action/handler.ts +1 -1
- package/amplify/functions/automation-view/handler.py +17 -0
- package/amplify/functions/automation-view/resource.js +24 -0
- package/amplify/functions/automation-view/resource.ts +34 -0
- package/amplify/functions/automation-view/schema.js +26 -0
- package/amplify/functions/automation-view/schema.ts +28 -0
- package/amplify/functions/automation-view/wire.js +56 -0
- package/amplify/functions/automation-view/wire.ts +76 -0
- package/amplify/functions/content-actions/handler.py +70 -2
- package/amplify/functions/content-actions/requirements.txt +1 -0
- package/amplify/functions/content-actions/resource.js +1 -1
- package/amplify/functions/content-actions/resource.ts +1 -1
- package/amplify/functions/editorial-checks/handler.py +14 -0
- package/amplify/functions/editorial-checks/requirements.txt +1 -0
- package/amplify/functions/editorial-checks/resource.js +43 -0
- package/amplify/functions/editorial-checks/resource.ts +60 -0
- package/amplify/functions/editorial-checks/schema.js +122 -0
- package/amplify/functions/editorial-checks/schema.ts +124 -0
- package/amplify/functions/editorial-checks/wire.js +53 -0
- package/amplify/functions/editorial-checks/wire.ts +61 -0
- package/amplify/functions/model-attachment-upload/handler.ts +1 -0
- package/amplify/functions/monitoring-view/handler.py +11 -0
- package/amplify/functions/monitoring-view/resource.js +24 -0
- package/amplify/functions/monitoring-view/resource.ts +34 -0
- package/amplify/functions/monitoring-view/schema.js +29 -0
- package/amplify/functions/monitoring-view/schema.ts +31 -0
- package/amplify/functions/monitoring-view/wire.js +64 -0
- package/amplify/functions/monitoring-view/wire.ts +81 -0
- package/amplify/functions/public-forms/core.js +247 -0
- package/amplify/functions/public-forms/core.ts +310 -0
- package/amplify/functions/public-forms/handler.ts +41 -0
- package/amplify/functions/public-forms/origins.js +35 -0
- package/amplify/functions/public-forms/origins.ts +43 -0
- package/amplify/functions/public-forms/resource.js +9 -0
- package/amplify/functions/public-forms/resource.ts +10 -0
- package/amplify/functions/public-forms/wire.js +25 -0
- package/amplify/functions/public-forms/wire.ts +32 -0
- package/amplify/functions/shared/editorial-context.ts +63 -4
- package/amplify/functions/shared/python-bundle.js +20 -14
- package/amplify/functions/shared/python-bundle.ts +25 -18
- package/amplify/plugins/newsletter/config.js +245 -0
- package/amplify/plugins/newsletter/config.ts +305 -0
- package/amplify/plugins/newsletter/function/handler.py +6 -0
- package/amplify/plugins/newsletter/function/requirements.txt +5 -0
- package/amplify/plugins/newsletter/ops/handler.py +12 -0
- package/amplify/plugins/newsletter/ops/resource.js +27 -0
- package/amplify/plugins/newsletter/ops/resource.ts +37 -0
- package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
- package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
- package/amplify/plugins/newsletter/schema.js +57 -0
- package/amplify/plugins/newsletter/schema.ts +62 -0
- package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
- package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
- package/amplify/plugins/newsletter/sending.js +140 -0
- package/amplify/plugins/newsletter/sending.ts +186 -0
- package/amplify/plugins/reader-steering/config.js +182 -0
- package/amplify/plugins/reader-steering/config.ts +290 -0
- package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
- package/amplify/plugins/reader-steering/function/handler.py +1567 -0
- package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
- package/amplify/plugins/reader-steering/function/moderation.py +316 -0
- package/amplify/plugins/reader-steering/function/proposals.py +458 -0
- package/amplify/plugins/reader-steering/function/resource.js +27 -0
- package/amplify/plugins/reader-steering/function/resource.ts +35 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.js +186 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +212 -0
- package/amplify/plugins/reader-steering/schema.js +229 -0
- package/amplify/plugins/reader-steering/schema.ts +231 -0
- package/amplify/plugins/registry.js +4 -0
- package/amplify/plugins/registry.ts +4 -0
- package/amplify/plugins/signup/schema.js +2 -1
- package/amplify/plugins/signup/schema.ts +2 -1
- package/amplify/plugins/types.ts +5 -0
- package/amplify/plugins/wire.js +1 -0
- package/amplify/plugins/wire.ts +1 -0
- package/amplify/publication/projection-lease.js +30 -0
- package/amplify/publication/projection-lease.ts +39 -0
- package/amplify/publication/redirect-and-index.js +105 -0
- package/amplify/publication/redirect-rules.js +73 -0
- package/amplify/publication/redirect-rules.ts +93 -0
- package/amplify/publication/s3-projection.js +7 -1
- package/amplify/publication/s3-projection.ts +9 -1
- package/amplify/site-backend-config.js +19 -0
- package/amplify/site-backend-config.ts +45 -1
- package/amplify/site-backend-names.js +4 -2
- package/amplify/site-backend-names.ts +4 -2
- package/amplify/site-backend.js +51 -4
- package/amplify/site-backend.ts +60 -4
- package/app/api/auth/prospectus/sign-in/route.ts +4 -0
- package/app/api/newsletter/confirm/route.ts +13 -0
- package/app/api/newsletter/preferences/route.ts +13 -0
- package/app/api/newsletter/state/route.ts +9 -0
- package/app/api/newsletter/subscribe/route.ts +9 -0
- package/app/api/newsletter/unsubscribe/route.ts +13 -0
- package/app/api/newsroom/chat/[...path]/route.ts +17 -0
- package/app/api/newsroom/curation/context/route.ts +9 -0
- package/app/api/newsroom/curation/priming/route.ts +9 -0
- package/app/api/newsroom/curation/publication/route.ts +9 -0
- package/app/api/reader/comment/delete/route.ts +9 -0
- package/app/api/reader/comment/edit/route.ts +9 -0
- package/app/api/reader/comment/route.ts +9 -0
- package/app/api/reader/flag/route.ts +9 -0
- package/app/api/reader/handle/route.ts +9 -0
- package/app/api/reader/me/route.ts +9 -0
- package/app/api/reader/rating/route.ts +9 -0
- package/app/api/reader/report/route.ts +9 -0
- package/app/api/reader/retired/route.ts +9 -0
- package/app/api/reader/submit/route.ts +9 -0
- package/app/api/reader/vote/route.ts +9 -0
- package/app/layout.tsx +5 -1
- package/bin/papyrus-infra.mjs +20 -1
- package/components/automation-panel.tsx +49 -0
- package/components/automation-run-now.tsx +97 -0
- package/components/automation-view.tsx +195 -0
- package/components/editorial-checks-admin-panel.tsx +362 -0
- package/components/editorial-finding-card.tsx +162 -0
- package/components/editorial-findings-panel.tsx +91 -0
- package/components/editorial-preview-popover.tsx +103 -0
- package/components/markus-body-editor.tsx +213 -0
- package/components/markus-ir-preview.tsx +147 -6
- package/components/moderation-panel.tsx +248 -0
- package/components/monitoring-overview-view.tsx +414 -0
- package/components/monitoring-panel.tsx +60 -0
- package/components/news-desk-client-provider.tsx +18 -18
- package/components/news-desk-taxonomy-client.ts +28 -8
- package/components/newsletter-panel.tsx +474 -0
- package/components/newsletter-sections.tsx +163 -0
- package/components/newsroom-access-gate.tsx +16 -2
- package/components/newsroom-article-editor.tsx +145 -18
- package/components/newsroom-articles-view.tsx +6 -6
- package/components/newsroom-assignment-detail.tsx +6 -6
- package/components/newsroom-board-view.tsx +14 -10
- package/components/newsroom-curation-bootstrap.tsx +45 -0
- package/components/newsroom-ops-overview.tsx +26 -96
- package/components/newsroom-ops-shell.tsx +9 -5
- package/components/newsroom-organizer.css +90 -61
- package/components/newsroom-overview-skeleton.tsx +25 -0
- package/components/newsroom-references-view.tsx +192 -49
- package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
- package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
- package/components/newsroom-theme-fonts/night-desk.css +10 -0
- package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
- package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
- package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
- package/components/newsroom-theme.css +39 -0
- package/components/newsroom-theme.tsx +33 -0
- package/components/notification-center.tsx +281 -0
- package/components/papyrus-console-conversation.css +14 -0
- package/components/papyrus-console-conversation.tsx +55 -0
- package/components/papyrus-console-shell.tsx +62 -420
- package/components/papyrus-private-chat-panel.tsx +134 -0
- package/components/publication-cyclotron-panel.tsx +15 -10
- package/components/reader-auth-state.ts +1 -1
- package/components/reference-add-entry.tsx +75 -0
- package/components/reference-curation-controls.tsx +31 -22
- package/components/reference-cyclotron-priming.tsx +63 -25
- package/components/reference-cyclotron-status.tsx +4 -4
- package/components/reference-cyclotron-suggestion.tsx +15 -11
- package/components/reference-live-agreement.tsx +10 -10
- package/components/reference-priming.tsx +14 -10
- package/components/reference-relevance-review.tsx +4 -4
- package/components/reference-section-suggestion.tsx +2 -2
- package/components/topic-steering-workspace.tsx +241 -144
- package/components/use-editorial-checks.ts +133 -0
- package/infra/amplify-app-shell.js +96 -16
- package/infra/amplify-app-shell.ts +108 -18
- package/infra/cloud-email-config.js +32 -0
- package/infra/cloud-email-config.ts +39 -0
- package/infra/cloud-email.js +57 -0
- package/infra/cloud-email.test.js +69 -0
- package/infra/cloud-email.test.ts +66 -0
- package/infra/cloud-email.ts +60 -0
- package/infra/index.js +1 -0
- package/infra/secure-string-parameter-handler.js +41 -0
- package/infra/secure-string-parameter-handler.ts +41 -0
- package/infra/secure-string-parameter.js +79 -0
- package/infra/secure-string-parameter.ts +91 -0
- package/infra/site-config.js +92 -6
- package/infra/site-config.ts +138 -6
- package/lib/automation-client.ts +54 -0
- package/lib/automation-fixture.ts +93 -0
- package/lib/automation-overview.ts +252 -0
- package/lib/category-repository.ts +14 -12
- package/lib/console-web-context.ts +1 -1
- package/lib/curation-routes.ts +345 -0
- package/lib/cyclotron-suggestions.ts +11 -11
- package/lib/editorial-checks-backend.ts +16 -0
- package/lib/editorial-checks-client.ts +212 -0
- package/lib/editorial-checks-demo.ts +147 -0
- package/lib/editorial-checks-settings.ts +27 -0
- package/lib/editorial-checks.ts +175 -0
- package/lib/editorial-preview.ts +228 -0
- package/lib/monitoring-client.ts +83 -0
- package/lib/monitoring-overview-fixture.ts +51 -0
- package/lib/monitoring-overview.ts +188 -0
- package/lib/news-desk-session.ts +12 -21
- package/lib/newsletter-api.ts +418 -0
- package/lib/newsletter-client.ts +46 -0
- package/lib/newsletter-fixture.ts +184 -0
- package/lib/newsletter-model.ts +202 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/newsroom-demo-dashboard.ts +1 -1
- package/lib/newsroom-nav.ts +1 -1
- package/lib/newsroom-organizer-nav.ts +2 -1
- package/lib/papyrus-chat-admission.ts +65 -0
- package/lib/papyrus-hosted-chat.ts +217 -0
- package/lib/papyrus-private-chat-client.ts +81 -0
- package/lib/prospectus-session.ts +30 -14
- package/lib/prospectus-sign-in-routes.ts +106 -31
- package/lib/prospectus-site-identity.ts +46 -6
- package/lib/reader-api.ts +145 -0
- package/lib/reader-moderation.ts +220 -0
- package/lib/reader-retirement-route.ts +89 -0
- package/lib/reader-subject.md +29 -0
- package/lib/reader-subject.ts +53 -0
- package/lib/reference-review-details.ts +71 -0
- package/lib/reference-review-request.ts +14 -0
- package/lib/relevance-decisions.ts +42 -10
- package/lib/relevance-status.ts +6 -6
- package/lib/review-client.ts +120 -0
- package/lib/review-fixture.ts +45 -0
- package/lib/review-requests.ts +246 -0
- package/lib/semantic-graph.ts +2 -0
- package/lib/shared-chat-admission.ts +182 -0
- package/lib/site-brand-tenant.ts +11 -6
- package/lib/site-brand.ts +23 -3
- package/lib/site-origin-value.ts +50 -0
- package/lib/site-origin.ts +8 -0
- package/lib/use-reference-review-data.ts +44 -0
- package/middleware.ts +14 -3
- package/package.json +98 -4
- package/routes.manifest.json +227 -2
- package/src/with-papyrus.mjs +1 -1
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
- package/components/newsroom-newsprint-board.css +0 -7
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** One pattern redirect as the edge function reads it: regex, target with $1..$9, status, excluded paths. */
|
|
5
|
+
export type EdgeRedirectRule = { p: string; t: string; s: number; x: string[] };
|
|
6
|
+
|
|
7
|
+
/** Mirrors DIRECTORY_HTML_PATTERN and DIRECTORY_SLASH_PATTERN in standard/config.py. */
|
|
8
|
+
const DIRECTORY_HTML_PATTERN = "^/(?!(?:.*/)?index\\.html$)(.+)\\.html$";
|
|
9
|
+
const DIRECTORY_SLASH_PATTERN = "^/((?:[^/]+/)*[^/.]+)$";
|
|
10
|
+
|
|
11
|
+
const START_MARKER = "/*PAPYRUS_PATTERN_RULES*/";
|
|
12
|
+
const END_MARKER = "/*END_PAPYRUS_PATTERN_RULES*/";
|
|
13
|
+
|
|
14
|
+
type ProjectionRedirects = { patterns?: { pattern: string; to: string; status?: number; except?: string[] }[] };
|
|
15
|
+
type ProjectionSettings = { site?: unknown; urls?: { style?: string }; redirects?: ProjectionRedirects; agents?: { markdown?: boolean } };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The pattern rules a site declares in projection.json: its `redirects.patterns`, plus, for
|
|
19
|
+
* `urls.style: "directory"`, `/x.html` to `/x/`. Mirrors `StandardSettings.edge_rules` in Python.
|
|
20
|
+
*/
|
|
21
|
+
export function edgeRulesFromSettings(settings: ProjectionSettings): EdgeRedirectRule[] {
|
|
22
|
+
const rules = (settings.redirects?.patterns ?? []).map((rule) => ({
|
|
23
|
+
p: rule.pattern,
|
|
24
|
+
t: rule.to,
|
|
25
|
+
s: rule.status ?? 301,
|
|
26
|
+
x: rule.except ?? [],
|
|
27
|
+
}));
|
|
28
|
+
if (settings.urls?.style === "directory") {
|
|
29
|
+
// /x.html to /x/ (a directory index file such as /x/index.html is served as it is), then /x to /x/.
|
|
30
|
+
rules.push({ p: DIRECTORY_HTML_PATTERN, t: "/$1/", s: 301, x: ["/404.html"] });
|
|
31
|
+
rules.push({ p: DIRECTORY_SLASH_PATTERN, t: "/$1/", s: 301, x: ["/404"] });
|
|
32
|
+
}
|
|
33
|
+
return rules;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A standard-front-end site publishes Markdown twins unless it turns `agents.markdown` off. */
|
|
37
|
+
export function negotiatesMarkdown(settings: ProjectionSettings): boolean {
|
|
38
|
+
return settings.site !== undefined && settings.agents?.markdown !== false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A directory-style site keeps a page's Markdown twin at `/x/index.md`; the edge then serves a bare `/x.md` from there. */
|
|
42
|
+
export function usesDirectoryTwins(settings: ProjectionSettings): boolean {
|
|
43
|
+
return negotiatesMarkdown(settings) && settings.urls?.style === "directory";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type EdgeConfig = { rules: EdgeRedirectRule[]; negotiateMarkdown: boolean; directoryTwins: boolean };
|
|
47
|
+
|
|
48
|
+
export function readEdgeConfig(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeConfig {
|
|
49
|
+
if (staticDir === undefined) return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
50
|
+
const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
|
|
51
|
+
if (!existsSync(settingsPath)) return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
52
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8")) as ProjectionSettings;
|
|
53
|
+
return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings), directoryTwins: usesDirectoryTwins(settings) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function readEdgeRules(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeRedirectRule[] {
|
|
57
|
+
return readEdgeConfig(staticDir, baseDirectory).rules;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
61
|
+
const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
62
|
+
const TWINS_START = "/*PAPYRUS_DIRECTORY_TWINS*/";
|
|
63
|
+
const TWINS_END = "/*END_PAPYRUS_DIRECTORY_TWINS*/";
|
|
64
|
+
|
|
65
|
+
function spliceFlag(source: string, start: string, end: string, label: string): string {
|
|
66
|
+
const from = source.indexOf(start);
|
|
67
|
+
const to = source.indexOf(end);
|
|
68
|
+
if (from === -1 || to === -1 || to < from) {
|
|
69
|
+
throw new Error(`redirect function source has no ${label} markers`);
|
|
70
|
+
}
|
|
71
|
+
return source.slice(0, from + start.length) + "true" + source.slice(to);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
|
|
75
|
+
export function compileRedirectFunctionSource(
|
|
76
|
+
source: string,
|
|
77
|
+
rules: EdgeRedirectRule[],
|
|
78
|
+
options: { negotiateMarkdown?: boolean; directoryTwins?: boolean } = {},
|
|
79
|
+
): string {
|
|
80
|
+
const start = source.indexOf(START_MARKER);
|
|
81
|
+
const end = source.indexOf(END_MARKER);
|
|
82
|
+
if (start === -1 || end === -1 || end < start) {
|
|
83
|
+
throw new Error("redirect function source has no pattern-rule markers");
|
|
84
|
+
}
|
|
85
|
+
let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
|
|
86
|
+
if (options.negotiateMarkdown === true) {
|
|
87
|
+
compiled = spliceFlag(compiled, NEGOTIATE_START, NEGOTIATE_END, "markdown negotiation");
|
|
88
|
+
}
|
|
89
|
+
if (options.directoryTwins === true) {
|
|
90
|
+
compiled = spliceFlag(compiled, TWINS_START, TWINS_END, "directory twin");
|
|
91
|
+
}
|
|
92
|
+
return compiled;
|
|
93
|
+
}
|
|
@@ -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.
|
|
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.
|
|
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", {
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
export const MAXIMUM_HTML_S_MAXAGE_SECONDS = 86400;
|
|
2
|
+
const MONITOR_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
3
|
+
export function validateMonitoringConfig(monitoring) {
|
|
4
|
+
if (monitoring === undefined)
|
|
5
|
+
return [];
|
|
6
|
+
if (typeof monitoring !== "object" || monitoring === null || Array.isArray(monitoring)) {
|
|
7
|
+
return ["monitoring must be an object"];
|
|
8
|
+
}
|
|
9
|
+
const config = monitoring;
|
|
10
|
+
if (config.emailMonitorId !== undefined
|
|
11
|
+
&& (typeof config.emailMonitorId !== "string" || !MONITOR_ID_PATTERN.test(config.emailMonitorId))) {
|
|
12
|
+
return ["monitoring.emailMonitorId must be a lowercase monitor id (1-63 letters, digits, or hyphens)"];
|
|
13
|
+
}
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
export function assertValidMonitoringConfig(monitoring) {
|
|
17
|
+
const problems = validateMonitoringConfig(monitoring);
|
|
18
|
+
if (problems.length > 0)
|
|
19
|
+
throw new Error(`Invalid backend.monitoring: ${problems.join("; ")}`);
|
|
20
|
+
}
|
|
2
21
|
const CLOUDFRONT_DISTRIBUTION_ARN = /^arn:aws[a-z-]*:cloudfront::\d{12}:distribution\/[A-Z0-9]+$/;
|
|
3
22
|
function isRelativePathWithoutParentSegments(value) {
|
|
4
23
|
if (value === "" || value.startsWith("/") || value.includes("\\"))
|
|
@@ -40,6 +40,31 @@ export type PapyrusPublicationCacheConfig = {
|
|
|
40
40
|
|
|
41
41
|
export const MAXIMUM_HTML_S_MAXAGE_SECONDS = 86400;
|
|
42
42
|
|
|
43
|
+
export type PapyrusMonitoringConfig = {
|
|
44
|
+
/** Existing shared Monitoring partition that stores this site's private email receipts. */
|
|
45
|
+
emailMonitorId?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const MONITOR_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
49
|
+
|
|
50
|
+
export function validateMonitoringConfig(monitoring: unknown): string[] {
|
|
51
|
+
if (monitoring === undefined) return [];
|
|
52
|
+
if (typeof monitoring !== "object" || monitoring === null || Array.isArray(monitoring)) {
|
|
53
|
+
return ["monitoring must be an object"];
|
|
54
|
+
}
|
|
55
|
+
const config = monitoring as { emailMonitorId?: unknown };
|
|
56
|
+
if (config.emailMonitorId !== undefined
|
|
57
|
+
&& (typeof config.emailMonitorId !== "string" || !MONITOR_ID_PATTERN.test(config.emailMonitorId))) {
|
|
58
|
+
return ["monitoring.emailMonitorId must be a lowercase monitor id (1-63 letters, digits, or hyphens)"];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertValidMonitoringConfig(monitoring: unknown): void {
|
|
64
|
+
const problems = validateMonitoringConfig(monitoring);
|
|
65
|
+
if (problems.length > 0) throw new Error(`Invalid backend.monitoring: ${problems.join("; ")}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
43
68
|
export type PapyrusPublicationConfig = {
|
|
44
69
|
kind: "s3-projection";
|
|
45
70
|
/** Site repo path (relative, `.py`) of the SiteProjector bundled into content-actions as `site_projector`. */
|
|
@@ -138,6 +163,8 @@ export type PapyrusSiteBackendConfig = {
|
|
|
138
163
|
*/
|
|
139
164
|
productionAppId?: string;
|
|
140
165
|
auth?: PapyrusAuthConfig;
|
|
166
|
+
/** Shared Monitoring partition used by private email intake for this publication. */
|
|
167
|
+
monitoring?: PapyrusMonitoringConfig;
|
|
141
168
|
/**
|
|
142
169
|
* Maximum number of data model nested stacks CloudFormation creates at once.
|
|
143
170
|
* Stacks are chained in waves of this size so a new AWS account is not hit
|
|
@@ -145,6 +172,13 @@ export type PapyrusSiteBackendConfig = {
|
|
|
145
172
|
* DependsOn ordering; no resource changes. Default 12; 0 turns it off.
|
|
146
173
|
*/
|
|
147
174
|
resolverWaveSize?: number;
|
|
175
|
+
/**
|
|
176
|
+
* Optional model groups to leave out of the data API (see `OPTIONAL_MODEL_GROUPS`; today `editions`).
|
|
177
|
+
* CloudFormation allows 2,500 resources per stack operation, nested stacks included, and a fresh create of the full
|
|
178
|
+
* model set exceeds it. Deploy a new site once with a group excluded, then remove the exclusion and deploy again:
|
|
179
|
+
* the second deploy is an ordinary update that adds the models.
|
|
180
|
+
*/
|
|
181
|
+
excludeModelGroups?: string[];
|
|
148
182
|
/** Static reader app (Markus-static sites) that a publish rebuilds through IAM `amplify:StartJob`. */
|
|
149
183
|
reader?: { amplifyAppId: string; branchName?: string };
|
|
150
184
|
/** Lets the newsroom start the `staging` branch build of this CMS app. */
|
|
@@ -159,7 +193,7 @@ export type PapyrusSiteBackendConfig = {
|
|
|
159
193
|
/**
|
|
160
194
|
* SES receipt options for a site that is not the legacy p.apyr.us backend.
|
|
161
195
|
* 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).
|
|
196
|
+
* activated by CloudFormation (SES allows one active rule set per account/region).
|
|
163
197
|
*/
|
|
164
198
|
inboundEmailSes?: {
|
|
165
199
|
/** Add this site's rule to a rule set another stack owns instead of creating one. */
|
|
@@ -178,6 +212,16 @@ export type PapyrusSiteBackendConfig = {
|
|
|
178
212
|
* Unset: no plugin, and the synthesized backend is exactly what it was before plugins existed.
|
|
179
213
|
*/
|
|
180
214
|
plugins?: PluginRequest[];
|
|
215
|
+
/**
|
|
216
|
+
* The public form receiving endpoint every site gets (docs/public-forms.md). It accepts posts only from the site's
|
|
217
|
+
* own public hosts (distribution aliases, reader domain, revalidate URL, sign-in redirect URLs) plus these.
|
|
218
|
+
*/
|
|
219
|
+
publicForms?: {
|
|
220
|
+
/** Extra https origins (or http://localhost) allowed to post, for example `https://www.example.org`. */
|
|
221
|
+
allowedOrigins?: string[];
|
|
222
|
+
/** Extra honeypot field names besides `website`. */
|
|
223
|
+
honeypotFields?: string[];
|
|
224
|
+
};
|
|
181
225
|
features?: {
|
|
182
226
|
consoleResponder?: boolean;
|
|
183
227
|
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
|
|
72
|
-
* its own brand-named rule set and never activates it from
|
|
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
|
|
113
|
-
* its own brand-named rule set and never activates it from
|
|
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)
|
package/amplify/site-backend.js
CHANGED
|
@@ -11,18 +11,26 @@ 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
|
-
import { assertValidPublicationConfig } from "./site-backend-config.js";
|
|
17
|
+
import { assertValidMonitoringConfig, 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 { wireEmailReferenceIntake, 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";
|
|
33
|
+
import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access.js";
|
|
26
34
|
import { categoryAction } from "./functions/category-action/resource.js";
|
|
27
35
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
|
|
28
36
|
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource.js";
|
|
@@ -30,6 +38,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource.js";
|
|
|
30
38
|
import { manageUserRole } from "./functions/manage-user-role/resource.js";
|
|
31
39
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource.js";
|
|
32
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";
|
|
33
44
|
import { procedureAction } from "./functions/procedure-action/resource.js";
|
|
34
45
|
import { readerSettings } from "./functions/reader-settings/resource.js";
|
|
35
46
|
import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource.js";
|
|
@@ -76,6 +87,8 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
76
87
|
const publication = resolveSitePublication(site, activeBrand?.renderer?.kind);
|
|
77
88
|
if (publication !== undefined)
|
|
78
89
|
assertValidPublicationConfig(publication);
|
|
90
|
+
assertValidMonitoringConfig(site.monitoring);
|
|
91
|
+
const emailMonitorId = site.monitoring?.emailMonitorId ?? "hn";
|
|
79
92
|
const publicationPlan = planS3ProjectionBackend(publication);
|
|
80
93
|
const renditionsPlan = planRenditions(publication, process.cwd());
|
|
81
94
|
setSiteFilesForContentActionsBundle(publicationPlan.enabled ? publicationPlan.siteFiles : undefined);
|
|
@@ -85,15 +98,19 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
85
98
|
const backend = defineBackend({
|
|
86
99
|
...pluginFunctions,
|
|
87
100
|
assignmentAction,
|
|
101
|
+
automationView,
|
|
88
102
|
auth: defineSiteAuth(authConfig),
|
|
89
103
|
categoryAction,
|
|
90
104
|
contentActions,
|
|
91
|
-
|
|
105
|
+
editorialChecks,
|
|
106
|
+
data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
|
|
92
107
|
knowledgeQuery,
|
|
93
108
|
manageUserRole,
|
|
109
|
+
monitoringView,
|
|
94
110
|
modelAttachmentUpload,
|
|
95
111
|
newsroomSummary,
|
|
96
112
|
procedureAction,
|
|
113
|
+
publicForms,
|
|
97
114
|
readerSettings,
|
|
98
115
|
emailSubmissionProcessor,
|
|
99
116
|
sesInboundReceive,
|
|
@@ -101,6 +118,9 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
101
118
|
slackDelivery,
|
|
102
119
|
storage,
|
|
103
120
|
});
|
|
121
|
+
if (authConfig.disableGoogleOAuth !== true) {
|
|
122
|
+
applyGoogleEmailVerifiedMapping(backend.auth.stack);
|
|
123
|
+
}
|
|
104
124
|
orderModelStacksInWaves(backend.data.resources.graphqlApi.node.scope, Object.keys(backend.data.resources.tables), site.resolverWaveSize ?? DEFAULT_RESOLVER_WAVE_SIZE);
|
|
105
125
|
const requestedCognitoDomainPrefix = (authConfig.cognitoDomainPrefix ?? "").trim();
|
|
106
126
|
if (authConfig.applyCognitoDomainPrefix === true
|
|
@@ -304,11 +324,21 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
304
324
|
}
|
|
305
325
|
grantCorporaRead(backend.knowledgeQuery.resources.lambda);
|
|
306
326
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda);
|
|
327
|
+
scopeAssignmentDataAccess(backend.assignmentAction.resources.lambda, backend.data.resources.graphqlApi.arn);
|
|
307
328
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda);
|
|
308
329
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
|
|
309
330
|
const contentActionsLambda = backend.contentActions.resources.lambda;
|
|
310
331
|
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
332
|
+
if ((activeBrand?.corpusKey ?? "").trim() !== "") {
|
|
333
|
+
// A deploy creates the publication's canonical corpus from the brand when the backend has none.
|
|
334
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
|
|
335
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
|
|
336
|
+
}
|
|
337
|
+
// AppSync validates the token; pending intake additionally binds its actor
|
|
338
|
+
// and publication to this deployment's pool and existing API endpoint.
|
|
339
|
+
contentActionsLambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
|
|
311
340
|
contentActionsLambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
|
|
341
|
+
wireEmailReferenceIntake(contentActionsLambda, emailMonitorId);
|
|
312
342
|
contentActionsLambda.addToRolePolicy(new PolicyStatement({
|
|
313
343
|
actions: ["appsync:GraphQL"],
|
|
314
344
|
resources: ["*"],
|
|
@@ -340,6 +370,7 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
340
370
|
}
|
|
341
371
|
if (s3ProjectionPublication !== undefined) {
|
|
342
372
|
addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
|
|
373
|
+
addProjectionLeaseAccess(contentActionsLambda);
|
|
343
374
|
}
|
|
344
375
|
const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
|
|
345
376
|
for (const [name, value] of Object.entries(publicationWiring.environment)) {
|
|
@@ -444,7 +475,7 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
444
475
|
resources: ["*"],
|
|
445
476
|
}));
|
|
446
477
|
const inboundEventStack = Stack.of(receiveLambda);
|
|
447
|
-
// SES allows one active receipt rule set per region, so a non-legacy site
|
|
478
|
+
// SES allows one active receipt rule set per account and region, so a non-legacy site
|
|
448
479
|
// creates its own brand-named rule set and never activates it here.
|
|
449
480
|
const inboundSesPlan = planInboundEmailSes(siteBackendIdentity, enableInboundEmail, site.inboundEmailSes);
|
|
450
481
|
if (inboundSesPlan.createReceiptRules) {
|
|
@@ -548,8 +579,21 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
548
579
|
: []),
|
|
549
580
|
],
|
|
550
581
|
}));
|
|
582
|
+
const publicFormsEndpoint = wirePublicForms(backend, {
|
|
583
|
+
allowedOrigins: derivePublicFormOrigins({
|
|
584
|
+
explicit: site.publicForms?.allowedOrigins,
|
|
585
|
+
hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
|
|
586
|
+
urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
|
|
587
|
+
}),
|
|
588
|
+
honeypotFields: site.publicForms?.honeypotFields,
|
|
589
|
+
});
|
|
551
590
|
backend.addOutput({
|
|
552
591
|
custom: {
|
|
592
|
+
publicForms: { endpoint: publicFormsEndpoint },
|
|
593
|
+
// Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
|
|
594
|
+
publication: s3ProjectionPublication !== undefined
|
|
595
|
+
? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
|
|
596
|
+
: null,
|
|
553
597
|
prospectusSignIn: prospectusGoogleOnlyClient
|
|
554
598
|
? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
|
|
555
599
|
: null,
|
|
@@ -584,6 +628,9 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
584
628
|
: null,
|
|
585
629
|
},
|
|
586
630
|
});
|
|
631
|
+
wireEditorialChecks(backend);
|
|
632
|
+
wireMonitoringView(backend, emailMonitorId);
|
|
633
|
+
wireAutomationView(backend);
|
|
587
634
|
wirePlugins(backend, plugins, contentActionsLambda);
|
|
588
635
|
return backend;
|
|
589
636
|
}
|
package/amplify/site-backend.ts
CHANGED
|
@@ -14,9 +14,10 @@ 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
|
-
import { assertValidPublicationConfig } from "./site-backend-config";
|
|
20
|
+
import { assertValidMonitoringConfig, assertValidPublicationConfig } from "./site-backend-config";
|
|
20
21
|
import { revalidateSecretReadStatement } from "./revalidate-secret-policy";
|
|
21
22
|
import {
|
|
22
23
|
revalidateDistributionEnvironment,
|
|
@@ -24,12 +25,19 @@ 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 { wireEmailReferenceIntake, 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";
|
|
40
|
+
import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access";
|
|
33
41
|
import { categoryAction } from "./functions/category-action/resource";
|
|
34
42
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
35
43
|
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
|
|
@@ -37,6 +45,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
|
37
45
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
38
46
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
39
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";
|
|
40
51
|
import { procedureAction } from "./functions/procedure-action/resource";
|
|
41
52
|
import { readerSettings } from "./functions/reader-settings/resource";
|
|
42
53
|
import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource";
|
|
@@ -97,6 +108,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
97
108
|
};
|
|
98
109
|
const publication = resolveSitePublication(site, activeBrand?.renderer?.kind);
|
|
99
110
|
if (publication !== undefined) assertValidPublicationConfig(publication);
|
|
111
|
+
assertValidMonitoringConfig(site.monitoring);
|
|
112
|
+
const emailMonitorId = site.monitoring?.emailMonitorId ?? "hn";
|
|
100
113
|
const publicationPlan = planS3ProjectionBackend(publication);
|
|
101
114
|
const renditionsPlan = planRenditions(publication, process.cwd());
|
|
102
115
|
setSiteFilesForContentActionsBundle(publicationPlan.enabled ? publicationPlan.siteFiles : undefined);
|
|
@@ -106,15 +119,19 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
106
119
|
const backend = defineBackend({
|
|
107
120
|
...pluginFunctions,
|
|
108
121
|
assignmentAction,
|
|
122
|
+
automationView,
|
|
109
123
|
auth: defineSiteAuth(authConfig),
|
|
110
124
|
categoryAction,
|
|
111
125
|
contentActions,
|
|
112
|
-
|
|
126
|
+
editorialChecks,
|
|
127
|
+
data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
|
|
113
128
|
knowledgeQuery,
|
|
114
129
|
manageUserRole,
|
|
130
|
+
monitoringView,
|
|
115
131
|
modelAttachmentUpload,
|
|
116
132
|
newsroomSummary,
|
|
117
133
|
procedureAction,
|
|
134
|
+
publicForms,
|
|
118
135
|
readerSettings,
|
|
119
136
|
emailSubmissionProcessor,
|
|
120
137
|
sesInboundReceive,
|
|
@@ -123,6 +140,10 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
123
140
|
storage,
|
|
124
141
|
});
|
|
125
142
|
|
|
143
|
+
if (authConfig.disableGoogleOAuth !== true) {
|
|
144
|
+
applyGoogleEmailVerifiedMapping(backend.auth.stack);
|
|
145
|
+
}
|
|
146
|
+
|
|
126
147
|
orderModelStacksInWaves(
|
|
127
148
|
backend.data.resources.graphqlApi.node.scope as Construct,
|
|
128
149
|
Object.keys(backend.data.resources.tables),
|
|
@@ -387,14 +408,30 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
387
408
|
}
|
|
388
409
|
grantCorporaRead(backend.knowledgeQuery.resources.lambda as LambdaFunction);
|
|
389
410
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
|
|
411
|
+
scopeAssignmentDataAccess(
|
|
412
|
+
backend.assignmentAction.resources.lambda as LambdaFunction,
|
|
413
|
+
backend.data.resources.graphqlApi.arn,
|
|
414
|
+
);
|
|
390
415
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
|
|
391
416
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
|
|
392
417
|
const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
|
|
393
418
|
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
419
|
+
if ((activeBrand?.corpusKey ?? "").trim() !== "") {
|
|
420
|
+
// A deploy creates the publication's canonical corpus from the brand when the backend has none.
|
|
421
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
|
|
422
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
|
|
423
|
+
}
|
|
424
|
+
// AppSync validates the token; pending intake additionally binds its actor
|
|
425
|
+
// and publication to this deployment's pool and existing API endpoint.
|
|
426
|
+
contentActionsLambda.addEnvironment(
|
|
427
|
+
"PAPYRUS_USER_POOL_ISSUER",
|
|
428
|
+
`https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`,
|
|
429
|
+
);
|
|
394
430
|
contentActionsLambda.addEnvironment(
|
|
395
431
|
"PAPYRUS_GRAPHQL_ENDPOINT",
|
|
396
432
|
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
|
|
397
433
|
);
|
|
434
|
+
wireEmailReferenceIntake(contentActionsLambda, emailMonitorId);
|
|
398
435
|
contentActionsLambda.addToRolePolicy(
|
|
399
436
|
new PolicyStatement({
|
|
400
437
|
actions: ["appsync:GraphQL"],
|
|
@@ -430,6 +467,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
430
467
|
}
|
|
431
468
|
if (s3ProjectionPublication !== undefined) {
|
|
432
469
|
addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
|
|
470
|
+
addProjectionLeaseAccess(contentActionsLambda);
|
|
433
471
|
}
|
|
434
472
|
const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
|
|
435
473
|
for (const [name, value] of Object.entries(publicationWiring.environment)) {
|
|
@@ -570,7 +608,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
570
608
|
|
|
571
609
|
const inboundEventStack = Stack.of(receiveLambda);
|
|
572
610
|
|
|
573
|
-
// SES allows one active receipt rule set per region, so a non-legacy site
|
|
611
|
+
// SES allows one active receipt rule set per account and region, so a non-legacy site
|
|
574
612
|
// creates its own brand-named rule set and never activates it here.
|
|
575
613
|
const inboundSesPlan = planInboundEmailSes(siteBackendIdentity, enableInboundEmail, site.inboundEmailSes);
|
|
576
614
|
if (inboundSesPlan.createReceiptRules) {
|
|
@@ -685,8 +723,23 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
685
723
|
}),
|
|
686
724
|
);
|
|
687
725
|
|
|
726
|
+
const publicFormsEndpoint = wirePublicForms(backend, {
|
|
727
|
+
allowedOrigins: derivePublicFormOrigins({
|
|
728
|
+
explicit: site.publicForms?.allowedOrigins,
|
|
729
|
+
hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
|
|
730
|
+
urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
|
|
731
|
+
}),
|
|
732
|
+
honeypotFields: site.publicForms?.honeypotFields,
|
|
733
|
+
});
|
|
734
|
+
|
|
688
735
|
backend.addOutput({
|
|
689
736
|
custom: {
|
|
737
|
+
publicForms: { endpoint: publicFormsEndpoint },
|
|
738
|
+
// Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
|
|
739
|
+
publication:
|
|
740
|
+
s3ProjectionPublication !== undefined
|
|
741
|
+
? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
|
|
742
|
+
: null,
|
|
690
743
|
prospectusSignIn: prospectusGoogleOnlyClient
|
|
691
744
|
? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
|
|
692
745
|
: null,
|
|
@@ -721,6 +774,9 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
721
774
|
: null,
|
|
722
775
|
},
|
|
723
776
|
});
|
|
777
|
+
wireEditorialChecks(backend);
|
|
778
|
+
wireMonitoringView(backend, emailMonitorId);
|
|
779
|
+
wireAutomationView(backend);
|
|
724
780
|
wirePlugins(backend, plugins, contentActionsLambda);
|
|
725
781
|
|
|
726
782
|
return backend;
|
|
@@ -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
|
+
}
|