@anthusai/papyrus 1.0.0-next.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/auth/prospectus-google-client.js +66 -0
- package/amplify/auth/prospectus-google-client.ts +71 -0
- package/amplify/auth/redirect-urls.js +54 -0
- package/amplify/auth/redirect-urls.ts +66 -0
- package/amplify/auth/resource.js +23 -5
- package/amplify/auth/resource.ts +31 -5
- package/amplify/data/resource.js +42 -80
- package/amplify/data/resource.ts +42 -80
- package/amplify/data/schema.js +45 -62
- package/amplify/data/schema.ts +47 -62
- package/amplify/functions/category-action/handler.ts +5 -3
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
- package/amplify/functions/console-chat-responder/resource.js +0 -7
- package/amplify/functions/console-chat-responder/resource.ts +0 -8
- package/amplify/functions/console-chat-responder/src/main.rs +0 -9
- package/amplify/functions/content-actions/handler.py +143 -1
- package/amplify/functions/content-actions/requirements.txt +2 -1
- package/amplify/functions/content-actions/resource.js +6 -1
- package/amplify/functions/content-actions/resource.ts +9 -2
- package/amplify/functions/email-submission-processor/handler.py +3 -1
- package/amplify/functions/renditions/handler.py +120 -0
- package/amplify/functions/renditions/requirements.txt +4 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +32 -4
- package/amplify/functions/shared/email-submission.ts +109 -1
- package/amplify/functions/shared/python-bundle.js +42 -2
- package/amplify/functions/shared/python-bundle.ts +47 -2
- package/amplify/functions/shared/reference-curation-metadata.ts +29 -0
- package/amplify/inbound-email/ses-intake.js +86 -0
- package/amplify/inbound-email/ses-intake.ts +110 -0
- package/amplify/plugins/registry.js +19 -0
- package/amplify/plugins/registry.ts +23 -0
- package/amplify/plugins/signup/function/handler.py +200 -0
- package/amplify/plugins/signup/function/resource.js +27 -0
- package/amplify/plugins/signup/function/resource.ts +35 -0
- package/amplify/plugins/signup/papyrus.plugin.js +59 -0
- package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
- package/amplify/plugins/signup/schema.js +40 -0
- package/amplify/plugins/signup/schema.ts +41 -0
- package/amplify/plugins/signup/waf.js +48 -0
- package/amplify/plugins/signup/waf.ts +51 -0
- package/amplify/plugins/types.js +1 -0
- package/amplify/plugins/types.ts +39 -0
- package/amplify/plugins/wire.js +24 -0
- package/amplify/plugins/wire.ts +26 -0
- package/amplify/publication/default-publication.js +18 -0
- package/amplify/publication/default-publication.ts +20 -0
- package/amplify/publication/invalidation-alarm.js +26 -0
- package/amplify/publication/invalidation-alarm.ts +30 -0
- package/amplify/publication/redirect-and-index.js +37 -0
- package/amplify/publication/renditions-function.js +74 -0
- package/amplify/publication/renditions-function.ts +112 -0
- package/amplify/publication/renditions-settings.js +51 -0
- package/amplify/publication/renditions-settings.ts +58 -0
- package/amplify/publication/reproject-on-deploy.js +73 -0
- package/amplify/publication/reproject-on-deploy.ts +78 -0
- package/amplify/publication/s3-projection-settings.js +67 -0
- package/amplify/publication/s3-projection-settings.ts +101 -0
- package/amplify/publication/s3-projection.js +151 -0
- package/amplify/publication/s3-projection.ts +201 -0
- package/amplify/resolver-waves.js +26 -0
- package/amplify/resolver-waves.ts +34 -0
- package/amplify/revalidate-invalidation-settings.js +30 -0
- package/amplify/revalidate-invalidation-settings.ts +35 -0
- package/amplify/revalidate-secret-policy.js +13 -0
- package/amplify/revalidate-secret-policy.ts +19 -0
- package/amplify/site-backend-config.js +65 -1
- package/amplify/site-backend-config.ts +153 -2
- package/amplify/site-backend-names.js +103 -0
- package/amplify/site-backend-names.ts +166 -0
- package/amplify/site-backend.js +150 -176
- package/amplify/site-backend.ts +189 -201
- package/amplify/storage-backups/construct.js +31 -0
- package/amplify/storage-backups/construct.ts +47 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
- package/app/[year]/[month]/[day]/page.tsx +1 -1
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
- package/app/api/archive/editions/route.ts +1 -1
- package/app/api/auth/prospectus/callback/route.ts +9 -0
- package/app/api/auth/prospectus/selection/route.ts +9 -0
- package/app/api/auth/prospectus/session/route.ts +9 -0
- package/app/api/auth/prospectus/sign-in/route.ts +9 -0
- package/app/api/auth/prospectus/sign-out/route.ts +9 -0
- package/app/api/cyclotron/priming-proof/route.ts +11 -0
- package/app/api/media/[...path]/route.ts +5 -16
- package/app/api/revalidate/route.ts +15 -7
- package/app/archive/page.tsx +1 -1
- package/app/articles/[slug]/page.tsx +7 -5
- package/app/blog-rhythm.css +278 -0
- package/app/cyclotron/page.tsx +13 -0
- package/app/globals.css +2 -0
- package/app/layout.tsx +16 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
- package/app/newsroom/[section]/page.tsx +6 -3
- package/app/newsroom/layout.tsx +4 -9
- package/app/newsroom/settings/page.tsx +6 -0
- package/app/newsroom/work/page.tsx +10 -0
- package/app/page.tsx +22 -8
- package/app/robots.ts +2 -5
- package/app/tailwind.css +1 -6
- package/components/amplify-client-provider.tsx +5 -22
- package/components/archive-shell.tsx +2 -22
- package/components/article-video.tsx +152 -5
- package/components/blog-page-background.tsx +23 -6
- package/components/news-desk-page.tsx +4 -15
- package/components/news-desk-taxonomy-client.ts +90 -3
- package/components/newspaper.tsx +6 -2
- package/components/newsroom-access-gate.tsx +2 -1
- package/components/newsroom-board-view.tsx +99 -0
- package/components/newsroom-client-shell.tsx +12 -0
- package/components/newsroom-curation-provider.tsx +17 -0
- 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 +93 -0
- 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 +93 -0
- package/components/newsroom-newsprint-board.css +48 -0
- package/components/newsroom-ops-overview.tsx +28 -11
- package/components/newsroom-ops-shell.tsx +7 -1
- package/components/newsroom-organizer-shell.tsx +130 -0
- package/components/newsroom-organizer.css +63 -0
- package/components/newsroom-references-view.tsx +92 -8
- package/components/papyrus-console-shell.tsx +4 -2
- package/components/pictogram-figure.tsx +9 -2
- package/components/presentation-header.tsx +135 -0
- package/components/presentation-rhythm-hrule.tsx +17 -0
- package/components/priming-proof-page.tsx +94 -0
- package/components/publication-cyclotron-panel.tsx +57 -0
- package/components/reader-auth-control.tsx +8 -5
- package/components/reference-curation-controls.tsx +77 -0
- package/components/reference-cyclotron-status.tsx +109 -0
- package/components/reference-priming.tsx +18 -0
- package/components/reference-relevance-review.tsx +75 -0
- package/components/settings-page.tsx +2 -1
- package/components/signup-entries-panel.tsx +94 -0
- package/components/topic-steering-workspace.tsx +78 -8
- package/components/use-blog-header-obstacles.ts +81 -0
- package/components/use-brand-fonts-ready.ts +28 -0
- package/components/use-near-viewport.ts +30 -0
- package/components/use-rhythm-overlay.ts +29 -0
- package/infra/amplify-app-shell.js +166 -27
- package/infra/amplify-app-shell.ts +180 -31
- package/infra/build-specs.js +23 -7
- package/infra/build-specs.ts +27 -10
- package/infra/site-config.js +248 -36
- package/infra/site-config.ts +313 -51
- package/lib/amplify-outputs-path.ts +5 -1
- package/lib/amplify-server-runtime.ts +36 -1
- package/lib/approved-priming-proof-reports.ts +6 -0
- package/lib/blog-feature-solver.ts +459 -0
- package/lib/blog-rhythm.ts +134 -0
- package/lib/brand-fonts-watch.ts +43 -0
- package/lib/brand-fonts.ts +112 -0
- package/lib/cached-content-repository.ts +12 -0
- package/lib/category-repository.ts +43 -93
- package/lib/content-item-lifecycle.ts +55 -0
- package/lib/content-repository.ts +3 -0
- package/lib/content-types.ts +4 -0
- package/lib/curation-client.ts +61 -0
- package/lib/edition-routes.ts +31 -29
- package/lib/graphql-content-repository.ts +56 -22
- package/lib/media-route-response.ts +45 -0
- package/lib/newspaper-layout.ts +3 -2
- package/lib/newsroom-base-path.ts +12 -3
- package/lib/newsroom-board.ts +99 -0
- package/lib/newsroom-category-drilldown.ts +3 -2
- package/lib/newsroom-forum-routes.ts +6 -4
- package/lib/newsroom-index-filters.ts +14 -10
- package/lib/newsroom-nav.ts +3 -1
- package/lib/newsroom-organizer-nav.ts +37 -0
- package/lib/newsroom-section-routing.ts +20 -0
- package/lib/oauth-redirect-priority.ts +40 -0
- package/lib/papyrus-web-locations.ts +7 -6
- package/lib/plugin-settings.ts +11 -0
- package/lib/presentation-footer.ts +3 -2
- package/lib/pretext-layout.ts +12 -3
- package/lib/priming-proof-report.ts +90 -0
- package/lib/prospectus-publication-access.ts +53 -0
- package/lib/prospectus-session.ts +196 -0
- package/lib/prospectus-sign-in-routes.ts +284 -0
- package/lib/prospectus-site-identity.ts +303 -0
- package/lib/publication-cyclotron.ts +74 -0
- package/lib/rate-limit-retry.ts +55 -0
- package/lib/reader-base-path.ts +24 -0
- package/lib/reader-cache-lifetime.ts +5 -0
- package/lib/reader-static-params.ts +85 -4
- package/lib/reader-storage-url.ts +9 -12
- package/lib/reference-policy.ts +5 -0
- package/lib/relevance-decisions.ts +103 -0
- package/lib/relevance-status.ts +63 -0
- package/lib/renderer.ts +7 -0
- package/lib/revalidate-prewarm.ts +53 -0
- package/lib/revalidate-secret.ts +59 -0
- package/lib/robots-policy.ts +14 -0
- package/lib/root-route-redirect.ts +16 -0
- package/lib/semantic-graph.ts +7 -0
- package/lib/shared-credentials-provider.ts +61 -0
- package/lib/signup-csv.ts +24 -0
- package/lib/site-brand.ts +59 -0
- package/lib/staging-gated-path.ts +23 -0
- package/lib/staging-preview-object.ts +21 -5
- package/lib/test-editor-override.ts +4 -0
- package/lib/video-loading.ts +22 -0
- package/lib/video-script.ts +14 -0
- package/middleware.ts +11 -19
- package/package.json +73 -8
- package/renderers/pretext/article-page.tsx +39 -3
- package/renderers/pretext/presentation-shared.tsx +113 -0
- package/renderers/pretext/presentation-shell.tsx +15 -102
- package/renderers/pretext/rhythm-article-page.tsx +165 -0
- package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
- package/renderers/pretext/rhythm-shell-style.ts +35 -0
- package/routes.manifest.json +123 -12
- package/src/sync.mjs +30 -6
- package/src/with-papyrus.d.mts +9 -0
- package/src/with-papyrus.mjs +48 -3
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
- package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
- package/components/ai-elements/message.tsx +0 -360
package/amplify/site-backend.ts
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import { defineBackend, secret } from "@aws-amplify/backend";
|
|
2
|
-
import {
|
|
3
|
-
import * as backup from "aws-cdk-lib/aws-backup";
|
|
4
|
-
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
|
|
2
|
+
import { Stack } from "aws-cdk-lib";
|
|
3
|
+
import type * as backup from "aws-cdk-lib/aws-backup";
|
|
5
4
|
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
|
|
6
|
-
import * as events from "aws-cdk-lib/aws-events";
|
|
7
|
-
import * as route53 from "aws-cdk-lib/aws-route53";
|
|
8
|
-
import * as ses from "aws-cdk-lib/aws-ses";
|
|
9
|
-
import * as sesActions from "aws-cdk-lib/aws-ses-actions";
|
|
10
5
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
11
6
|
import { CfnEventSourceMapping, CfnFunction, Function as LambdaFunction, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
|
|
12
7
|
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
13
8
|
import { CfnIndex, CfnVectorBucket, CfnVectorBucketPolicy } from "aws-cdk-lib/aws-s3vectors";
|
|
9
|
+
import type { Construct } from "constructs";
|
|
14
10
|
import { dirname, resolve } from "node:path";
|
|
15
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { DEFAULT_RESOLVER_WAVE_SIZE, orderModelStacksInWaves } from "./resolver-waves";
|
|
13
|
+
import { resolveSitePublication } from "./publication/default-publication";
|
|
14
|
+
import { REPROJECT_ON_DEPLOY_TIMEOUT_SECONDS, addReprojectOnDeploy, reprojectDeployToken } from "./publication/reproject-on-deploy";
|
|
15
|
+
import { addProjectionRetries, addS3ProjectionPublication, type S3ProjectionPublication } from "./publication/s3-projection";
|
|
16
|
+
import { planRenditions } from "./publication/renditions-settings";
|
|
17
|
+
import { contentActionsPublicationWiring, planS3ProjectionBackend } from "./publication/s3-projection-settings";
|
|
16
18
|
import { rebuildTriggerSettings } from "./rebuild-trigger-settings";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
+
import { assertValidPublicationConfig } from "./site-backend-config";
|
|
20
|
+
import { revalidateSecretReadStatement } from "./revalidate-secret-policy";
|
|
21
|
+
import {
|
|
22
|
+
revalidateDistributionEnvironment,
|
|
23
|
+
revalidateInvalidationStatement,
|
|
24
|
+
validateRevalidateDistributionIds,
|
|
25
|
+
} from "./revalidate-invalidation-settings";
|
|
26
|
+
import { addInvalidationPathAlarm } from "./publication/invalidation-alarm";
|
|
27
|
+
import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource";
|
|
28
|
+
import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client";
|
|
29
|
+
import { defineSiteData } from "./data/resource";
|
|
30
|
+
import { resolvePlugins } from "./plugins/registry";
|
|
31
|
+
import { wirePlugins } from "./plugins/wire";
|
|
19
32
|
import { assignmentAction } from "./functions/assignment-action/resource";
|
|
20
33
|
import { categoryAction } from "./functions/category-action/resource";
|
|
21
34
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
22
|
-
import {
|
|
23
|
-
import { contentActions } from "./functions/content-actions/resource";
|
|
35
|
+
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
|
|
24
36
|
import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
25
37
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
26
38
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
@@ -31,7 +43,19 @@ import { emailSubmissionProcessor } from "./functions/email-submission-processor
|
|
|
31
43
|
import { sesInboundReceive } from "./functions/ses-inbound-receive/resource";
|
|
32
44
|
import { slackDelivery } from "./functions/slack-delivery/resource";
|
|
33
45
|
import { slackEvents } from "./functions/slack-events/resource";
|
|
46
|
+
import { addInboundEmailSesIntake } from "./inbound-email/ses-intake";
|
|
34
47
|
import { InboundEmailStack } from "./inbound-email/stack";
|
|
48
|
+
import {
|
|
49
|
+
deriveKnowledgeVectorIndexName,
|
|
50
|
+
deriveReceiptRuleName,
|
|
51
|
+
deriveReceiptRuleSetName,
|
|
52
|
+
deriveStorageBackupVaultName,
|
|
53
|
+
isLegacyProductionPipeline,
|
|
54
|
+
planInboundEmailSes,
|
|
55
|
+
resolveSiteBackendFeatureFlags,
|
|
56
|
+
type SiteBackendIdentity,
|
|
57
|
+
} from "./site-backend-names";
|
|
58
|
+
import { addStorageBackups } from "./storage-backups/construct";
|
|
35
59
|
import { storage } from "./storage/resource";
|
|
36
60
|
import type { PapyrusSite } from "../lib/define-site";
|
|
37
61
|
|
|
@@ -44,57 +68,48 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
44
68
|
|
|
45
69
|
const amplifyBranch = (process.env.AWS_BRANCH ?? "").trim();
|
|
46
70
|
const amplifyAppId = (process.env.AWS_APP_ID ?? "").trim();
|
|
47
|
-
// Amplify app id whose `main` branch is
|
|
48
|
-
//
|
|
71
|
+
// Amplify app id whose `main` branch is the original p.apyr.us backend; only
|
|
72
|
+
// that app keeps the historical account-global names (see site-backend-names.ts).
|
|
49
73
|
const productionAppId = (site.productionAppId ?? "").trim();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
: `papyrus-knowledge-${sanitizeAwsName(papyrusSiteBrand, 40)}`;
|
|
83
|
-
|
|
84
|
-
// SES active receipt rule sets and shared backup vault names are account-global.
|
|
85
|
-
// Keep them on the production pipeline only unless explicitly opted in.
|
|
86
|
-
// A feature flag in the site config wins over the environment.
|
|
87
|
-
const enableInboundEmail = site.features?.inboundEmail ?? readEnvFlag("PAPYRUS_ENABLE_INBOUND_EMAIL", isAmplifyProductionPipeline);
|
|
88
|
-
const enableSlackAgent = site.features?.slack ?? readEnvFlag("PAPYRUS_ENABLE_SLACK", false);
|
|
89
|
-
const enableStorageBackups = site.features?.storageBackups ?? readEnvFlag("PAPYRUS_ENABLE_STORAGE_BACKUPS", isAmplifyProductionPipeline);
|
|
90
|
-
|
|
74
|
+
const siteBackendIdentity: SiteBackendIdentity = {
|
|
75
|
+
brandId: (
|
|
76
|
+
site.brandId
|
|
77
|
+
?? process.env.PAPYRUS_SITE_BRAND
|
|
78
|
+
?? process.env.NEXT_PUBLIC_PAPYRUS_SITE_BRAND
|
|
79
|
+
?? "papyrus"
|
|
80
|
+
).trim().toLowerCase(),
|
|
81
|
+
amplifyAppId,
|
|
82
|
+
amplifyBranch,
|
|
83
|
+
productionAppId,
|
|
84
|
+
};
|
|
85
|
+
const isAmplifyProductionPipeline = isLegacyProductionPipeline(siteBackendIdentity);
|
|
86
|
+
const knowledgeVectorIndexName = deriveKnowledgeVectorIndexName(siteBackendIdentity);
|
|
87
|
+
const featureFlags = resolveSiteBackendFeatureFlags(site.features, process.env, siteBackendIdentity);
|
|
88
|
+
const enableInboundEmail = featureFlags.inboundEmail;
|
|
89
|
+
const enableSlackAgent = featureFlags.slack;
|
|
90
|
+
const enableStorageBackups = featureFlags.storageBackups;
|
|
91
|
+
|
|
92
|
+
const baseAuthConfig = site.auth ?? authConfigFromEnv();
|
|
93
|
+
const activeBrand = (papyrusSite.brands ?? []).find((brand) => brand.id.toLowerCase() === siteBackendIdentity.brandId);
|
|
94
|
+
const authConfig = {
|
|
95
|
+
...baseAuthConfig,
|
|
96
|
+
newsroomBasePath: baseAuthConfig.newsroomBasePath ?? activeBrand?.newsroomBasePath,
|
|
97
|
+
};
|
|
98
|
+
const publication = resolveSitePublication(site, activeBrand?.renderer?.kind);
|
|
99
|
+
if (publication !== undefined) assertValidPublicationConfig(publication);
|
|
100
|
+
const publicationPlan = planS3ProjectionBackend(publication);
|
|
101
|
+
const renditionsPlan = planRenditions(publication, process.cwd());
|
|
102
|
+
setSiteFilesForContentActionsBundle(publicationPlan.enabled ? publicationPlan.siteFiles : undefined);
|
|
103
|
+
const plugins = resolvePlugins(site.plugins);
|
|
104
|
+
const pluginDefinitions = Object.assign({}, ...plugins.map(({ plugin }) => plugin.schemaDefinitions()));
|
|
105
|
+
const pluginFunctions: Record<never, never> = Object.assign({}, ...plugins.map(({ plugin }) => plugin.functions()));
|
|
91
106
|
const backend = defineBackend({
|
|
107
|
+
...pluginFunctions,
|
|
92
108
|
assignmentAction,
|
|
93
|
-
auth: defineSiteAuth(
|
|
109
|
+
auth: defineSiteAuth(authConfig),
|
|
94
110
|
categoryAction,
|
|
95
111
|
contentActions,
|
|
96
|
-
data,
|
|
97
|
-
graphqlJwtAuthorizer,
|
|
112
|
+
data: defineSiteData(pluginDefinitions),
|
|
98
113
|
knowledgeQuery,
|
|
99
114
|
manageUserRole,
|
|
100
115
|
modelAttachmentUpload,
|
|
@@ -108,10 +123,29 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
108
123
|
storage,
|
|
109
124
|
});
|
|
110
125
|
|
|
126
|
+
orderModelStacksInWaves(
|
|
127
|
+
backend.data.resources.graphqlApi.node.scope as Construct,
|
|
128
|
+
Object.keys(backend.data.resources.tables),
|
|
129
|
+
site.resolverWaveSize ?? DEFAULT_RESOLVER_WAVE_SIZE,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const requestedCognitoDomainPrefix = (authConfig.cognitoDomainPrefix ?? "").trim();
|
|
133
|
+
if (
|
|
134
|
+
authConfig.applyCognitoDomainPrefix === true
|
|
135
|
+
&& requestedCognitoDomainPrefix !== ""
|
|
136
|
+
&& authConfig.disableGoogleOAuth !== true
|
|
137
|
+
) {
|
|
138
|
+
applyCognitoDomainPrefix(backend.auth.stack, requestedCognitoDomainPrefix);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const prospectusGoogleOnlyClient = authConfig.disableGoogleOAuth === true
|
|
142
|
+
? undefined
|
|
143
|
+
: addProspectusGoogleOnlyClient(backend.auth.stack, backend.auth.resources.userPool, siteAuthRedirectUrls(authConfig));
|
|
144
|
+
|
|
111
145
|
const amplifyBackendDir = dirname(fileURLToPath(import.meta.url));
|
|
112
146
|
// Repo root in a Papyrus checkout; package root when installed from npm.
|
|
113
147
|
const projectRoot = resolve(amplifyBackendDir, "..");
|
|
114
|
-
const enableConsoleResponder =
|
|
148
|
+
const enableConsoleResponder = featureFlags.consoleResponder;
|
|
115
149
|
|
|
116
150
|
if (
|
|
117
151
|
enableConsoleResponder
|
|
@@ -160,43 +194,19 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
160
194
|
const messageThreadTable = backend.data.resources.tables.MessageThread;
|
|
161
195
|
const dataStack = Stack.of(messageTable);
|
|
162
196
|
const graphqlEndpoint = backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl;
|
|
163
|
-
const
|
|
164
|
-
const jwtAuthorizerEnvironment = jwtAuthorizerCfn?.environment;
|
|
165
|
-
const jwtAuthorizerVariables =
|
|
166
|
-
jwtAuthorizerEnvironment
|
|
167
|
-
&& typeof jwtAuthorizerEnvironment === "object"
|
|
168
|
-
&& "variables" in jwtAuthorizerEnvironment
|
|
169
|
-
? (jwtAuthorizerEnvironment as { variables?: Record<string, string> }).variables
|
|
170
|
-
: undefined;
|
|
171
|
-
const jwtSsmEnvConfig =
|
|
172
|
-
process.env.AMPLIFY_SSM_ENV_CONFIG?.trim()
|
|
173
|
-
|| jwtAuthorizerVariables?.AMPLIFY_SSM_ENV_CONFIG?.trim()
|
|
174
|
-
|| "";
|
|
197
|
+
const amplifySsmEnvConfig = process.env.AMPLIFY_SSM_ENV_CONFIG?.trim() || "";
|
|
175
198
|
|
|
176
199
|
if (enableConsoleResponder) {
|
|
177
|
-
|
|
200
|
+
new ConsoleChatResponderStack(dataStack, "ConsoleChatResponder", {
|
|
178
201
|
messageTable,
|
|
179
202
|
messageStreamArn,
|
|
180
203
|
threadTable: messageThreadTable,
|
|
181
204
|
projectRoot,
|
|
182
205
|
graphqlEndpoint,
|
|
183
|
-
amplifySsmEnvConfig: jwtSsmEnvConfig || undefined,
|
|
184
206
|
responseTarget: process.env.PAPYRUS_CONSOLE_RESPONSE_TARGET,
|
|
185
207
|
model: process.env.PAPYRUS_CONSOLE_MODEL,
|
|
186
208
|
prebuiltImageUri: process.env.PAPYRUS_CONSOLE_RESPONDER_IMAGE_URI,
|
|
187
209
|
});
|
|
188
|
-
if (jwtSsmEnvConfig) {
|
|
189
|
-
consoleChatResponder.responderFunction.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", jwtSsmEnvConfig);
|
|
190
|
-
}
|
|
191
|
-
const jwtSecretSsmParam =
|
|
192
|
-
process.env.PAPYRUS_JWT_SECRET_SSM_PARAM?.trim()
|
|
193
|
-
|| (productionAppId !== "" && amplifyAppId === productionAppId
|
|
194
|
-
? `/amplify/${amplifyAppId}/main-branch-cb38ada667/PAPYRUS_JWT_SECRET`
|
|
195
|
-
: "/amplify/papyrus/ryan-sandbox-adcd88a186/PAPYRUS_JWT_SECRET");
|
|
196
|
-
consoleChatResponder.responderFunction.addEnvironment(
|
|
197
|
-
"PAPYRUS_JWT_SECRET_SSM_PARAM",
|
|
198
|
-
jwtSecretSsmParam,
|
|
199
|
-
);
|
|
200
210
|
}
|
|
201
211
|
|
|
202
212
|
if (enableSlackAgent) {
|
|
@@ -217,9 +227,9 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
217
227
|
"PAPYRUS_SLACK_ALLOWED_USER_IDS",
|
|
218
228
|
(process.env.PAPYRUS_SLACK_ALLOWED_USER_IDS ?? "").trim(),
|
|
219
229
|
);
|
|
220
|
-
if (
|
|
221
|
-
backend.slackEvents.addEnvironment("AMPLIFY_SSM_ENV_CONFIG",
|
|
222
|
-
backend.slackDelivery.addEnvironment("AMPLIFY_SSM_ENV_CONFIG",
|
|
230
|
+
if (amplifySsmEnvConfig) {
|
|
231
|
+
backend.slackEvents.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", amplifySsmEnvConfig);
|
|
232
|
+
backend.slackDelivery.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", amplifySsmEnvConfig);
|
|
223
233
|
}
|
|
224
234
|
backend.slackDelivery.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", graphqlEndpoint);
|
|
225
235
|
backend.slackDelivery.addEnvironment(
|
|
@@ -297,8 +307,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
297
307
|
let storageBackupVault: backup.BackupVault | undefined;
|
|
298
308
|
|
|
299
309
|
const storageBucketCfn = storageBucket.node.defaultChild as s3.CfnBucket | undefined;
|
|
300
|
-
if ((enableStorageBackups || enableInboundEmail) && storageBucketCfn) {
|
|
301
|
-
// Backups
|
|
310
|
+
if ((enableStorageBackups || enableInboundEmail || renditionsPlan.enabled) && storageBucketCfn) {
|
|
311
|
+
// Backups, inbound-email intake and upload-time renditions all rely on S3 → EventBridge notifications.
|
|
302
312
|
storageBucketCfn.addPropertyOverride(
|
|
303
313
|
"NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled",
|
|
304
314
|
true,
|
|
@@ -307,41 +317,17 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
307
317
|
|
|
308
318
|
if (enableStorageBackups) {
|
|
309
319
|
const storageBackupsStack = backend.createStack("storage-backups");
|
|
310
|
-
const storageBackupVaultName =
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
? `papyrus-${productionAppId}-main-media-backup-vault`
|
|
314
|
-
: `papyrus-${sanitizeAwsName(storageBackupsStack.stackName, 32)}-media-vault`),
|
|
320
|
+
const storageBackupVaultName = deriveStorageBackupVaultName(
|
|
321
|
+
siteBackendIdentity,
|
|
322
|
+
process.env.PAPYRUS_STORAGE_BACKUP_VAULT_NAME,
|
|
315
323
|
);
|
|
316
324
|
|
|
317
|
-
|
|
325
|
+
const storageBackups = addStorageBackups(storageBackupsStack, {
|
|
326
|
+
storageBucket,
|
|
318
327
|
backupVaultName: storageBackupVaultName,
|
|
319
|
-
removalPolicy: RemovalPolicy.RETAIN,
|
|
320
|
-
});
|
|
321
|
-
storageBackupPlan = new backup.BackupPlan(storageBackupsStack, "PapyrusStorageBackupPlan", {
|
|
322
|
-
backupVault: storageBackupVault,
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
storageBackupPlan.addRule(
|
|
326
|
-
new backup.BackupPlanRule({
|
|
327
|
-
ruleName: "papyrus-storage-pitr-35d",
|
|
328
|
-
enableContinuousBackup: true,
|
|
329
|
-
deleteAfter: Duration.days(35),
|
|
330
|
-
}),
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
storageBackupPlan.addRule(
|
|
334
|
-
new backup.BackupPlanRule({
|
|
335
|
-
ruleName: "papyrus-storage-daily-365d",
|
|
336
|
-
scheduleExpression: events.Schedule.cron({ minute: "0", hour: "5" }),
|
|
337
|
-
deleteAfter: Duration.days(365),
|
|
338
|
-
}),
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
storageBackupPlan.addSelection("PapyrusStorageBackupSelection", {
|
|
342
|
-
allowRestores: true,
|
|
343
|
-
resources: [backup.BackupResource.fromArn(storageBucket.bucketArn)],
|
|
344
328
|
});
|
|
329
|
+
storageBackupVault = storageBackups.vault;
|
|
330
|
+
storageBackupPlan = storageBackups.plan;
|
|
345
331
|
}
|
|
346
332
|
|
|
347
333
|
// Grant S3 access on Lambda roles only (not storage bucket policies) to avoid
|
|
@@ -430,9 +416,66 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
430
416
|
new PolicyStatement({ actions: rebuildSettings.actions, resources: rebuildSettings.resources }),
|
|
431
417
|
);
|
|
432
418
|
}
|
|
419
|
+
let s3ProjectionPublication: S3ProjectionPublication | undefined;
|
|
420
|
+
if (publication !== undefined) {
|
|
421
|
+
s3ProjectionPublication = addS3ProjectionPublication(backend.createStack("s3-projection"), publication, {
|
|
422
|
+
mediaBucket: storageBucket,
|
|
423
|
+
renditions: renditionsPlan,
|
|
424
|
+
});
|
|
425
|
+
(contentActionsLambda.node.defaultChild as CfnFunction).memorySize = publicationPlan.contentActionsMemorySize;
|
|
426
|
+
if (publication.reprojectOnDeploy === true) {
|
|
427
|
+
(contentActionsLambda.node.defaultChild as CfnFunction).timeout = REPROJECT_ON_DEPLOY_TIMEOUT_SECONDS;
|
|
428
|
+
addReprojectOnDeploy(backend.createStack("reproject-on-deploy"), contentActionsLambda, reprojectDeployToken(process.env));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (s3ProjectionPublication !== undefined) {
|
|
432
|
+
addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
|
|
433
|
+
}
|
|
434
|
+
const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
|
|
435
|
+
for (const [name, value] of Object.entries(publicationWiring.environment)) {
|
|
436
|
+
contentActionsLambda.addEnvironment(name, value);
|
|
437
|
+
}
|
|
438
|
+
for (const statement of publicationWiring.policyStatements) {
|
|
439
|
+
contentActionsLambda.addToRolePolicy(statement);
|
|
440
|
+
}
|
|
433
441
|
if ((site.revalidateBaseUrl ?? "").trim() !== "") {
|
|
434
442
|
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_BASE_URL", (site.revalidateBaseUrl ?? "").trim());
|
|
435
|
-
|
|
443
|
+
const revalidateSecretParameter = (process.env.PAPYRUS_REVALIDATE_SECRET_PARAMETER ?? "").trim();
|
|
444
|
+
if (!revalidateSecretParameter.startsWith("/")) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
"revalidateBaseUrl is set but PAPYRUS_REVALIDATE_SECRET_PARAMETER is not (an SSM parameter name starting with /). The app-shell template sets it on the production branch for Pretext sites; see docs/site-hosting.md.",
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_SECRET_PARAMETER", revalidateSecretParameter);
|
|
450
|
+
const revalidateStack = Stack.of(contentActionsLambda);
|
|
451
|
+
contentActionsLambda.addToRolePolicy(
|
|
452
|
+
new PolicyStatement(
|
|
453
|
+
revalidateSecretReadStatement({
|
|
454
|
+
partition: revalidateStack.partition,
|
|
455
|
+
region: revalidateStack.region,
|
|
456
|
+
account: revalidateStack.account,
|
|
457
|
+
parameterName: revalidateSecretParameter,
|
|
458
|
+
}),
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
const distributionProblems = validateRevalidateDistributionIds(site.revalidateDistributionIds, site.revalidateBaseUrl);
|
|
462
|
+
if (distributionProblems.length > 0) throw new Error(`Invalid backend config: ${distributionProblems.join("; ")}`);
|
|
463
|
+
const distributionIds = site.revalidateDistributionIds ?? [];
|
|
464
|
+
if (distributionIds.length > 0) {
|
|
465
|
+
for (const [name, value] of Object.entries(revalidateDistributionEnvironment(distributionIds))) {
|
|
466
|
+
backend.contentActions.addEnvironment(name, value);
|
|
467
|
+
}
|
|
468
|
+
contentActionsLambda.addToRolePolicy(
|
|
469
|
+
new PolicyStatement(
|
|
470
|
+
revalidateInvalidationStatement({ partition: revalidateStack.partition, account: revalidateStack.account, distributionIds }),
|
|
471
|
+
),
|
|
472
|
+
);
|
|
473
|
+
for (const id of distributionIds) {
|
|
474
|
+
addInvalidationPathAlarm(revalidateStack, `ReaderEdgeInvalidationPaths${id}`, id);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} else if (site.revalidateDistributionIds !== undefined) {
|
|
478
|
+
throw new Error("Invalid backend config: revalidateDistributionIds requires revalidateBaseUrl");
|
|
436
479
|
}
|
|
437
480
|
grantNewsroomReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
438
481
|
grantMediaReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
@@ -527,86 +570,26 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
527
570
|
|
|
528
571
|
const inboundEventStack = Stack.of(receiveLambda);
|
|
529
572
|
|
|
530
|
-
// SES receipt rule
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
},
|
|
551
|
-
physicalResourceId: PhysicalResourceId.of(`ses-domain-verify-${inboundEmailDomain}`),
|
|
552
|
-
},
|
|
553
|
-
policy: AwsCustomResourcePolicy.fromSdkCalls({
|
|
554
|
-
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
|
|
555
|
-
}),
|
|
556
|
-
timeout: Duration.minutes(2),
|
|
557
|
-
});
|
|
558
|
-
new route53.TxtRecord(storageStack, "PapyrusInboundEmailDomainVerification", {
|
|
559
|
-
zone: inboundDnsZone,
|
|
560
|
-
recordName: `_amazonses.${inboundDnsRecordName}`,
|
|
561
|
-
ttl: Duration.minutes(5),
|
|
562
|
-
values: [verifyInboundEmailDomain.getResponseField("VerificationToken")],
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
const inboundRuleSet = new ses.ReceiptRuleSet(storageStack, "PapyrusInboundEmailRuleSet", {
|
|
566
|
-
receiptRuleSetName: `papyrus-inbound-${inboundEmailDomain.replace(/\./g, "-")}`,
|
|
567
|
-
});
|
|
568
|
-
inboundRuleSet.addRule("PapyrusInboundSubmissions", {
|
|
569
|
-
recipients: inboundRecipients,
|
|
570
|
-
enabled: true,
|
|
571
|
-
scanEnabled: true,
|
|
572
|
-
tlsPolicy: ses.TlsPolicy.REQUIRE,
|
|
573
|
-
actions: [
|
|
574
|
-
new sesActions.S3({
|
|
575
|
-
bucket: storageBucket,
|
|
576
|
-
objectKeyPrefix: "inbound-email/",
|
|
577
|
-
}),
|
|
578
|
-
],
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
new AwsCustomResource(storageStack, "ActivateInboundEmailRuleSet", {
|
|
582
|
-
onCreate: {
|
|
583
|
-
service: "SES",
|
|
584
|
-
action: "setActiveReceiptRuleSet",
|
|
585
|
-
parameters: {
|
|
586
|
-
RuleSetName: inboundRuleSet.receiptRuleSetName,
|
|
587
|
-
},
|
|
588
|
-
physicalResourceId: PhysicalResourceId.of(`activate-${inboundRuleSet.receiptRuleSetName}`),
|
|
589
|
-
},
|
|
590
|
-
onUpdate: {
|
|
591
|
-
service: "SES",
|
|
592
|
-
action: "setActiveReceiptRuleSet",
|
|
593
|
-
parameters: {
|
|
594
|
-
RuleSetName: inboundRuleSet.receiptRuleSetName,
|
|
595
|
-
},
|
|
596
|
-
physicalResourceId: PhysicalResourceId.of(`activate-${inboundRuleSet.receiptRuleSetName}`),
|
|
597
|
-
},
|
|
598
|
-
onDelete: {
|
|
599
|
-
service: "SES",
|
|
600
|
-
action: "setActiveReceiptRuleSet",
|
|
601
|
-
parameters: {
|
|
602
|
-
RuleSetName: null,
|
|
603
|
-
},
|
|
604
|
-
},
|
|
605
|
-
policy: AwsCustomResourcePolicy.fromSdkCalls({
|
|
606
|
-
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
|
|
607
|
-
}),
|
|
608
|
-
timeout: Duration.minutes(2),
|
|
609
|
-
});
|
|
573
|
+
// SES allows one active receipt rule set per region, so a non-legacy site
|
|
574
|
+
// creates its own brand-named rule set and never activates it here.
|
|
575
|
+
const inboundSesPlan = planInboundEmailSes(siteBackendIdentity, enableInboundEmail, site.inboundEmailSes);
|
|
576
|
+
if (inboundSesPlan.createReceiptRules) {
|
|
577
|
+
addInboundEmailSesIntake(storageStack, {
|
|
578
|
+
storageBucket,
|
|
579
|
+
recipients: inboundRecipients,
|
|
580
|
+
ruleSetName: deriveReceiptRuleSetName(siteBackendIdentity, inboundEmailDomain),
|
|
581
|
+
existingRuleSetName: site.inboundEmailSes?.existingReceiptRuleSetName,
|
|
582
|
+
ruleName: deriveReceiptRuleName(siteBackendIdentity),
|
|
583
|
+
activateRuleSet: inboundSesPlan.activateReceiptRuleSet,
|
|
584
|
+
domainIdentity: inboundSesPlan.manageDomainIdentity
|
|
585
|
+
? {
|
|
586
|
+
domain: inboundEmailDomain,
|
|
587
|
+
hostedZoneId: inboundDnsZoneId,
|
|
588
|
+
hostedZoneName: inboundDnsZoneName,
|
|
589
|
+
recordName: inboundDnsRecordName,
|
|
590
|
+
}
|
|
591
|
+
: undefined,
|
|
592
|
+
});
|
|
610
593
|
}
|
|
611
594
|
|
|
612
595
|
new InboundEmailStack(inboundEventStack, "InboundEmail", {
|
|
@@ -704,6 +687,9 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
704
687
|
|
|
705
688
|
backend.addOutput({
|
|
706
689
|
custom: {
|
|
690
|
+
prospectusSignIn: prospectusGoogleOnlyClient
|
|
691
|
+
? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
|
|
692
|
+
: null,
|
|
707
693
|
inboundEmail: enableInboundEmail
|
|
708
694
|
? {
|
|
709
695
|
domain: inboundEmailDomain,
|
|
@@ -735,5 +721,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
735
721
|
: null,
|
|
736
722
|
},
|
|
737
723
|
});
|
|
724
|
+
wirePlugins(backend, plugins, contentActionsLambda);
|
|
725
|
+
|
|
738
726
|
return backend;
|
|
739
727
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Duration, RemovalPolicy } from "aws-cdk-lib";
|
|
2
|
+
import * as backup from "aws-cdk-lib/aws-backup";
|
|
3
|
+
import * as events from "aws-cdk-lib/aws-events";
|
|
4
|
+
/**
|
|
5
|
+
* Adds the resources directly to `scope` (no wrapper construct) so the logical
|
|
6
|
+
* ids of the live p.apyr.us backup stack stay unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export function addStorageBackups(scope, props) {
|
|
9
|
+
const vault = new backup.BackupVault(scope, "PapyrusStorageBackupVault", {
|
|
10
|
+
backupVaultName: props.backupVaultName,
|
|
11
|
+
removalPolicy: RemovalPolicy.RETAIN,
|
|
12
|
+
});
|
|
13
|
+
const plan = new backup.BackupPlan(scope, "PapyrusStorageBackupPlan", {
|
|
14
|
+
backupVault: vault,
|
|
15
|
+
});
|
|
16
|
+
plan.addRule(new backup.BackupPlanRule({
|
|
17
|
+
ruleName: "papyrus-storage-pitr-35d",
|
|
18
|
+
enableContinuousBackup: true,
|
|
19
|
+
deleteAfter: Duration.days(35),
|
|
20
|
+
}));
|
|
21
|
+
plan.addRule(new backup.BackupPlanRule({
|
|
22
|
+
ruleName: "papyrus-storage-daily-365d",
|
|
23
|
+
scheduleExpression: events.Schedule.cron({ minute: "0", hour: "5" }),
|
|
24
|
+
deleteAfter: Duration.days(365),
|
|
25
|
+
}));
|
|
26
|
+
plan.addSelection("PapyrusStorageBackupSelection", {
|
|
27
|
+
allowRestores: true,
|
|
28
|
+
resources: [backup.BackupResource.fromArn(props.storageBucket.bucketArn)],
|
|
29
|
+
});
|
|
30
|
+
return { plan, vault };
|
|
31
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Duration, RemovalPolicy } from "aws-cdk-lib";
|
|
2
|
+
import * as backup from "aws-cdk-lib/aws-backup";
|
|
3
|
+
import * as events from "aws-cdk-lib/aws-events";
|
|
4
|
+
import type * as s3 from "aws-cdk-lib/aws-s3";
|
|
5
|
+
import type { Construct } from "constructs";
|
|
6
|
+
|
|
7
|
+
export type StorageBackupsProps = {
|
|
8
|
+
storageBucket: s3.IBucket;
|
|
9
|
+
backupVaultName: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Adds the resources directly to `scope` (no wrapper construct) so the logical
|
|
14
|
+
* ids of the live p.apyr.us backup stack stay unchanged.
|
|
15
|
+
*/
|
|
16
|
+
export function addStorageBackups(scope: Construct, props: StorageBackupsProps) {
|
|
17
|
+
const vault = new backup.BackupVault(scope, "PapyrusStorageBackupVault", {
|
|
18
|
+
backupVaultName: props.backupVaultName,
|
|
19
|
+
removalPolicy: RemovalPolicy.RETAIN,
|
|
20
|
+
});
|
|
21
|
+
const plan = new backup.BackupPlan(scope, "PapyrusStorageBackupPlan", {
|
|
22
|
+
backupVault: vault,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
plan.addRule(
|
|
26
|
+
new backup.BackupPlanRule({
|
|
27
|
+
ruleName: "papyrus-storage-pitr-35d",
|
|
28
|
+
enableContinuousBackup: true,
|
|
29
|
+
deleteAfter: Duration.days(35),
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
plan.addRule(
|
|
34
|
+
new backup.BackupPlanRule({
|
|
35
|
+
ruleName: "papyrus-storage-daily-365d",
|
|
36
|
+
scheduleExpression: events.Schedule.cron({ minute: "0", hour: "5" }),
|
|
37
|
+
deleteAfter: Duration.days(365),
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
plan.addSelection("PapyrusStorageBackupSelection", {
|
|
42
|
+
allowRestores: true,
|
|
43
|
+
resources: [backup.BackupResource.fromArn(props.storageBucket.bucketArn)],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return { plan, vault };
|
|
47
|
+
}
|
|
@@ -4,8 +4,13 @@ import { getCachedEditionItem, loadCachedEditionContent } from "../../../../../l
|
|
|
4
4
|
import { getEditionDatePath, parseEditionArticleRoute } from "../../../../../lib/edition-routes";
|
|
5
5
|
import { buildPresentationFooterEntries } from "../../../../../lib/presentation-footer";
|
|
6
6
|
import { SITE_BRAND } from "../../../../../lib/site-brand";
|
|
7
|
+
import { generateEditionItemStaticParams } from "../../../../../lib/reader-static-params";
|
|
7
8
|
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
8
|
-
export const revalidate =
|
|
9
|
+
export const revalidate = 60;
|
|
10
|
+
|
|
11
|
+
export async function generateStaticParams() {
|
|
12
|
+
return generateEditionItemStaticParams();
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
type DateScopedArticlePageProps = {
|
|
11
16
|
params: Promise<{
|
|
@@ -52,10 +57,12 @@ export default async function DateScopedArticlePage({ params }: DateScopedArticl
|
|
|
52
57
|
editionFooter={{
|
|
53
58
|
editionBasePath,
|
|
54
59
|
entries: buildPresentationFooterEntries(content),
|
|
60
|
+
sections: content.sections.map((section) => ({ key: section.key, label: section.label })),
|
|
55
61
|
subtitle: SITE_BRAND.footerSubtitleOverride ?? (content.description?.trim() || "Inside Papyrus"),
|
|
56
62
|
title: SITE_BRAND.footerTitle,
|
|
57
63
|
}}
|
|
58
64
|
item={item}
|
|
65
|
+
videoScript={content.videoScripts?.[item.slug] ?? null}
|
|
59
66
|
backHref={`${editionBasePath}#${item.slug}`}
|
|
60
67
|
/>
|
|
61
68
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { siteServesNextReaderRoutes } from "../../../../lib/reader-static-params";
|
|
2
3
|
import { getSiteRenderer } from "../../../../lib/site-renderer";
|
|
3
4
|
import type { PresentationTarget } from "../../../../renderers/pretext";
|
|
4
5
|
import { loadCachedEditionContent } from "../../../../lib/cached-content-repository";
|
|
@@ -22,6 +23,7 @@ export async function EditionRoutePage({
|
|
|
22
23
|
lockedPresentation,
|
|
23
24
|
target = { kind: "edition" },
|
|
24
25
|
}: EditionRoutePageProps) {
|
|
26
|
+
if (!siteServesNextReaderRoutes()) notFound();
|
|
25
27
|
const route = parseEditionDateRoute({ year, month, day });
|
|
26
28
|
if (!route) notFound();
|
|
27
29
|
if (!route.isCanonical) redirect(route.canonicalPath);
|
|
@@ -2,8 +2,13 @@ import { notFound, redirect } from "next/navigation";
|
|
|
2
2
|
import { EditionRoutePage } from "../../edition-route-page";
|
|
3
3
|
import { parseEditionPageRoute } from "../../../../../../lib/edition-routes";
|
|
4
4
|
import { getDefaultPretextLayout, getForcedPresentation } from "../../../../../../lib/site-brand";
|
|
5
|
+
import { generateEditionPageStaticParams } from "../../../../../../lib/reader-static-params";
|
|
5
6
|
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
6
|
-
export const revalidate =
|
|
7
|
+
export const revalidate = 60;
|
|
8
|
+
|
|
9
|
+
export async function generateStaticParams() {
|
|
10
|
+
return generateEditionPageStaticParams();
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
type EditionPageRouteProps = {
|
|
9
14
|
params: Promise<{
|
|
@@ -2,7 +2,7 @@ import { EditionRoutePage } from "./edition-route-page";
|
|
|
2
2
|
import { generateEditionDateStaticParams } from "../../../../lib/reader-static-params";
|
|
3
3
|
|
|
4
4
|
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
5
|
-
export const revalidate =
|
|
5
|
+
export const revalidate = 60;
|
|
6
6
|
|
|
7
7
|
export async function generateStaticParams() {
|
|
8
8
|
return generateEditionDateStaticParams();
|