@anthusai/papyrus 2.0.0-next.5 → 2.0.0-next.51
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/data/resource.js +23 -3
- package/amplify/data/resource.ts +25 -3
- package/amplify/data/schema.js +2028 -1979
- package/amplify/data/schema.ts +72 -14
- 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/content-actions/handler.py +53 -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/{plugins/editorial-checks/function → functions/editorial-checks}/resource.js +7 -7
- package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.ts +7 -7
- package/amplify/functions/editorial-checks/schema.js +121 -0
- package/amplify/{plugins → functions}/editorial-checks/schema.ts +33 -6
- package/amplify/functions/editorial-checks/wire.js +53 -0
- package/amplify/functions/editorial-checks/wire.ts +61 -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 +16 -0
- package/amplify/functions/monitoring-view/schema.ts +18 -0
- package/amplify/functions/monitoring-view/wire.js +37 -0
- package/amplify/functions/monitoring-view/wire.ts +47 -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 +195 -0
- package/amplify/plugins/newsletter/config.ts +240 -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 +260 -0
- package/amplify/plugins/newsletter/papyrus.plugin.ts +308 -0
- package/amplify/plugins/newsletter/schema.js +56 -0
- package/amplify/plugins/newsletter/schema.ts +61 -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 +162 -0
- package/amplify/plugins/reader-steering/config.ts +248 -0
- package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
- package/amplify/plugins/reader-steering/function/handler.py +1363 -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/resource.js +27 -0
- package/amplify/plugins/reader-steering/function/resource.ts +35 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.js +175 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +201 -0
- package/amplify/plugins/reader-steering/schema.js +140 -0
- package/amplify/plugins/reader-steering/schema.ts +142 -0
- package/amplify/plugins/registry.js +4 -2
- package/amplify/plugins/registry.ts +4 -2
- package/amplify/plugins/types.ts +5 -0
- package/amplify/plugins/wire.js +1 -0
- package/amplify/plugins/wire.ts +1 -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.ts +17 -0
- package/amplify/site-backend.js +36 -1
- package/amplify/site-backend.ts +44 -1
- package/app/api/newsletter/confirm/route.ts +13 -0
- package/app/api/newsletter/preferences/route.ts +13 -0
- package/app/api/newsletter/subscribe/route.ts +9 -0
- package/app/api/newsletter/unsubscribe/route.ts +13 -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/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/bin/papyrus-infra.mjs +20 -1
- package/components/editorial-checks-admin-panel.tsx +36 -11
- package/components/moderation-panel.tsx +248 -0
- package/components/monitoring-overview-view.tsx +241 -0
- package/components/monitoring-panel.tsx +40 -0
- package/components/newsletter-panel.tsx +474 -0
- package/components/newsletter-sections.tsx +163 -0
- package/components/newsroom-article-editor.tsx +2 -4
- package/components/newsroom-board-view.tsx +4 -1
- package/components/newsroom-curation-bootstrap.tsx +45 -0
- package/components/newsroom-organizer.css +31 -0
- package/components/newsroom-references-view.tsx +14 -14
- package/components/publication-cyclotron-panel.tsx +15 -10
- package/components/reference-add-entry.tsx +75 -0
- package/components/reference-curation-controls.tsx +31 -22
- package/components/reference-cyclotron-priming.tsx +58 -19
- package/components/reference-cyclotron-suggestion.tsx +1 -1
- package/components/reference-priming.tsx +14 -10
- package/components/reference-section-suggestion.tsx +1 -1
- package/components/topic-steering-workspace.tsx +64 -20
- package/components/use-editorial-checks.ts +12 -10
- package/infra/amplify-app-shell.js +90 -16
- package/infra/amplify-app-shell.ts +102 -18
- 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 +85 -6
- package/infra/site-config.ts +123 -6
- package/lib/category-repository.ts +11 -9
- package/lib/curation-routes.ts +345 -0
- package/lib/editorial-checks-backend.ts +1 -1
- package/lib/editorial-checks-client.ts +19 -1
- package/lib/editorial-checks-demo.ts +2 -2
- package/lib/editorial-checks-settings.ts +3 -0
- package/lib/editorial-checks.ts +8 -11
- package/lib/editorial-preview.ts +5 -5
- package/lib/monitoring-client.ts +29 -0
- package/lib/monitoring-overview-fixture.ts +51 -0
- package/lib/monitoring-overview.ts +145 -0
- package/lib/newsletter-api.ts +435 -0
- package/lib/newsletter-client.ts +46 -0
- package/lib/newsletter-fixture.ts +184 -0
- package/lib/newsletter-model.ts +202 -0
- package/lib/newsletter-pages.ts +364 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/prospectus-session.ts +15 -14
- package/lib/prospectus-sign-in-routes.ts +54 -13
- package/lib/prospectus-site-identity.ts +24 -1
- 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/relevance-status.ts +2 -2
- package/lib/site-brand-tenant.ts +11 -6
- package/lib/site-brand.ts +6 -3
- package/lib/site-origin.ts +67 -0
- package/middleware.ts +14 -3
- package/package.json +42 -4
- package/routes.manifest.json +191 -1
- package/amplify/plugins/editorial-checks/function/handler.py +0 -11
- package/amplify/plugins/editorial-checks/function/requirements.txt +0 -1
- package/amplify/plugins/editorial-checks/papyrus.plugin.js +0 -50
- package/amplify/plugins/editorial-checks/papyrus.plugin.ts +0 -63
- package/amplify/plugins/editorial-checks/schema.js +0 -94
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
/** Mirrors DIRECTORY_HTML_PATTERN and DIRECTORY_SLASH_PATTERN in standard/config.py. */
|
|
4
|
+
const DIRECTORY_HTML_PATTERN = "^/(?!(?:.*/)?index\\.html$)(.+)\\.html$";
|
|
5
|
+
const DIRECTORY_SLASH_PATTERN = "^/((?:[^/]+/)*[^/.]+)$";
|
|
6
|
+
const START_MARKER = "/*PAPYRUS_PATTERN_RULES*/";
|
|
7
|
+
const END_MARKER = "/*END_PAPYRUS_PATTERN_RULES*/";
|
|
8
|
+
/**
|
|
9
|
+
* The pattern rules a site declares in projection.json: its `redirects.patterns`, plus, for
|
|
10
|
+
* `urls.style: "directory"`, `/x.html` to `/x/`. Mirrors `StandardSettings.edge_rules` in Python.
|
|
11
|
+
*/
|
|
12
|
+
export function edgeRulesFromSettings(settings) {
|
|
13
|
+
const rules = (settings.redirects?.patterns ?? []).map((rule) => ({
|
|
14
|
+
p: rule.pattern,
|
|
15
|
+
t: rule.to,
|
|
16
|
+
s: rule.status ?? 301,
|
|
17
|
+
x: rule.except ?? [],
|
|
18
|
+
}));
|
|
19
|
+
if (settings.urls?.style === "directory") {
|
|
20
|
+
// /x.html to /x/ (a directory index file such as /x/index.html is served as it is), then /x to /x/.
|
|
21
|
+
rules.push({ p: DIRECTORY_HTML_PATTERN, t: "/$1/", s: 301, x: ["/404.html"] });
|
|
22
|
+
rules.push({ p: DIRECTORY_SLASH_PATTERN, t: "/$1/", s: 301, x: ["/404"] });
|
|
23
|
+
}
|
|
24
|
+
return rules;
|
|
25
|
+
}
|
|
26
|
+
/** A standard-front-end site publishes Markdown twins unless it turns `agents.markdown` off. */
|
|
27
|
+
export function negotiatesMarkdown(settings) {
|
|
28
|
+
return settings.site !== undefined && settings.agents?.markdown !== false;
|
|
29
|
+
}
|
|
30
|
+
/** A directory-style site keeps a page's Markdown twin at `/x/index.md`; the edge then serves a bare `/x.md` from there. */
|
|
31
|
+
export function usesDirectoryTwins(settings) {
|
|
32
|
+
return negotiatesMarkdown(settings) && settings.urls?.style === "directory";
|
|
33
|
+
}
|
|
34
|
+
export function readEdgeConfig(staticDir, baseDirectory = process.cwd()) {
|
|
35
|
+
if (staticDir === undefined)
|
|
36
|
+
return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
37
|
+
const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
|
|
38
|
+
if (!existsSync(settingsPath))
|
|
39
|
+
return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
40
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
41
|
+
return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings), directoryTwins: usesDirectoryTwins(settings) };
|
|
42
|
+
}
|
|
43
|
+
export function readEdgeRules(staticDir, baseDirectory = process.cwd()) {
|
|
44
|
+
return readEdgeConfig(staticDir, baseDirectory).rules;
|
|
45
|
+
}
|
|
46
|
+
const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
47
|
+
const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
48
|
+
const TWINS_START = "/*PAPYRUS_DIRECTORY_TWINS*/";
|
|
49
|
+
const TWINS_END = "/*END_PAPYRUS_DIRECTORY_TWINS*/";
|
|
50
|
+
function spliceFlag(source, start, end, label) {
|
|
51
|
+
const from = source.indexOf(start);
|
|
52
|
+
const to = source.indexOf(end);
|
|
53
|
+
if (from === -1 || to === -1 || to < from) {
|
|
54
|
+
throw new Error(`redirect function source has no ${label} markers`);
|
|
55
|
+
}
|
|
56
|
+
return source.slice(0, from + start.length) + "true" + source.slice(to);
|
|
57
|
+
}
|
|
58
|
+
/** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
|
|
59
|
+
export function compileRedirectFunctionSource(source, rules, options = {}) {
|
|
60
|
+
const start = source.indexOf(START_MARKER);
|
|
61
|
+
const end = source.indexOf(END_MARKER);
|
|
62
|
+
if (start === -1 || end === -1 || end < start) {
|
|
63
|
+
throw new Error("redirect function source has no pattern-rule markers");
|
|
64
|
+
}
|
|
65
|
+
let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
|
|
66
|
+
if (options.negotiateMarkdown === true) {
|
|
67
|
+
compiled = spliceFlag(compiled, NEGOTIATE_START, NEGOTIATE_END, "markdown negotiation");
|
|
68
|
+
}
|
|
69
|
+
if (options.directoryTwins === true) {
|
|
70
|
+
compiled = spliceFlag(compiled, TWINS_START, TWINS_END, "directory twin");
|
|
71
|
+
}
|
|
72
|
+
return compiled;
|
|
73
|
+
}
|
|
@@ -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", {
|
|
@@ -145,6 +145,13 @@ export type PapyrusSiteBackendConfig = {
|
|
|
145
145
|
* DependsOn ordering; no resource changes. Default 12; 0 turns it off.
|
|
146
146
|
*/
|
|
147
147
|
resolverWaveSize?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Optional model groups to leave out of the data API (see `OPTIONAL_MODEL_GROUPS`; today `editions`).
|
|
150
|
+
* CloudFormation allows 2,500 resources per stack operation, nested stacks included, and a fresh create of the full
|
|
151
|
+
* model set exceeds it. Deploy a new site once with a group excluded, then remove the exclusion and deploy again:
|
|
152
|
+
* the second deploy is an ordinary update that adds the models.
|
|
153
|
+
*/
|
|
154
|
+
excludeModelGroups?: string[];
|
|
148
155
|
/** Static reader app (Markus-static sites) that a publish rebuilds through IAM `amplify:StartJob`. */
|
|
149
156
|
reader?: { amplifyAppId: string; branchName?: string };
|
|
150
157
|
/** Lets the newsroom start the `staging` branch build of this CMS app. */
|
|
@@ -178,6 +185,16 @@ export type PapyrusSiteBackendConfig = {
|
|
|
178
185
|
* Unset: no plugin, and the synthesized backend is exactly what it was before plugins existed.
|
|
179
186
|
*/
|
|
180
187
|
plugins?: PluginRequest[];
|
|
188
|
+
/**
|
|
189
|
+
* The public form receiving endpoint every site gets (docs/public-forms.md). It accepts posts only from the site's
|
|
190
|
+
* own public hosts (distribution aliases, reader domain, revalidate URL, sign-in redirect URLs) plus these.
|
|
191
|
+
*/
|
|
192
|
+
publicForms?: {
|
|
193
|
+
/** Extra https origins (or http://localhost) allowed to post, for example `https://www.example.org`. */
|
|
194
|
+
allowedOrigins?: string[];
|
|
195
|
+
/** Extra honeypot field names besides `website`. */
|
|
196
|
+
honeypotFields?: string[];
|
|
197
|
+
};
|
|
181
198
|
features?: {
|
|
182
199
|
consoleResponder?: boolean;
|
|
183
200
|
inboundEmail?: boolean;
|
package/amplify/site-backend.js
CHANGED
|
@@ -20,9 +20,14 @@ import { addInvalidationPathAlarm } from "./publication/invalidation-alarm.js";
|
|
|
20
20
|
import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource.js";
|
|
21
21
|
import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client.js";
|
|
22
22
|
import { defineSiteData } from "./data/resource.js";
|
|
23
|
+
import { editorialChecks } from "./functions/editorial-checks/resource.js";
|
|
24
|
+
import { monitoringView } from "./functions/monitoring-view/resource.js";
|
|
25
|
+
import { wireMonitoringView } from "./functions/monitoring-view/wire.js";
|
|
26
|
+
import { wireEditorialChecks } from "./functions/editorial-checks/wire.js";
|
|
23
27
|
import { resolvePlugins } from "./plugins/registry.js";
|
|
24
28
|
import { wirePlugins } from "./plugins/wire.js";
|
|
25
29
|
import { assignmentAction } from "./functions/assignment-action/resource.js";
|
|
30
|
+
import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access.js";
|
|
26
31
|
import { categoryAction } from "./functions/category-action/resource.js";
|
|
27
32
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
|
|
28
33
|
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource.js";
|
|
@@ -30,6 +35,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource.js";
|
|
|
30
35
|
import { manageUserRole } from "./functions/manage-user-role/resource.js";
|
|
31
36
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource.js";
|
|
32
37
|
import { newsroomSummary } from "./functions/newsroom-summary/resource.js";
|
|
38
|
+
import { derivePublicFormOrigins } from "./functions/public-forms/origins.js";
|
|
39
|
+
import { publicForms } from "./functions/public-forms/resource.js";
|
|
40
|
+
import { wirePublicForms } from "./functions/public-forms/wire.js";
|
|
33
41
|
import { procedureAction } from "./functions/procedure-action/resource.js";
|
|
34
42
|
import { readerSettings } from "./functions/reader-settings/resource.js";
|
|
35
43
|
import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource.js";
|
|
@@ -88,12 +96,15 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
88
96
|
auth: defineSiteAuth(authConfig),
|
|
89
97
|
categoryAction,
|
|
90
98
|
contentActions,
|
|
91
|
-
|
|
99
|
+
editorialChecks,
|
|
100
|
+
data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
|
|
92
101
|
knowledgeQuery,
|
|
93
102
|
manageUserRole,
|
|
103
|
+
monitoringView,
|
|
94
104
|
modelAttachmentUpload,
|
|
95
105
|
newsroomSummary,
|
|
96
106
|
procedureAction,
|
|
107
|
+
publicForms,
|
|
97
108
|
readerSettings,
|
|
98
109
|
emailSubmissionProcessor,
|
|
99
110
|
sesInboundReceive,
|
|
@@ -304,10 +315,19 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
304
315
|
}
|
|
305
316
|
grantCorporaRead(backend.knowledgeQuery.resources.lambda);
|
|
306
317
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda);
|
|
318
|
+
scopeAssignmentDataAccess(backend.assignmentAction.resources.lambda, backend.data.resources.graphqlApi.arn);
|
|
307
319
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda);
|
|
308
320
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
|
|
309
321
|
const contentActionsLambda = backend.contentActions.resources.lambda;
|
|
310
322
|
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
323
|
+
if ((activeBrand?.corpusKey ?? "").trim() !== "") {
|
|
324
|
+
// A deploy creates the publication's canonical corpus from the brand when the backend has none.
|
|
325
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
|
|
326
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
|
|
327
|
+
}
|
|
328
|
+
// AppSync validates the token; pending intake additionally binds its actor
|
|
329
|
+
// and publication to this deployment's pool and existing API endpoint.
|
|
330
|
+
contentActionsLambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
|
|
311
331
|
contentActionsLambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
|
|
312
332
|
contentActionsLambda.addToRolePolicy(new PolicyStatement({
|
|
313
333
|
actions: ["appsync:GraphQL"],
|
|
@@ -548,8 +568,21 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
548
568
|
: []),
|
|
549
569
|
],
|
|
550
570
|
}));
|
|
571
|
+
const publicFormsEndpoint = wirePublicForms(backend, {
|
|
572
|
+
allowedOrigins: derivePublicFormOrigins({
|
|
573
|
+
explicit: site.publicForms?.allowedOrigins,
|
|
574
|
+
hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
|
|
575
|
+
urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
|
|
576
|
+
}),
|
|
577
|
+
honeypotFields: site.publicForms?.honeypotFields,
|
|
578
|
+
});
|
|
551
579
|
backend.addOutput({
|
|
552
580
|
custom: {
|
|
581
|
+
publicForms: { endpoint: publicFormsEndpoint },
|
|
582
|
+
// Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
|
|
583
|
+
publication: s3ProjectionPublication !== undefined
|
|
584
|
+
? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
|
|
585
|
+
: null,
|
|
553
586
|
prospectusSignIn: prospectusGoogleOnlyClient
|
|
554
587
|
? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
|
|
555
588
|
: null,
|
|
@@ -584,6 +617,8 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
584
617
|
: null,
|
|
585
618
|
},
|
|
586
619
|
});
|
|
620
|
+
wireEditorialChecks(backend);
|
|
621
|
+
wireMonitoringView(backend);
|
|
587
622
|
wirePlugins(backend, plugins, contentActionsLambda);
|
|
588
623
|
return backend;
|
|
589
624
|
}
|
package/amplify/site-backend.ts
CHANGED
|
@@ -27,9 +27,14 @@ import { addInvalidationPathAlarm } from "./publication/invalidation-alarm";
|
|
|
27
27
|
import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource";
|
|
28
28
|
import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client";
|
|
29
29
|
import { defineSiteData } from "./data/resource";
|
|
30
|
+
import { editorialChecks } from "./functions/editorial-checks/resource";
|
|
31
|
+
import { monitoringView } from "./functions/monitoring-view/resource";
|
|
32
|
+
import { wireMonitoringView } from "./functions/monitoring-view/wire";
|
|
33
|
+
import { wireEditorialChecks } from "./functions/editorial-checks/wire";
|
|
30
34
|
import { resolvePlugins } from "./plugins/registry";
|
|
31
35
|
import { wirePlugins } from "./plugins/wire";
|
|
32
36
|
import { assignmentAction } from "./functions/assignment-action/resource";
|
|
37
|
+
import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access";
|
|
33
38
|
import { categoryAction } from "./functions/category-action/resource";
|
|
34
39
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
35
40
|
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
|
|
@@ -37,6 +42,9 @@ import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
|
37
42
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
38
43
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
39
44
|
import { newsroomSummary } from "./functions/newsroom-summary/resource";
|
|
45
|
+
import { derivePublicFormOrigins } from "./functions/public-forms/origins";
|
|
46
|
+
import { publicForms } from "./functions/public-forms/resource";
|
|
47
|
+
import { wirePublicForms } from "./functions/public-forms/wire";
|
|
40
48
|
import { procedureAction } from "./functions/procedure-action/resource";
|
|
41
49
|
import { readerSettings } from "./functions/reader-settings/resource";
|
|
42
50
|
import { emailSubmissionProcessor } from "./functions/email-submission-processor/resource";
|
|
@@ -109,12 +117,15 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
109
117
|
auth: defineSiteAuth(authConfig),
|
|
110
118
|
categoryAction,
|
|
111
119
|
contentActions,
|
|
112
|
-
|
|
120
|
+
editorialChecks,
|
|
121
|
+
data: defineSiteData(pluginDefinitions, site.excludeModelGroups ?? []),
|
|
113
122
|
knowledgeQuery,
|
|
114
123
|
manageUserRole,
|
|
124
|
+
monitoringView,
|
|
115
125
|
modelAttachmentUpload,
|
|
116
126
|
newsroomSummary,
|
|
117
127
|
procedureAction,
|
|
128
|
+
publicForms,
|
|
118
129
|
readerSettings,
|
|
119
130
|
emailSubmissionProcessor,
|
|
120
131
|
sesInboundReceive,
|
|
@@ -387,10 +398,25 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
387
398
|
}
|
|
388
399
|
grantCorporaRead(backend.knowledgeQuery.resources.lambda as LambdaFunction);
|
|
389
400
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
|
|
401
|
+
scopeAssignmentDataAccess(
|
|
402
|
+
backend.assignmentAction.resources.lambda as LambdaFunction,
|
|
403
|
+
backend.data.resources.graphqlApi.arn,
|
|
404
|
+
);
|
|
390
405
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
|
|
391
406
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
|
|
392
407
|
const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
|
|
393
408
|
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
409
|
+
if ((activeBrand?.corpusKey ?? "").trim() !== "") {
|
|
410
|
+
// A deploy creates the publication's canonical corpus from the brand when the backend has none.
|
|
411
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
|
|
412
|
+
contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
|
|
413
|
+
}
|
|
414
|
+
// AppSync validates the token; pending intake additionally binds its actor
|
|
415
|
+
// and publication to this deployment's pool and existing API endpoint.
|
|
416
|
+
contentActionsLambda.addEnvironment(
|
|
417
|
+
"PAPYRUS_USER_POOL_ISSUER",
|
|
418
|
+
`https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`,
|
|
419
|
+
);
|
|
394
420
|
contentActionsLambda.addEnvironment(
|
|
395
421
|
"PAPYRUS_GRAPHQL_ENDPOINT",
|
|
396
422
|
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
|
|
@@ -685,8 +711,23 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
685
711
|
}),
|
|
686
712
|
);
|
|
687
713
|
|
|
714
|
+
const publicFormsEndpoint = wirePublicForms(backend, {
|
|
715
|
+
allowedOrigins: derivePublicFormOrigins({
|
|
716
|
+
explicit: site.publicForms?.allowedOrigins,
|
|
717
|
+
hosts: [activeBrand?.readerDeployment?.domain, ...(publication?.distribution?.aliases ?? [])],
|
|
718
|
+
urls: [site.revalidateBaseUrl, ...siteAuthRedirectUrls(authConfig)],
|
|
719
|
+
}),
|
|
720
|
+
honeypotFields: site.publicForms?.honeypotFields,
|
|
721
|
+
});
|
|
722
|
+
|
|
688
723
|
backend.addOutput({
|
|
689
724
|
custom: {
|
|
725
|
+
publicForms: { endpoint: publicFormsEndpoint },
|
|
726
|
+
// Lets the operator CLI re-project on publish without PAPYRUS_PUBLICATION_KIND / PAPYRUS_CONTENT_ACTIONS_FUNCTION (PPY-0c0b75).
|
|
727
|
+
publication:
|
|
728
|
+
s3ProjectionPublication !== undefined
|
|
729
|
+
? { kind: "s3-projection", contentActionsFunction: contentActionsLambda.functionName }
|
|
730
|
+
: null,
|
|
690
731
|
prospectusSignIn: prospectusGoogleOnlyClient
|
|
691
732
|
? { clientId: prospectusGoogleOnlyClient.ref, callbackPath: PROSPECTUS_SIGN_IN_CALLBACK_PATH }
|
|
692
733
|
: null,
|
|
@@ -721,6 +762,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
721
762
|
: null,
|
|
722
763
|
},
|
|
723
764
|
});
|
|
765
|
+
wireEditorialChecks(backend);
|
|
766
|
+
wireMonitoringView(backend);
|
|
724
767
|
wirePlugins(backend, plugins, contentActionsLambda);
|
|
725
768
|
|
|
726
769
|
return backend;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleNewsletterConfirmPage, handleNewsletterConfirmPost } 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 handleNewsletterConfirmPage(request);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function POST(request: NextRequest) {
|
|
12
|
+
return handleNewsletterConfirmPost(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 { handleNewsletterSubscribe } from "../../../../lib/newsletter-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleNewsletterSubscribe(request);
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleNewsletterUnsubscribe } from "../../../../lib/newsletter-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function GET(request: NextRequest) {
|
|
8
|
+
return handleNewsletterUnsubscribe(request);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function POST(request: NextRequest) {
|
|
12
|
+
return handleNewsletterUnsubscribe(request);
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleCurationRoute } from "../../../../../lib/curation-routes";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function GET(request: NextRequest) {
|
|
8
|
+
return handleCurationRoute(request, "context");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleCurationRoute } from "../../../../../lib/curation-routes";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleCurationRoute(request, "priming");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleCurationRoute } from "../../../../../lib/curation-routes";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleCurationRoute(request, "publication");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "comment-delete");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "comment-edit");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "comment");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "handle");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function GET(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "me");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "rating");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "report");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRetiredSubjects } from "../../../../lib/reader-retirement-route";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function GET(request: NextRequest) {
|
|
8
|
+
return handleReaderRetiredSubjects(request);
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "submit");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { handleReaderRequest } from "../../../../lib/reader-api";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export function POST(request: NextRequest) {
|
|
8
|
+
return handleReaderRequest(request, "vote");
|
|
9
|
+
}
|