@anthusai/papyrus 1.0.0-next.9 → 1.0.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/redirect-urls.js +54 -0
- package/amplify/auth/redirect-urls.ts +66 -0
- package/amplify/auth/resource.js +20 -5
- package/amplify/auth/resource.ts +27 -5
- package/amplify/data/resource.js +42 -80
- package/amplify/data/resource.ts +42 -80
- package/amplify/data/schema.js +45 -60
- package/amplify/data/schema.ts +47 -60
- 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/renditions/handler.py +120 -0
- package/amplify/functions/renditions/requirements.txt +4 -0
- 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 +143 -176
- package/amplify/site-backend.ts +181 -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/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 -0
- 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 +92 -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 -6
- 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 +148 -27
- package/infra/amplify-app-shell.ts +160 -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 +89 -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 +64 -1
- 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 +68 -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/amplify/site-backend.ts
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
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 } from "./auth/resource";
|
|
28
|
+
import { defineSiteData } from "./data/resource";
|
|
29
|
+
import { resolvePlugins } from "./plugins/registry";
|
|
30
|
+
import { wirePlugins } from "./plugins/wire";
|
|
19
31
|
import { assignmentAction } from "./functions/assignment-action/resource";
|
|
20
32
|
import { categoryAction } from "./functions/category-action/resource";
|
|
21
33
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
22
|
-
import {
|
|
23
|
-
import { contentActions } from "./functions/content-actions/resource";
|
|
34
|
+
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
|
|
24
35
|
import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
25
36
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
26
37
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
@@ -31,7 +42,19 @@ import { emailSubmissionProcessor } from "./functions/email-submission-processor
|
|
|
31
42
|
import { sesInboundReceive } from "./functions/ses-inbound-receive/resource";
|
|
32
43
|
import { slackDelivery } from "./functions/slack-delivery/resource";
|
|
33
44
|
import { slackEvents } from "./functions/slack-events/resource";
|
|
45
|
+
import { addInboundEmailSesIntake } from "./inbound-email/ses-intake";
|
|
34
46
|
import { InboundEmailStack } from "./inbound-email/stack";
|
|
47
|
+
import {
|
|
48
|
+
deriveKnowledgeVectorIndexName,
|
|
49
|
+
deriveReceiptRuleName,
|
|
50
|
+
deriveReceiptRuleSetName,
|
|
51
|
+
deriveStorageBackupVaultName,
|
|
52
|
+
isLegacyProductionPipeline,
|
|
53
|
+
planInboundEmailSes,
|
|
54
|
+
resolveSiteBackendFeatureFlags,
|
|
55
|
+
type SiteBackendIdentity,
|
|
56
|
+
} from "./site-backend-names";
|
|
57
|
+
import { addStorageBackups } from "./storage-backups/construct";
|
|
35
58
|
import { storage } from "./storage/resource";
|
|
36
59
|
import type { PapyrusSite } from "../lib/define-site";
|
|
37
60
|
|
|
@@ -44,57 +67,48 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
44
67
|
|
|
45
68
|
const amplifyBranch = (process.env.AWS_BRANCH ?? "").trim();
|
|
46
69
|
const amplifyAppId = (process.env.AWS_APP_ID ?? "").trim();
|
|
47
|
-
// Amplify app id whose `main` branch is
|
|
48
|
-
//
|
|
70
|
+
// Amplify app id whose `main` branch is the original p.apyr.us backend; only
|
|
71
|
+
// that app keeps the historical account-global names (see site-backend-names.ts).
|
|
49
72
|
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
|
-
|
|
73
|
+
const siteBackendIdentity: SiteBackendIdentity = {
|
|
74
|
+
brandId: (
|
|
75
|
+
site.brandId
|
|
76
|
+
?? process.env.PAPYRUS_SITE_BRAND
|
|
77
|
+
?? process.env.NEXT_PUBLIC_PAPYRUS_SITE_BRAND
|
|
78
|
+
?? "papyrus"
|
|
79
|
+
).trim().toLowerCase(),
|
|
80
|
+
amplifyAppId,
|
|
81
|
+
amplifyBranch,
|
|
82
|
+
productionAppId,
|
|
83
|
+
};
|
|
84
|
+
const isAmplifyProductionPipeline = isLegacyProductionPipeline(siteBackendIdentity);
|
|
85
|
+
const knowledgeVectorIndexName = deriveKnowledgeVectorIndexName(siteBackendIdentity);
|
|
86
|
+
const featureFlags = resolveSiteBackendFeatureFlags(site.features, process.env, siteBackendIdentity);
|
|
87
|
+
const enableInboundEmail = featureFlags.inboundEmail;
|
|
88
|
+
const enableSlackAgent = featureFlags.slack;
|
|
89
|
+
const enableStorageBackups = featureFlags.storageBackups;
|
|
90
|
+
|
|
91
|
+
const baseAuthConfig = site.auth ?? authConfigFromEnv();
|
|
92
|
+
const activeBrand = (papyrusSite.brands ?? []).find((brand) => brand.id.toLowerCase() === siteBackendIdentity.brandId);
|
|
93
|
+
const authConfig = {
|
|
94
|
+
...baseAuthConfig,
|
|
95
|
+
newsroomBasePath: baseAuthConfig.newsroomBasePath ?? activeBrand?.newsroomBasePath,
|
|
96
|
+
};
|
|
97
|
+
const publication = resolveSitePublication(site, activeBrand?.renderer?.kind);
|
|
98
|
+
if (publication !== undefined) assertValidPublicationConfig(publication);
|
|
99
|
+
const publicationPlan = planS3ProjectionBackend(publication);
|
|
100
|
+
const renditionsPlan = planRenditions(publication, process.cwd());
|
|
101
|
+
setSiteFilesForContentActionsBundle(publicationPlan.enabled ? publicationPlan.siteFiles : undefined);
|
|
102
|
+
const plugins = resolvePlugins(site.plugins);
|
|
103
|
+
const pluginDefinitions = Object.assign({}, ...plugins.map(({ plugin }) => plugin.schemaDefinitions()));
|
|
104
|
+
const pluginFunctions: Record<never, never> = Object.assign({}, ...plugins.map(({ plugin }) => plugin.functions()));
|
|
91
105
|
const backend = defineBackend({
|
|
106
|
+
...pluginFunctions,
|
|
92
107
|
assignmentAction,
|
|
93
|
-
auth: defineSiteAuth(
|
|
108
|
+
auth: defineSiteAuth(authConfig),
|
|
94
109
|
categoryAction,
|
|
95
110
|
contentActions,
|
|
96
|
-
data,
|
|
97
|
-
graphqlJwtAuthorizer,
|
|
111
|
+
data: defineSiteData(pluginDefinitions),
|
|
98
112
|
knowledgeQuery,
|
|
99
113
|
manageUserRole,
|
|
100
114
|
modelAttachmentUpload,
|
|
@@ -108,10 +122,25 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
108
122
|
storage,
|
|
109
123
|
});
|
|
110
124
|
|
|
125
|
+
orderModelStacksInWaves(
|
|
126
|
+
backend.data.resources.graphqlApi.node.scope as Construct,
|
|
127
|
+
Object.keys(backend.data.resources.tables),
|
|
128
|
+
site.resolverWaveSize ?? DEFAULT_RESOLVER_WAVE_SIZE,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const requestedCognitoDomainPrefix = (authConfig.cognitoDomainPrefix ?? "").trim();
|
|
132
|
+
if (
|
|
133
|
+
authConfig.applyCognitoDomainPrefix === true
|
|
134
|
+
&& requestedCognitoDomainPrefix !== ""
|
|
135
|
+
&& authConfig.disableGoogleOAuth !== true
|
|
136
|
+
) {
|
|
137
|
+
applyCognitoDomainPrefix(backend.auth.stack, requestedCognitoDomainPrefix);
|
|
138
|
+
}
|
|
139
|
+
|
|
111
140
|
const amplifyBackendDir = dirname(fileURLToPath(import.meta.url));
|
|
112
141
|
// Repo root in a Papyrus checkout; package root when installed from npm.
|
|
113
142
|
const projectRoot = resolve(amplifyBackendDir, "..");
|
|
114
|
-
const enableConsoleResponder =
|
|
143
|
+
const enableConsoleResponder = featureFlags.consoleResponder;
|
|
115
144
|
|
|
116
145
|
if (
|
|
117
146
|
enableConsoleResponder
|
|
@@ -160,43 +189,19 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
160
189
|
const messageThreadTable = backend.data.resources.tables.MessageThread;
|
|
161
190
|
const dataStack = Stack.of(messageTable);
|
|
162
191
|
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
|
-
|| "";
|
|
192
|
+
const amplifySsmEnvConfig = process.env.AMPLIFY_SSM_ENV_CONFIG?.trim() || "";
|
|
175
193
|
|
|
176
194
|
if (enableConsoleResponder) {
|
|
177
|
-
|
|
195
|
+
new ConsoleChatResponderStack(dataStack, "ConsoleChatResponder", {
|
|
178
196
|
messageTable,
|
|
179
197
|
messageStreamArn,
|
|
180
198
|
threadTable: messageThreadTable,
|
|
181
199
|
projectRoot,
|
|
182
200
|
graphqlEndpoint,
|
|
183
|
-
amplifySsmEnvConfig: jwtSsmEnvConfig || undefined,
|
|
184
201
|
responseTarget: process.env.PAPYRUS_CONSOLE_RESPONSE_TARGET,
|
|
185
202
|
model: process.env.PAPYRUS_CONSOLE_MODEL,
|
|
186
203
|
prebuiltImageUri: process.env.PAPYRUS_CONSOLE_RESPONDER_IMAGE_URI,
|
|
187
204
|
});
|
|
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
205
|
}
|
|
201
206
|
|
|
202
207
|
if (enableSlackAgent) {
|
|
@@ -217,9 +222,9 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
217
222
|
"PAPYRUS_SLACK_ALLOWED_USER_IDS",
|
|
218
223
|
(process.env.PAPYRUS_SLACK_ALLOWED_USER_IDS ?? "").trim(),
|
|
219
224
|
);
|
|
220
|
-
if (
|
|
221
|
-
backend.slackEvents.addEnvironment("AMPLIFY_SSM_ENV_CONFIG",
|
|
222
|
-
backend.slackDelivery.addEnvironment("AMPLIFY_SSM_ENV_CONFIG",
|
|
225
|
+
if (amplifySsmEnvConfig) {
|
|
226
|
+
backend.slackEvents.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", amplifySsmEnvConfig);
|
|
227
|
+
backend.slackDelivery.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", amplifySsmEnvConfig);
|
|
223
228
|
}
|
|
224
229
|
backend.slackDelivery.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", graphqlEndpoint);
|
|
225
230
|
backend.slackDelivery.addEnvironment(
|
|
@@ -297,8 +302,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
297
302
|
let storageBackupVault: backup.BackupVault | undefined;
|
|
298
303
|
|
|
299
304
|
const storageBucketCfn = storageBucket.node.defaultChild as s3.CfnBucket | undefined;
|
|
300
|
-
if ((enableStorageBackups || enableInboundEmail) && storageBucketCfn) {
|
|
301
|
-
// Backups
|
|
305
|
+
if ((enableStorageBackups || enableInboundEmail || renditionsPlan.enabled) && storageBucketCfn) {
|
|
306
|
+
// Backups, inbound-email intake and upload-time renditions all rely on S3 → EventBridge notifications.
|
|
302
307
|
storageBucketCfn.addPropertyOverride(
|
|
303
308
|
"NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled",
|
|
304
309
|
true,
|
|
@@ -307,41 +312,17 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
307
312
|
|
|
308
313
|
if (enableStorageBackups) {
|
|
309
314
|
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`),
|
|
315
|
+
const storageBackupVaultName = deriveStorageBackupVaultName(
|
|
316
|
+
siteBackendIdentity,
|
|
317
|
+
process.env.PAPYRUS_STORAGE_BACKUP_VAULT_NAME,
|
|
315
318
|
);
|
|
316
319
|
|
|
317
|
-
|
|
320
|
+
const storageBackups = addStorageBackups(storageBackupsStack, {
|
|
321
|
+
storageBucket,
|
|
318
322
|
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
323
|
});
|
|
324
|
+
storageBackupVault = storageBackups.vault;
|
|
325
|
+
storageBackupPlan = storageBackups.plan;
|
|
345
326
|
}
|
|
346
327
|
|
|
347
328
|
// Grant S3 access on Lambda roles only (not storage bucket policies) to avoid
|
|
@@ -430,9 +411,66 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
430
411
|
new PolicyStatement({ actions: rebuildSettings.actions, resources: rebuildSettings.resources }),
|
|
431
412
|
);
|
|
432
413
|
}
|
|
414
|
+
let s3ProjectionPublication: S3ProjectionPublication | undefined;
|
|
415
|
+
if (publication !== undefined) {
|
|
416
|
+
s3ProjectionPublication = addS3ProjectionPublication(backend.createStack("s3-projection"), publication, {
|
|
417
|
+
mediaBucket: storageBucket,
|
|
418
|
+
renditions: renditionsPlan,
|
|
419
|
+
});
|
|
420
|
+
(contentActionsLambda.node.defaultChild as CfnFunction).memorySize = publicationPlan.contentActionsMemorySize;
|
|
421
|
+
if (publication.reprojectOnDeploy === true) {
|
|
422
|
+
(contentActionsLambda.node.defaultChild as CfnFunction).timeout = REPROJECT_ON_DEPLOY_TIMEOUT_SECONDS;
|
|
423
|
+
addReprojectOnDeploy(backend.createStack("reproject-on-deploy"), contentActionsLambda, reprojectDeployToken(process.env));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (s3ProjectionPublication !== undefined) {
|
|
427
|
+
addProjectionRetries(contentActionsLambda, s3ProjectionPublication.deadLetterQueue);
|
|
428
|
+
}
|
|
429
|
+
const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
|
|
430
|
+
for (const [name, value] of Object.entries(publicationWiring.environment)) {
|
|
431
|
+
contentActionsLambda.addEnvironment(name, value);
|
|
432
|
+
}
|
|
433
|
+
for (const statement of publicationWiring.policyStatements) {
|
|
434
|
+
contentActionsLambda.addToRolePolicy(statement);
|
|
435
|
+
}
|
|
433
436
|
if ((site.revalidateBaseUrl ?? "").trim() !== "") {
|
|
434
437
|
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_BASE_URL", (site.revalidateBaseUrl ?? "").trim());
|
|
435
|
-
|
|
438
|
+
const revalidateSecretParameter = (process.env.PAPYRUS_REVALIDATE_SECRET_PARAMETER ?? "").trim();
|
|
439
|
+
if (!revalidateSecretParameter.startsWith("/")) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"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.",
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_SECRET_PARAMETER", revalidateSecretParameter);
|
|
445
|
+
const revalidateStack = Stack.of(contentActionsLambda);
|
|
446
|
+
contentActionsLambda.addToRolePolicy(
|
|
447
|
+
new PolicyStatement(
|
|
448
|
+
revalidateSecretReadStatement({
|
|
449
|
+
partition: revalidateStack.partition,
|
|
450
|
+
region: revalidateStack.region,
|
|
451
|
+
account: revalidateStack.account,
|
|
452
|
+
parameterName: revalidateSecretParameter,
|
|
453
|
+
}),
|
|
454
|
+
),
|
|
455
|
+
);
|
|
456
|
+
const distributionProblems = validateRevalidateDistributionIds(site.revalidateDistributionIds, site.revalidateBaseUrl);
|
|
457
|
+
if (distributionProblems.length > 0) throw new Error(`Invalid backend config: ${distributionProblems.join("; ")}`);
|
|
458
|
+
const distributionIds = site.revalidateDistributionIds ?? [];
|
|
459
|
+
if (distributionIds.length > 0) {
|
|
460
|
+
for (const [name, value] of Object.entries(revalidateDistributionEnvironment(distributionIds))) {
|
|
461
|
+
backend.contentActions.addEnvironment(name, value);
|
|
462
|
+
}
|
|
463
|
+
contentActionsLambda.addToRolePolicy(
|
|
464
|
+
new PolicyStatement(
|
|
465
|
+
revalidateInvalidationStatement({ partition: revalidateStack.partition, account: revalidateStack.account, distributionIds }),
|
|
466
|
+
),
|
|
467
|
+
);
|
|
468
|
+
for (const id of distributionIds) {
|
|
469
|
+
addInvalidationPathAlarm(revalidateStack, `ReaderEdgeInvalidationPaths${id}`, id);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} else if (site.revalidateDistributionIds !== undefined) {
|
|
473
|
+
throw new Error("Invalid backend config: revalidateDistributionIds requires revalidateBaseUrl");
|
|
436
474
|
}
|
|
437
475
|
grantNewsroomReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
438
476
|
grantMediaReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
@@ -527,86 +565,26 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
527
565
|
|
|
528
566
|
const inboundEventStack = Stack.of(receiveLambda);
|
|
529
567
|
|
|
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
|
-
});
|
|
568
|
+
// SES allows one active receipt rule set per region, so a non-legacy site
|
|
569
|
+
// creates its own brand-named rule set and never activates it here.
|
|
570
|
+
const inboundSesPlan = planInboundEmailSes(siteBackendIdentity, enableInboundEmail, site.inboundEmailSes);
|
|
571
|
+
if (inboundSesPlan.createReceiptRules) {
|
|
572
|
+
addInboundEmailSesIntake(storageStack, {
|
|
573
|
+
storageBucket,
|
|
574
|
+
recipients: inboundRecipients,
|
|
575
|
+
ruleSetName: deriveReceiptRuleSetName(siteBackendIdentity, inboundEmailDomain),
|
|
576
|
+
existingRuleSetName: site.inboundEmailSes?.existingReceiptRuleSetName,
|
|
577
|
+
ruleName: deriveReceiptRuleName(siteBackendIdentity),
|
|
578
|
+
activateRuleSet: inboundSesPlan.activateReceiptRuleSet,
|
|
579
|
+
domainIdentity: inboundSesPlan.manageDomainIdentity
|
|
580
|
+
? {
|
|
581
|
+
domain: inboundEmailDomain,
|
|
582
|
+
hostedZoneId: inboundDnsZoneId,
|
|
583
|
+
hostedZoneName: inboundDnsZoneName,
|
|
584
|
+
recordName: inboundDnsRecordName,
|
|
585
|
+
}
|
|
586
|
+
: undefined,
|
|
587
|
+
});
|
|
610
588
|
}
|
|
611
589
|
|
|
612
590
|
new InboundEmailStack(inboundEventStack, "InboundEmail", {
|
|
@@ -735,5 +713,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
735
713
|
: null,
|
|
736
714
|
},
|
|
737
715
|
});
|
|
716
|
+
wirePlugins(backend, plugins, contentActionsLambda);
|
|
717
|
+
|
|
738
718
|
return backend;
|
|
739
719
|
}
|
|
@@ -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();
|
|
@@ -2,8 +2,13 @@ import { notFound, redirect } from "next/navigation";
|
|
|
2
2
|
import { EditionRoutePage } from "../../edition-route-page";
|
|
3
3
|
import { createSectionKey } from "../../../../../../lib/edition-sections";
|
|
4
4
|
import { parseEditionSectionRoute } from "../../../../../../lib/edition-routes";
|
|
5
|
+
import { generateEditionSectionStaticParams } 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 generateEditionSectionStaticParams();
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
type EditionSectionRouteProps = {
|
|
9
14
|
params: Promise<{
|