@anthusai/papyrus 0.0.0 → 1.0.0-next.1
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/LICENSE +21 -0
- package/amplify/auth/resource.js +61 -0
- package/amplify/auth/resource.ts +65 -0
- package/amplify/data/resource.js +2037 -0
- package/amplify/data/resource.ts +2148 -0
- package/amplify/data/schema.js +1850 -0
- package/amplify/data/schema.ts +1953 -0
- package/amplify/functions/assignment-action/handler.ts +1416 -0
- package/amplify/functions/assignment-action/resource.js +8 -0
- package/amplify/functions/assignment-action/resource.ts +9 -0
- package/amplify/functions/category-action/handler.ts +2259 -0
- package/amplify/functions/category-action/resource.js +8 -0
- package/amplify/functions/category-action/resource.ts +9 -0
- package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
- package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
- package/amplify/functions/console-chat-responder/Dockerfile +19 -0
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
- package/amplify/functions/console-chat-responder/resource.js +153 -0
- package/amplify/functions/console-chat-responder/resource.ts +179 -0
- package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
- package/amplify/functions/email-submission-processor/handler.py +46 -0
- package/amplify/functions/email-submission-processor/requirements.txt +2 -0
- package/amplify/functions/email-submission-processor/resource.js +56 -0
- package/amplify/functions/email-submission-processor/resource.ts +69 -0
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
- package/amplify/functions/knowledge-query/handler.py +1 -0
- package/amplify/functions/knowledge-query/requirements.txt +3 -0
- package/amplify/functions/knowledge-query/resource.js +50 -0
- package/amplify/functions/knowledge-query/resource.ts +61 -0
- package/amplify/functions/manage-user-role/handler.ts +799 -0
- package/amplify/functions/manage-user-role/resource.js +8 -0
- package/amplify/functions/manage-user-role/resource.ts +9 -0
- package/amplify/functions/model-attachment-upload/handler.ts +387 -0
- package/amplify/functions/model-attachment-upload/resource.js +8 -0
- package/amplify/functions/model-attachment-upload/resource.ts +9 -0
- package/amplify/functions/newsroom-summary/handler.ts +321 -0
- package/amplify/functions/newsroom-summary/resource.js +8 -0
- package/amplify/functions/newsroom-summary/resource.ts +9 -0
- package/amplify/functions/procedure-action/handler.ts +621 -0
- package/amplify/functions/procedure-action/resource.js +8 -0
- package/amplify/functions/procedure-action/resource.ts +9 -0
- package/amplify/functions/reader-settings/handler.ts +322 -0
- package/amplify/functions/reader-settings/resource.js +8 -0
- package/amplify/functions/reader-settings/resource.ts +9 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
- package/amplify/functions/ses-inbound-receive/resource.js +8 -0
- package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
- package/amplify/functions/shared/amplify-secrets.ts +65 -0
- package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
- package/amplify/functions/shared/email-mime-intake.ts +196 -0
- package/amplify/functions/shared/email-submission.ts +415 -0
- package/amplify/functions/shared/inbound-email-intake.ts +37 -0
- package/amplify/functions/shared/lambda-data-client.ts +21 -0
- package/amplify/functions/shared/model-payloads.ts +186 -0
- package/amplify/functions/shared/python-bundle.js +115 -0
- package/amplify/functions/shared/python-bundle.ts +141 -0
- package/amplify/functions/shared/slack-events.ts +111 -0
- package/amplify/functions/slack-delivery/handler.py +112 -0
- package/amplify/functions/slack-delivery/requirements.txt +2 -0
- package/amplify/functions/slack-delivery/resource.js +38 -0
- package/amplify/functions/slack-delivery/resource.ts +51 -0
- package/amplify/functions/slack-events/handler.ts +155 -0
- package/amplify/functions/slack-events/resource.js +11 -0
- package/amplify/functions/slack-events/resource.ts +12 -0
- package/amplify/inbound-email/stack.js +46 -0
- package/amplify/inbound-email/stack.ts +61 -0
- package/amplify/package.json +3 -0
- package/amplify/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +30 -0
- package/amplify/site-backend.js +587 -0
- package/amplify/site-backend.ts +704 -0
- package/amplify/storage/resource.js +18 -0
- package/amplify/storage/resource.ts +19 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
- package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
- package/app/[year]/[month]/[day]/page.tsx +22 -0
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
- package/app/api/archive/editions/route.ts +18 -0
- package/app/api/media/[...path]/route.ts +35 -0
- package/app/api/revalidate/route.ts +94 -0
- package/app/archive/page.tsx +24 -0
- package/app/articles/[slug]/page.tsx +35 -0
- package/app/globals.css +10329 -0
- package/app/layout.tsx +146 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -0
- package/app/newsroom/forum/[threadId]/page.tsx +35 -0
- package/app/newsroom/insights/[threadId]/page.tsx +22 -0
- package/app/newsroom/insights/page.tsx +14 -0
- package/app/newsroom/layout.tsx +12 -0
- package/app/newsroom/page.tsx +31 -0
- package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
- package/app/page.tsx +272 -0
- package/app/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +10 -0
- package/bin/papyrus-infra.mjs +39 -0
- package/components/ai-elements/conversation.tsx +168 -0
- package/components/ai-elements/message.tsx +360 -0
- package/components/ai-elements/model-selector.tsx +30 -0
- package/components/ai-elements/prompt-input.tsx +1461 -0
- package/components/ai-elements/shimmer.tsx +76 -0
- package/components/ai-elements/suggestion.tsx +54 -0
- package/components/ai-elements/tool.tsx +173 -0
- package/components/amplify-client-provider.tsx +140 -0
- package/components/archive-grid.tsx +89 -0
- package/components/archive-shell.tsx +41 -0
- package/components/article-video.tsx +47 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +119 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- package/components/newsroom-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +319 -0
- package/components/newsroom-references-view.tsx +598 -0
- package/components/newsroom-topics-view.tsx +425 -0
- package/components/papyrus-console-shell.tsx +1912 -0
- package/components/pictogram-figure.tsx +65 -0
- package/components/presentation-footer.tsx +146 -0
- package/components/reader-auth-control.tsx +162 -0
- package/components/reader-auth-state.ts +122 -0
- package/components/reader-settings.ts +332 -0
- package/components/reader-theme-control.tsx +85 -0
- package/components/reference-source-preview.tsx +163 -0
- package/components/settings-page.tsx +239 -0
- package/components/topic-steering-workspace.tsx +18063 -0
- package/components/ui/badge.tsx +30 -0
- package/components/ui/button-group.tsx +87 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/card.tsx +32 -0
- package/components/ui/command.tsx +194 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +266 -0
- package/components/ui/hover-card.tsx +51 -0
- package/components/ui/input-group.tsx +158 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +16 -0
- package/components/ui/select.tsx +199 -0
- package/components/ui/separator.tsx +23 -0
- package/components/ui/sheet.tsx +92 -0
- package/components/ui/spinner.tsx +10 -0
- package/components/ui/tabs.tsx +113 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components/use-resolved-papyrus-theme.ts +47 -0
- package/infra/amplify-app-shell.js +109 -0
- package/infra/amplify-app-shell.ts +155 -0
- package/infra/index.js +12 -0
- package/lib/amplify-outputs-guard.ts +24 -0
- package/lib/amplify-outputs-path.ts +16 -0
- package/lib/amplify-server-runtime.ts +63 -0
- package/lib/archive-data.ts +30 -0
- package/lib/archive-types.ts +15 -0
- package/lib/articles.ts +320 -0
- package/lib/cached-content-repository.ts +89 -0
- package/lib/category-dashboard.ts +37 -0
- package/lib/category-repository.ts +1907 -0
- package/lib/console-chat-client.ts +1316 -0
- package/lib/console-web-context.ts +118 -0
- package/lib/content-load-timing.ts +23 -0
- package/lib/content-repository.ts +40 -0
- package/lib/content-types.ts +104 -0
- package/lib/define-site.js +3 -0
- package/lib/define-site.ts +21 -0
- package/lib/demo-amplify-outputs.ts +15 -0
- package/lib/dev-sandbox-editor-auth.ts +18 -0
- package/lib/doctrine.ts +128 -0
- package/lib/edition-routes.ts +155 -0
- package/lib/edition-sections.ts +111 -0
- package/lib/empty-edition-layout-plan.ts +109 -0
- package/lib/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +986 -0
- package/lib/image-url.ts +10 -0
- package/lib/layout-plan.ts +981 -0
- package/lib/layout-scenarios.ts +564 -0
- package/lib/markus-body.ts +168 -0
- package/lib/markus-ir.ts +235 -0
- package/lib/markus-projection.ts +243 -0
- package/lib/markus-to-article.ts +93 -0
- package/lib/news-desk-session.ts +160 -0
- package/lib/newspaper-layout.ts +3459 -0
- package/lib/newsroom-assignments.ts +173 -0
- package/lib/newsroom-base-path.ts +48 -0
- package/lib/newsroom-card-layout.ts +102 -0
- package/lib/newsroom-category-drilldown.ts +133 -0
- package/lib/newsroom-demo-dashboard.ts +267 -0
- package/lib/newsroom-demo-profile.ts +46 -0
- package/lib/newsroom-forum-routes.ts +153 -0
- package/lib/newsroom-index-filters.ts +202 -0
- package/lib/newsroom-knowledge-query-request.ts +51 -0
- package/lib/newsroom-list-selection.ts +42 -0
- package/lib/newsroom-nav.ts +42 -0
- package/lib/newsroom-references.ts +116 -0
- package/lib/newsroom-sections.ts +52 -0
- package/lib/newsroom-topics.ts +118 -0
- package/lib/papyrus-object-kinds.ts +24 -0
- package/lib/papyrus-web-locations.ts +480 -0
- package/lib/pilobol-sample.ts +167 -0
- package/lib/presentation-footer.ts +92 -0
- package/lib/pretext-layout.ts +179 -0
- package/lib/public-placeholder-config.ts +102 -0
- package/lib/publication-items.ts +181 -0
- package/lib/reader-route-config.ts +1 -0
- package/lib/reader-static-params.ts +56 -0
- package/lib/reader-storage-url.ts +63 -0
- package/lib/reference-display.ts +74 -0
- package/lib/reference-policy.ts +97 -0
- package/lib/reference-source-preview.ts +175 -0
- package/lib/renderer-config.ts +50 -0
- package/lib/renderer.ts +87 -0
- package/lib/reporting-story-budget.ts +638 -0
- package/lib/semantic-graph.ts +554 -0
- package/lib/site-brand-tenant.ts +41 -0
- package/lib/site-brand.ts +179 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/middleware.ts +78 -0
- package/package.json +296 -1
- package/publications/anth_us/brand.ts +68 -0
- package/publications/anth_us/theme.css +66 -0
- package/publications/papyrus/theme.css +11 -0
- package/publications/pilobol_us/brand.ts +37 -0
- package/publications/pilobol_us/theme.css +140 -0
- package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
- package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
- package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
- package/publications/threat_intelligence/brand.ts +40 -0
- package/publications/threat_intelligence/pictograms/art.tsx +608 -0
- package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
- package/publications/threat_intelligence/pictograms/registry.ts +30 -0
- package/publications/threat_intelligence/pictograms/system.tsx +305 -0
- package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
- package/publications/threat_intelligence/theme.css +1440 -0
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +174 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +477 -0
- package/routes.manifest.json +182 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -0
- package/src/backend.js +1 -0
- package/src/backend.ts +3 -0
- package/src/sync.mjs +87 -0
- package/src/with-papyrus.d.mts +2 -0
- package/src/with-papyrus.mjs +47 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineStorage } from "@aws-amplify/backend";
|
|
2
|
+
export const storage = defineStorage({
|
|
3
|
+
name: "papyrusMedia",
|
|
4
|
+
isDefault: true,
|
|
5
|
+
access: (allow) => ({
|
|
6
|
+
"media/*": [
|
|
7
|
+
allow.guest.to(["read"]),
|
|
8
|
+
allow.authenticated.to(["read"]),
|
|
9
|
+
allow.groups(["editor", "admin"]).to(["read", "write", "delete"]),
|
|
10
|
+
],
|
|
11
|
+
"corpora/*": [
|
|
12
|
+
allow.groups(["editor", "admin"]).to(["read"]),
|
|
13
|
+
],
|
|
14
|
+
"newsroom/*": [
|
|
15
|
+
allow.groups(["editor", "admin"]).to(["read", "write", "delete"]),
|
|
16
|
+
],
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineStorage } from "@aws-amplify/backend";
|
|
2
|
+
|
|
3
|
+
export const storage = defineStorage({
|
|
4
|
+
name: "papyrusMedia",
|
|
5
|
+
isDefault: true,
|
|
6
|
+
access: (allow) => ({
|
|
7
|
+
"media/*": [
|
|
8
|
+
allow.guest.to(["read"]),
|
|
9
|
+
allow.authenticated.to(["read"]),
|
|
10
|
+
allow.groups(["editor", "admin"]).to(["read", "write", "delete"]),
|
|
11
|
+
],
|
|
12
|
+
"corpora/*": [
|
|
13
|
+
allow.groups(["editor", "admin"]).to(["read"]),
|
|
14
|
+
],
|
|
15
|
+
"newsroom/*": [
|
|
16
|
+
allow.groups(["editor", "admin"]).to(["read", "write", "delete"]),
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { getSiteRenderer } from "../../../../../lib/site-renderer";
|
|
3
|
+
import { getCachedEditionItem, loadCachedEditionContent } from "../../../../../lib/cached-content-repository";
|
|
4
|
+
import { getEditionDatePath, parseEditionArticleRoute } from "../../../../../lib/edition-routes";
|
|
5
|
+
import { buildPresentationFooterEntries } from "../../../../../lib/presentation-footer";
|
|
6
|
+
import { SITE_BRAND } from "../../../../../lib/site-brand";
|
|
7
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
8
|
+
export const revalidate = 3600;
|
|
9
|
+
|
|
10
|
+
type DateScopedArticlePageProps = {
|
|
11
|
+
params: Promise<{
|
|
12
|
+
year: string;
|
|
13
|
+
month: string;
|
|
14
|
+
day: string;
|
|
15
|
+
articleSlug: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export async function generateMetadata({ params }: DateScopedArticlePageProps) {
|
|
20
|
+
const { year, month, day, articleSlug } = await params;
|
|
21
|
+
const route = parseEditionArticleRoute({ year, month, day, articleSlug });
|
|
22
|
+
if (!route) return {};
|
|
23
|
+
|
|
24
|
+
const item = await getCachedEditionItem({
|
|
25
|
+
editionDate: route.editionDate,
|
|
26
|
+
itemSlug: route.articleSlug,
|
|
27
|
+
});
|
|
28
|
+
if (!item) return {};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
title: `${item.type === "article" ? item.headline : item.title} | ${SITE_BRAND.articleTitleSuffix}`,
|
|
32
|
+
description: item.deck,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default async function DateScopedArticlePage({ params }: DateScopedArticlePageProps) {
|
|
37
|
+
const { year, month, day, articleSlug } = await params;
|
|
38
|
+
const route = parseEditionArticleRoute({ year, month, day, articleSlug });
|
|
39
|
+
if (!route) notFound();
|
|
40
|
+
if (!route.isCanonical) redirect(route.canonicalPath);
|
|
41
|
+
|
|
42
|
+
const content = await loadCachedEditionContent({ editionDate: route.editionDate });
|
|
43
|
+
const item = content.items.find((candidate) => candidate.slug === route.articleSlug);
|
|
44
|
+
if (!item) notFound();
|
|
45
|
+
const editionBasePath = getEditionDatePath(route.editionDate);
|
|
46
|
+
|
|
47
|
+
const siteRenderer = getSiteRenderer();
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<siteRenderer.renderItem
|
|
51
|
+
editionDate={content.editionDate}
|
|
52
|
+
editionFooter={{
|
|
53
|
+
editionBasePath,
|
|
54
|
+
entries: buildPresentationFooterEntries(content),
|
|
55
|
+
subtitle: SITE_BRAND.id === "threat-intelligence" ? "" : (content.description?.trim() || "Inside Papyrus"),
|
|
56
|
+
title: SITE_BRAND.id === "threat-intelligence" ? "ANTHUS THREAT INTELLIGENCE" : undefined,
|
|
57
|
+
}}
|
|
58
|
+
item={item}
|
|
59
|
+
backHref={`${editionBasePath}#${item.slug}`}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { getSiteRenderer } from "../../../../lib/site-renderer";
|
|
3
|
+
import type { PresentationTarget } from "../../../../renderers/pretext";
|
|
4
|
+
import { loadCachedEditionContent } from "../../../../lib/cached-content-repository";
|
|
5
|
+
import { parseEditionDateRoute } from "../../../../lib/edition-routes";
|
|
6
|
+
import type { PretextLayout } from "../../../../lib/renderer-config";
|
|
7
|
+
|
|
8
|
+
type EditionRoutePageProps = {
|
|
9
|
+
year: string;
|
|
10
|
+
month: string;
|
|
11
|
+
day: string;
|
|
12
|
+
initialPageNumber?: number;
|
|
13
|
+
lockedPresentation?: PretextLayout;
|
|
14
|
+
target?: PresentationTarget;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function EditionRoutePage({
|
|
18
|
+
year,
|
|
19
|
+
month,
|
|
20
|
+
day,
|
|
21
|
+
initialPageNumber = 1,
|
|
22
|
+
lockedPresentation,
|
|
23
|
+
target = { kind: "edition" },
|
|
24
|
+
}: EditionRoutePageProps) {
|
|
25
|
+
const route = parseEditionDateRoute({ year, month, day });
|
|
26
|
+
if (!route) notFound();
|
|
27
|
+
if (!route.isCanonical) redirect(route.canonicalPath);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const content = await loadCachedEditionContent({
|
|
31
|
+
editionDate: route.editionDate,
|
|
32
|
+
});
|
|
33
|
+
if (initialPageNumber < 1 || initialPageNumber > content.layoutPlan.pages.length) notFound();
|
|
34
|
+
if (target.kind === "section" && !content.sections.some((section) => section.key === target.sectionKey)) notFound();
|
|
35
|
+
const siteRenderer = getSiteRenderer();
|
|
36
|
+
return (
|
|
37
|
+
<siteRenderer.renderEdition
|
|
38
|
+
content={content}
|
|
39
|
+
editionBasePath={route.canonicalPath}
|
|
40
|
+
initialPageNumber={initialPageNumber}
|
|
41
|
+
lockedPresentation={lockedPresentation}
|
|
42
|
+
target={target}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (isMissingGraphQLEditionError(error)) notFound();
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isMissingGraphQLEditionError(error: unknown): boolean {
|
|
52
|
+
return error instanceof Error && error.message.includes("No published GraphQL edition found");
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { EditionRoutePage } from "../../edition-route-page";
|
|
3
|
+
import { parseEditionPageRoute } from "../../../../../../lib/edition-routes";
|
|
4
|
+
import { getDefaultPretextLayout, getForcedPresentation } from "../../../../../../lib/site-brand";
|
|
5
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
6
|
+
export const revalidate = 3600;
|
|
7
|
+
|
|
8
|
+
type EditionPageRouteProps = {
|
|
9
|
+
params: Promise<{
|
|
10
|
+
year: string;
|
|
11
|
+
month: string;
|
|
12
|
+
day: string;
|
|
13
|
+
pageNumber: string;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default async function EditionPageRoute({ params }: EditionPageRouteProps) {
|
|
18
|
+
const { year, month, day, pageNumber } = await params;
|
|
19
|
+
const route = parseEditionPageRoute({ year, month, day, pageNumber });
|
|
20
|
+
if (!route) notFound();
|
|
21
|
+
if (!route.isCanonical) redirect(route.canonicalPath);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<EditionRoutePage
|
|
25
|
+
day={day}
|
|
26
|
+
initialPageNumber={route.pageNumber}
|
|
27
|
+
lockedPresentation={getForcedPresentation() ?? getDefaultPretextLayout()}
|
|
28
|
+
month={month}
|
|
29
|
+
year={year}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EditionRoutePage } from "./edition-route-page";
|
|
2
|
+
import { generateEditionDateStaticParams } from "../../../../lib/reader-static-params";
|
|
3
|
+
|
|
4
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
5
|
+
export const revalidate = 3600;
|
|
6
|
+
|
|
7
|
+
export async function generateStaticParams() {
|
|
8
|
+
return generateEditionDateStaticParams();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type DateEditionPageProps = {
|
|
12
|
+
params: Promise<{
|
|
13
|
+
year: string;
|
|
14
|
+
month: string;
|
|
15
|
+
day: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default async function DateEditionPage({ params }: DateEditionPageProps) {
|
|
20
|
+
const { year, month, day } = await params;
|
|
21
|
+
return <EditionRoutePage day={day} month={month} year={year} />;
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { EditionRoutePage } from "../../edition-route-page";
|
|
3
|
+
import { createSectionKey } from "../../../../../../lib/edition-sections";
|
|
4
|
+
import { parseEditionSectionRoute } from "../../../../../../lib/edition-routes";
|
|
5
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
6
|
+
export const revalidate = 3600;
|
|
7
|
+
|
|
8
|
+
type EditionSectionRouteProps = {
|
|
9
|
+
params: Promise<{
|
|
10
|
+
year: string;
|
|
11
|
+
month: string;
|
|
12
|
+
day: string;
|
|
13
|
+
sectionKey: string;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default async function EditionSectionRoute({ params }: EditionSectionRouteProps) {
|
|
18
|
+
const { year, month, day, sectionKey } = await params;
|
|
19
|
+
const normalizedSectionKey = createSectionKey(sectionKey);
|
|
20
|
+
const route = parseEditionSectionRoute({ year, month, day, sectionKey: normalizedSectionKey });
|
|
21
|
+
if (!route) notFound();
|
|
22
|
+
if (!route.isCanonical) redirect(route.canonicalPath);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<EditionRoutePage
|
|
26
|
+
day={day}
|
|
27
|
+
month={month}
|
|
28
|
+
target={{ kind: "section", sectionKey: route.sectionKey }}
|
|
29
|
+
year={year}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { loadArchiveEditionPreviews, clampArchiveLimit } from "../../../../lib/archive-data";
|
|
3
|
+
import { ARCHIVE_BATCH_SIZE } from "../../../../lib/archive-types";
|
|
4
|
+
|
|
5
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
6
|
+
export const revalidate = 3600;
|
|
7
|
+
|
|
8
|
+
export async function GET(request: Request) {
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const cursor = url.searchParams.get("cursor");
|
|
11
|
+
const requestedLimit = Number(url.searchParams.get("limit") ?? ARCHIVE_BATCH_SIZE);
|
|
12
|
+
const payload = await loadArchiveEditionPreviews({
|
|
13
|
+
limit: clampArchiveLimit(requestedLimit),
|
|
14
|
+
nextToken: cursor,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return NextResponse.json(payload);
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { isGuestReadableStoragePath, signStorageUrl } from "../../../../lib/reader-storage-url";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
type MediaRouteContext = {
|
|
7
|
+
params: Promise<{
|
|
8
|
+
path: string[];
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function GET(_request: Request, context: MediaRouteContext) {
|
|
13
|
+
const { path } = await context.params;
|
|
14
|
+
const storagePath = path.map((segment) => decodeURIComponent(segment)).join("/");
|
|
15
|
+
if (!isGuestReadableStoragePath(storagePath)) {
|
|
16
|
+
return NextResponse.json({ error: "Forbidden storage path." }, { status: 403 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const signedUrl = await signStorageUrl(storagePath);
|
|
20
|
+
const upstream = await fetch(signedUrl, { cache: "no-store" });
|
|
21
|
+
if (!upstream.ok) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: `Upstream media fetch failed (${upstream.status}).` },
|
|
24
|
+
{ status: upstream.status === 404 ? 404 : 502 },
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const headers = new Headers();
|
|
29
|
+
const contentType = upstream.headers.get("content-type");
|
|
30
|
+
if (contentType) {
|
|
31
|
+
headers.set("content-type", contentType);
|
|
32
|
+
}
|
|
33
|
+
headers.set("cache-control", "public, max-age=3600, stale-while-revalidate=86400");
|
|
34
|
+
return new NextResponse(upstream.body, { status: 200, headers });
|
|
35
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { revalidatePath, revalidateTag } from "next/cache";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import {
|
|
4
|
+
ARCHIVE_CACHE_TAG,
|
|
5
|
+
ARTICLES_CACHE_TAG,
|
|
6
|
+
EDITIONS_CACHE_TAG,
|
|
7
|
+
articleCacheTag,
|
|
8
|
+
editionContentCacheTag,
|
|
9
|
+
editionItemCacheTag,
|
|
10
|
+
} from "../../../lib/cached-content-repository";
|
|
11
|
+
import { getEditionArticlePath, getEditionDatePath } from "../../../lib/edition-routes";
|
|
12
|
+
|
|
13
|
+
export const dynamic = "force-dynamic";
|
|
14
|
+
|
|
15
|
+
type RevalidateRequestBody = {
|
|
16
|
+
editionDate?: string;
|
|
17
|
+
articleSlugs?: string[];
|
|
18
|
+
itemSlugs?: string[];
|
|
19
|
+
paths?: string[];
|
|
20
|
+
tags?: string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function POST(request: Request) {
|
|
24
|
+
const configuredSecret = process.env.PAPYRUS_REVALIDATE_SECRET?.trim();
|
|
25
|
+
const providedSecret = request.headers.get("x-papyrus-revalidate-secret")?.trim();
|
|
26
|
+
if (!configuredSecret || providedSecret !== configuredSecret) {
|
|
27
|
+
return NextResponse.json({ ok: false, error: "Unauthorized." }, { status: 401 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const body = (await request.json()) as RevalidateRequestBody;
|
|
31
|
+
const revalidatedTags = new Set<string>();
|
|
32
|
+
const revalidatedPaths = new Set<string>();
|
|
33
|
+
|
|
34
|
+
const editionDate = body.editionDate?.trim();
|
|
35
|
+
if (editionDate) {
|
|
36
|
+
revalidateTag(editionContentCacheTag(editionDate));
|
|
37
|
+
revalidatedTags.add(editionContentCacheTag(editionDate));
|
|
38
|
+
revalidatePath(getEditionDatePath(editionDate));
|
|
39
|
+
revalidatedPaths.add(getEditionDatePath(editionDate));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const slug of body.articleSlugs ?? []) {
|
|
43
|
+
const normalized = slug.trim();
|
|
44
|
+
if (!normalized) continue;
|
|
45
|
+
revalidateTag(articleCacheTag(normalized));
|
|
46
|
+
revalidatedTags.add(articleCacheTag(normalized));
|
|
47
|
+
revalidatePath(`/articles/${encodeURIComponent(normalized)}`);
|
|
48
|
+
revalidatedPaths.add(`/articles/${encodeURIComponent(normalized)}`);
|
|
49
|
+
if (editionDate) {
|
|
50
|
+
revalidateTag(editionItemCacheTag(editionDate, normalized));
|
|
51
|
+
revalidatedTags.add(editionItemCacheTag(editionDate, normalized));
|
|
52
|
+
revalidatePath(getEditionArticlePath(editionDate, normalized));
|
|
53
|
+
revalidatedPaths.add(getEditionArticlePath(editionDate, normalized));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const slug of body.itemSlugs ?? []) {
|
|
58
|
+
const normalized = slug.trim();
|
|
59
|
+
if (!normalized || !editionDate) continue;
|
|
60
|
+
revalidateTag(editionItemCacheTag(editionDate, normalized));
|
|
61
|
+
revalidatedTags.add(editionItemCacheTag(editionDate, normalized));
|
|
62
|
+
revalidatePath(getEditionArticlePath(editionDate, normalized));
|
|
63
|
+
revalidatedPaths.add(getEditionArticlePath(editionDate, normalized));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const tag of body.tags ?? []) {
|
|
67
|
+
const normalized = tag.trim();
|
|
68
|
+
if (!normalized) continue;
|
|
69
|
+
revalidateTag(normalized);
|
|
70
|
+
revalidatedTags.add(normalized);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
revalidateTag(EDITIONS_CACHE_TAG);
|
|
74
|
+
revalidatedTags.add(EDITIONS_CACHE_TAG);
|
|
75
|
+
revalidateTag(ARCHIVE_CACHE_TAG);
|
|
76
|
+
revalidatedTags.add(ARCHIVE_CACHE_TAG);
|
|
77
|
+
revalidateTag(ARTICLES_CACHE_TAG);
|
|
78
|
+
revalidatedTags.add(ARTICLES_CACHE_TAG);
|
|
79
|
+
revalidatePath("/archive");
|
|
80
|
+
revalidatedPaths.add("/archive");
|
|
81
|
+
|
|
82
|
+
for (const path of body.paths ?? []) {
|
|
83
|
+
const normalized = path.trim();
|
|
84
|
+
if (!normalized.startsWith("/")) continue;
|
|
85
|
+
revalidatePath(normalized);
|
|
86
|
+
revalidatedPaths.add(normalized);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return NextResponse.json({
|
|
90
|
+
ok: true,
|
|
91
|
+
revalidatedTags: [...revalidatedTags],
|
|
92
|
+
revalidatedPaths: [...revalidatedPaths],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ArchiveShell } from "../../components/archive-shell";
|
|
2
|
+
import { ArchiveGrid } from "../../components/archive-grid";
|
|
3
|
+
import { loadArchiveEditionPreviews } from "../../lib/archive-data";
|
|
4
|
+
import { ARCHIVE_BATCH_SIZE } from "../../lib/archive-types";
|
|
5
|
+
|
|
6
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
7
|
+
export const revalidate = 3600;
|
|
8
|
+
|
|
9
|
+
export default async function ArchivePage() {
|
|
10
|
+
const initialBatch = await loadArchiveEditionPreviews({ limit: ARCHIVE_BATCH_SIZE });
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<ArchiveShell>
|
|
14
|
+
<header className="masthead archive-header" aria-describedby="archive-description" aria-labelledby="archive-title">
|
|
15
|
+
<div className="masthead__rule archive-header__rule" />
|
|
16
|
+
<h1 id="archive-title">
|
|
17
|
+
<span>ARCHIVE</span>
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="archive-description" id="archive-description">Previous editions</p>
|
|
20
|
+
</header>
|
|
21
|
+
<ArchiveGrid initialNextCursor={initialBatch.nextCursor ?? null} initialPreviews={initialBatch.previews} />
|
|
22
|
+
</ArchiveShell>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { getSiteRenderer } from "../../../lib/site-renderer";
|
|
3
|
+
import { getCachedArticle } from "../../../lib/cached-content-repository";
|
|
4
|
+
import { generateArticleStaticParams } from "../../../lib/reader-static-params";
|
|
5
|
+
import { SITE_BRAND } from "../../../lib/site-brand";
|
|
6
|
+
|
|
7
|
+
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
8
|
+
export const revalidate = 3600;
|
|
9
|
+
|
|
10
|
+
export async function generateStaticParams() {
|
|
11
|
+
return generateArticleStaticParams();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type ArticlePageProps = {
|
|
15
|
+
params: Promise<{ slug: string }>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function generateMetadata({ params }: ArticlePageProps) {
|
|
19
|
+
const { slug } = await params;
|
|
20
|
+
const article = await getCachedArticle(slug);
|
|
21
|
+
if (!article) return {};
|
|
22
|
+
return {
|
|
23
|
+
title: `${article.headline} | ${SITE_BRAND.articleTitleSuffix}`,
|
|
24
|
+
description: article.deck,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default async function ArticlePage({ params }: ArticlePageProps) {
|
|
29
|
+
const { slug } = await params;
|
|
30
|
+
const article = await getCachedArticle(slug);
|
|
31
|
+
if (!article) notFound();
|
|
32
|
+
|
|
33
|
+
const siteRenderer = getSiteRenderer();
|
|
34
|
+
return <siteRenderer.renderArticle article={article} backHref="/" />;
|
|
35
|
+
}
|