@anthusai/papyrus 1.0.0-next.2 → 1.0.0-next.3
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 +26 -0
- package/amplify/data/resource.ts +30 -0
- package/amplify/data/schema.js +26 -0
- package/amplify/data/schema.ts +30 -0
- package/amplify/functions/content-actions/handler.py +107 -0
- package/amplify/functions/content-actions/requirements.txt +4 -0
- package/amplify/functions/content-actions/resource.js +40 -0
- package/amplify/functions/content-actions/resource.ts +54 -0
- package/amplify/functions/shared/python-bundle.js +17 -2
- package/amplify/functions/shared/python-bundle.ts +23 -2
- package/amplify/site-backend.js +10 -0
- package/amplify/site-backend.ts +15 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +2 -2
- package/app/globals.css +1 -4
- package/app/layout.tsx +4 -12
- package/components/news-desk-page.tsx +1 -4
- package/lib/content-actions-client.ts +98 -0
- package/lib/empty-theme.css +1 -0
- package/lib/layout-scenarios.ts +8 -8
- package/lib/site-brand-tenant.ts +3 -22
- package/lib/site-brand.ts +44 -42
- package/middleware.ts +10 -35
- package/package.json +2 -1
- package/renderers/pretext/article-page.tsx +1 -6
- package/renderers/pretext/presentation-shell.tsx +2 -9
- package/routes.manifest.json +1 -1
- package/src/with-papyrus.mjs +15 -2
- package/publications/anth_us/brand.ts +0 -68
- package/publications/anth_us/theme.css +0 -66
- package/publications/pilobol_us/brand.ts +0 -37
- package/publications/pilobol_us/theme.css +0 -140
- package/publications/threat_intelligence/blog-defense/graph.ts +0 -636
- package/publications/threat_intelligence/blog-defense/layout.ts +0 -934
- package/publications/threat_intelligence/blog-defense/page-background.tsx +0 -590
- package/publications/threat_intelligence/brand.ts +0 -40
- package/publications/threat_intelligence/pictograms/art.tsx +0 -608
- package/publications/threat_intelligence/pictograms/figure.tsx +0 -82
- package/publications/threat_intelligence/pictograms/registry.ts +0 -30
- package/publications/threat_intelligence/pictograms/system.tsx +0 -305
- package/publications/threat_intelligence/seed/seed-edition-content.json +0 -629
- package/publications/threat_intelligence/theme.css +0 -1440
- /package/{publications/papyrus/theme.css → app/papyrus-theme.css} +0 -0
package/amplify/data/resource.js
CHANGED
|
@@ -2,6 +2,7 @@ import { a, defineData } from "@aws-amplify/backend";
|
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource.js";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource.js";
|
|
4
4
|
import { graphqlJwtAuthorizer } from "../functions/graphql-jwt-authorizer/resource.js";
|
|
5
|
+
import { contentActions } from "../functions/content-actions/resource.js";
|
|
5
6
|
import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
|
|
6
7
|
import { manageUserRole } from "../functions/manage-user-role/resource.js";
|
|
7
8
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource.js";
|
|
@@ -468,6 +469,30 @@ const schema = a.schema({
|
|
|
468
469
|
.returns(a.ref("NewsroomSummary"))
|
|
469
470
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
470
471
|
.handler(a.handler.function(newsroomSummary)),
|
|
472
|
+
deriveMarkus: a
|
|
473
|
+
.query()
|
|
474
|
+
.arguments({ input: a.json().required() })
|
|
475
|
+
.returns(a.json())
|
|
476
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
477
|
+
.handler(a.handler.function(contentActions)),
|
|
478
|
+
saveItemDraft: a
|
|
479
|
+
.mutation()
|
|
480
|
+
.arguments({ input: a.json().required() })
|
|
481
|
+
.returns(a.json())
|
|
482
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
483
|
+
.handler(a.handler.function(contentActions)),
|
|
484
|
+
publishItem: a
|
|
485
|
+
.mutation()
|
|
486
|
+
.arguments({ input: a.json().required() })
|
|
487
|
+
.returns(a.json())
|
|
488
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
489
|
+
.handler(a.handler.function(contentActions)),
|
|
490
|
+
unpublishItem: a
|
|
491
|
+
.mutation()
|
|
492
|
+
.arguments({ input: a.json().required() })
|
|
493
|
+
.returns(a.json())
|
|
494
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
495
|
+
.handler(a.handler.function(contentActions)),
|
|
471
496
|
knowledgeQuery: a
|
|
472
497
|
.query()
|
|
473
498
|
.arguments({
|
|
@@ -2012,6 +2037,7 @@ const schema = a.schema({
|
|
|
2012
2037
|
]),
|
|
2013
2038
|
}).authorization((allow) => [
|
|
2014
2039
|
allow.resource(categoryAction),
|
|
2040
|
+
allow.resource(contentActions),
|
|
2015
2041
|
allow.resource(manageUserRole),
|
|
2016
2042
|
allow.resource(modelAttachmentUpload),
|
|
2017
2043
|
allow.resource(newsroomSummary),
|
package/amplify/data/resource.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
|
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource";
|
|
4
4
|
import { graphqlJwtAuthorizer } from "../functions/graphql-jwt-authorizer/resource";
|
|
5
|
+
import { contentActions } from "../functions/content-actions/resource";
|
|
5
6
|
import { knowledgeQuery } from "../functions/knowledge-query/resource";
|
|
6
7
|
import { manageUserRole } from "../functions/manage-user-role/resource";
|
|
7
8
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource";
|
|
@@ -508,6 +509,34 @@ const schema = a.schema({
|
|
|
508
509
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
509
510
|
.handler(a.handler.function(newsroomSummary)),
|
|
510
511
|
|
|
512
|
+
deriveMarkus: a
|
|
513
|
+
.query()
|
|
514
|
+
.arguments({ input: a.json().required() })
|
|
515
|
+
.returns(a.json())
|
|
516
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
517
|
+
.handler(a.handler.function(contentActions)),
|
|
518
|
+
|
|
519
|
+
saveItemDraft: a
|
|
520
|
+
.mutation()
|
|
521
|
+
.arguments({ input: a.json().required() })
|
|
522
|
+
.returns(a.json())
|
|
523
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
524
|
+
.handler(a.handler.function(contentActions)),
|
|
525
|
+
|
|
526
|
+
publishItem: a
|
|
527
|
+
.mutation()
|
|
528
|
+
.arguments({ input: a.json().required() })
|
|
529
|
+
.returns(a.json())
|
|
530
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
531
|
+
.handler(a.handler.function(contentActions)),
|
|
532
|
+
|
|
533
|
+
unpublishItem: a
|
|
534
|
+
.mutation()
|
|
535
|
+
.arguments({ input: a.json().required() })
|
|
536
|
+
.returns(a.json())
|
|
537
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
538
|
+
.handler(a.handler.function(contentActions)),
|
|
539
|
+
|
|
511
540
|
knowledgeQuery: a
|
|
512
541
|
.query()
|
|
513
542
|
.arguments({
|
|
@@ -2120,6 +2149,7 @@ const schema = a.schema({
|
|
|
2120
2149
|
]),
|
|
2121
2150
|
}).authorization((allow) => [
|
|
2122
2151
|
allow.resource(categoryAction),
|
|
2152
|
+
allow.resource(contentActions),
|
|
2123
2153
|
allow.resource(manageUserRole),
|
|
2124
2154
|
allow.resource(modelAttachmentUpload),
|
|
2125
2155
|
allow.resource(newsroomSummary),
|
package/amplify/data/schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { a } from "@aws-amplify/backend";
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource.js";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource.js";
|
|
4
|
+
import { contentActions } from "../functions/content-actions/resource.js";
|
|
4
5
|
import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
|
|
5
6
|
import { manageUserRole } from "../functions/manage-user-role/resource.js";
|
|
6
7
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource.js";
|
|
@@ -368,6 +369,30 @@ export const schema = a.schema({
|
|
|
368
369
|
.returns(a.ref("NewsroomSummary"))
|
|
369
370
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
370
371
|
.handler(a.handler.function(newsroomSummary)),
|
|
372
|
+
deriveMarkus: a
|
|
373
|
+
.query()
|
|
374
|
+
.arguments({ input: a.json().required() })
|
|
375
|
+
.returns(a.json())
|
|
376
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
377
|
+
.handler(a.handler.function(contentActions)),
|
|
378
|
+
saveItemDraft: a
|
|
379
|
+
.mutation()
|
|
380
|
+
.arguments({ input: a.json().required() })
|
|
381
|
+
.returns(a.json())
|
|
382
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
383
|
+
.handler(a.handler.function(contentActions)),
|
|
384
|
+
publishItem: a
|
|
385
|
+
.mutation()
|
|
386
|
+
.arguments({ input: a.json().required() })
|
|
387
|
+
.returns(a.json())
|
|
388
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
389
|
+
.handler(a.handler.function(contentActions)),
|
|
390
|
+
unpublishItem: a
|
|
391
|
+
.mutation()
|
|
392
|
+
.arguments({ input: a.json().required() })
|
|
393
|
+
.returns(a.json())
|
|
394
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
395
|
+
.handler(a.handler.function(contentActions)),
|
|
371
396
|
knowledgeQuery: a
|
|
372
397
|
.query()
|
|
373
398
|
.arguments({
|
|
@@ -1843,6 +1868,7 @@ export const schema = a.schema({
|
|
|
1843
1868
|
]),
|
|
1844
1869
|
}).authorization((allow) => [
|
|
1845
1870
|
allow.resource(categoryAction),
|
|
1871
|
+
allow.resource(contentActions),
|
|
1846
1872
|
allow.resource(manageUserRole),
|
|
1847
1873
|
allow.resource(modelAttachmentUpload),
|
|
1848
1874
|
allow.resource(newsroomSummary),
|
package/amplify/data/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ClientSchema, a } from "@aws-amplify/backend";
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource";
|
|
4
|
+
import { contentActions } from "../functions/content-actions/resource";
|
|
4
5
|
import { knowledgeQuery } from "../functions/knowledge-query/resource";
|
|
5
6
|
import { manageUserRole } from "../functions/manage-user-role/resource";
|
|
6
7
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource";
|
|
@@ -402,6 +403,34 @@ export const schema = a.schema({
|
|
|
402
403
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
403
404
|
.handler(a.handler.function(newsroomSummary)),
|
|
404
405
|
|
|
406
|
+
deriveMarkus: a
|
|
407
|
+
.query()
|
|
408
|
+
.arguments({ input: a.json().required() })
|
|
409
|
+
.returns(a.json())
|
|
410
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
411
|
+
.handler(a.handler.function(contentActions)),
|
|
412
|
+
|
|
413
|
+
saveItemDraft: a
|
|
414
|
+
.mutation()
|
|
415
|
+
.arguments({ input: a.json().required() })
|
|
416
|
+
.returns(a.json())
|
|
417
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
418
|
+
.handler(a.handler.function(contentActions)),
|
|
419
|
+
|
|
420
|
+
publishItem: a
|
|
421
|
+
.mutation()
|
|
422
|
+
.arguments({ input: a.json().required() })
|
|
423
|
+
.returns(a.json())
|
|
424
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
425
|
+
.handler(a.handler.function(contentActions)),
|
|
426
|
+
|
|
427
|
+
unpublishItem: a
|
|
428
|
+
.mutation()
|
|
429
|
+
.arguments({ input: a.json().required() })
|
|
430
|
+
.returns(a.json())
|
|
431
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
432
|
+
.handler(a.handler.function(contentActions)),
|
|
433
|
+
|
|
405
434
|
knowledgeQuery: a
|
|
406
435
|
.query()
|
|
407
436
|
.arguments({
|
|
@@ -1944,6 +1973,7 @@ export const schema = a.schema({
|
|
|
1944
1973
|
]),
|
|
1945
1974
|
}).authorization((allow) => [
|
|
1946
1975
|
allow.resource(categoryAction),
|
|
1976
|
+
allow.resource(contentActions),
|
|
1947
1977
|
allow.resource(manageUserRole),
|
|
1948
1978
|
allow.resource(modelAttachmentUpload),
|
|
1949
1979
|
allow.resource(newsroomSummary),
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from papyrus_content.graphql_authoring import create_authoring_client
|
|
7
|
+
from papyrus_content.markus_renderer.derive import derive_body
|
|
8
|
+
from papyrus_content.publishing import (
|
|
9
|
+
ItemFields,
|
|
10
|
+
PublishError,
|
|
11
|
+
publish_item,
|
|
12
|
+
save_item,
|
|
13
|
+
unpublish_item,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_input(raw: Any) -> dict:
|
|
18
|
+
parsed = json.loads(raw) if isinstance(raw, (str, bytes)) else raw
|
|
19
|
+
if not isinstance(parsed, dict):
|
|
20
|
+
raise PublishError("invalid-input", "The input must be a JSON object.")
|
|
21
|
+
return parsed
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def actor_from_event(event: dict) -> str:
|
|
25
|
+
identity = event.get("identity") or {}
|
|
26
|
+
return identity.get("username") or identity.get("sub") or "unknown"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def failure_response(error: PublishError) -> dict:
|
|
30
|
+
if error.errors:
|
|
31
|
+
errors = [body_error.to_dict() for body_error in error.errors]
|
|
32
|
+
else:
|
|
33
|
+
errors = [{"code": error.code, "message": error.message, "line": None}]
|
|
34
|
+
return {"ok": False, "errors": errors}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def derive_markus_action(arguments: dict) -> dict:
|
|
38
|
+
derivation = derive_body(arguments.get("frontMatterYaml"), arguments.get("bodyMarkus") or "")
|
|
39
|
+
if not derivation.ok:
|
|
40
|
+
return {"ok": False, "errors": [body_error.to_dict() for body_error in derivation.errors]}
|
|
41
|
+
response = {
|
|
42
|
+
"ok": True,
|
|
43
|
+
"bodyIrBytes": len(json.dumps(derivation.body_ir, separators=(",", ":")).encode("utf-8")),
|
|
44
|
+
"errors": [],
|
|
45
|
+
}
|
|
46
|
+
if arguments.get("includeIr") is True:
|
|
47
|
+
response["bodyIr"] = derivation.body_ir
|
|
48
|
+
return response
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def save_item_draft_action(arguments: dict, actor: str) -> dict:
|
|
52
|
+
client, _claims = create_authoring_client()
|
|
53
|
+
fields = ItemFields(
|
|
54
|
+
type=arguments["type"],
|
|
55
|
+
slug=arguments["slug"],
|
|
56
|
+
section=arguments.get("section"),
|
|
57
|
+
front_matter_yaml=arguments.get("frontMatterYaml"),
|
|
58
|
+
body_markus=arguments.get("bodyMarkus") or "",
|
|
59
|
+
aliases=list(arguments.get("aliases") or []),
|
|
60
|
+
id=arguments.get("id"),
|
|
61
|
+
)
|
|
62
|
+
record = save_item(client, fields, actor=actor, expected_content_hash=arguments.get("expectedContentHash"))
|
|
63
|
+
return {
|
|
64
|
+
"ok": True,
|
|
65
|
+
"item": {
|
|
66
|
+
"id": record["id"],
|
|
67
|
+
"contentHash": record["contentHash"],
|
|
68
|
+
"status": record["status"],
|
|
69
|
+
"slug": record["slug"],
|
|
70
|
+
"versionNumber": record.get("versionNumber"),
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def publish_item_action(arguments: dict, actor: str) -> dict:
|
|
76
|
+
client, _claims = create_authoring_client()
|
|
77
|
+
result = publish_item(client, arguments["id"], actor=actor)
|
|
78
|
+
return {
|
|
79
|
+
"ok": True,
|
|
80
|
+
"changed": result.changed,
|
|
81
|
+
"publishedId": result.published_item_id,
|
|
82
|
+
"versionNumber": result.version_number,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def unpublish_item_action(arguments: dict, actor: str) -> dict:
|
|
87
|
+
client, _claims = create_authoring_client()
|
|
88
|
+
result = unpublish_item(client, arguments["id"], actor=actor)
|
|
89
|
+
return {"ok": True, "changed": result.changed}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def handler(event, context):
|
|
93
|
+
field = event["info"]["fieldName"]
|
|
94
|
+
arguments = parse_input(event["arguments"]["input"])
|
|
95
|
+
actor = actor_from_event(event)
|
|
96
|
+
try:
|
|
97
|
+
if field == "deriveMarkus":
|
|
98
|
+
return derive_markus_action(arguments)
|
|
99
|
+
if field == "saveItemDraft":
|
|
100
|
+
return save_item_draft_action(arguments, actor)
|
|
101
|
+
if field == "publishItem":
|
|
102
|
+
return publish_item_action(arguments, actor)
|
|
103
|
+
if field == "unpublishItem":
|
|
104
|
+
return unpublish_item_action(arguments, actor)
|
|
105
|
+
except PublishError as error:
|
|
106
|
+
return failure_response(error)
|
|
107
|
+
raise ValueError(f"Unsupported field {field}")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineFunction } from "@aws-amplify/backend";
|
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
|
3
|
+
import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { pythonLambdaCode } from "../shared/python-bundle.js";
|
|
8
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const projectRoot = path.resolve(dirname, "../../..");
|
|
10
|
+
export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.5.1", "Pillow>=10,<12"];
|
|
11
|
+
function installCheckoutRequirements(outputDir) {
|
|
12
|
+
const requirementsPath = path.join(projectRoot, "amplify/functions/content-actions/requirements.txt");
|
|
13
|
+
execSync(`python3 -m pip install -r "${requirementsPath}" -t "${outputDir}" --no-cache-dir --platform manylinux2014_aarch64 --implementation cp --python-version 3.12 --only-binary=:all: --ignore-requires-python`, { cwd: projectRoot, stdio: "inherit" });
|
|
14
|
+
}
|
|
15
|
+
export const contentActions = defineFunction((scope) => {
|
|
16
|
+
return new Function(scope, "papyrus-content-actions", {
|
|
17
|
+
runtime: Runtime.PYTHON_3_12,
|
|
18
|
+
architecture: Architecture.ARM_64,
|
|
19
|
+
handler: "handler.handler",
|
|
20
|
+
timeout: Duration.seconds(60),
|
|
21
|
+
memorySize: 1024,
|
|
22
|
+
code: pythonLambdaCode({
|
|
23
|
+
functionDir: "content-actions",
|
|
24
|
+
modules: ["papyrus_content"],
|
|
25
|
+
requirements: contentActionsRequirements,
|
|
26
|
+
noDeps: true,
|
|
27
|
+
checkoutExtras: installCheckoutRequirements,
|
|
28
|
+
}, [
|
|
29
|
+
"bash",
|
|
30
|
+
"-c",
|
|
31
|
+
[
|
|
32
|
+
"set -euo pipefail",
|
|
33
|
+
"mkdir -p /asset-output/papyrus_content",
|
|
34
|
+
"cp amplify/functions/content-actions/handler.py /asset-output/handler.py",
|
|
35
|
+
"cp -R src/papyrus_content/. /asset-output/papyrus_content/",
|
|
36
|
+
"python3 -m pip install -r amplify/functions/content-actions/requirements.txt -t /asset-output --no-cache-dir",
|
|
37
|
+
].join(" && "),
|
|
38
|
+
]),
|
|
39
|
+
});
|
|
40
|
+
}, { resourceGroupName: "data" });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineFunction } from "@aws-amplify/backend";
|
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
|
3
|
+
import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
4
|
+
import { Construct } from "constructs";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { pythonLambdaCode } from "../shared/python-bundle";
|
|
9
|
+
|
|
10
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const projectRoot = path.resolve(dirname, "../../..");
|
|
12
|
+
|
|
13
|
+
export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.5.1", "Pillow>=10,<12"];
|
|
14
|
+
|
|
15
|
+
function installCheckoutRequirements(outputDir: string): void {
|
|
16
|
+
const requirementsPath = path.join(projectRoot, "amplify/functions/content-actions/requirements.txt");
|
|
17
|
+
execSync(
|
|
18
|
+
`python3 -m pip install -r "${requirementsPath}" -t "${outputDir}" --no-cache-dir --platform manylinux2014_aarch64 --implementation cp --python-version 3.12 --only-binary=:all: --ignore-requires-python`,
|
|
19
|
+
{ cwd: projectRoot, stdio: "inherit" },
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const contentActions = defineFunction(
|
|
24
|
+
(scope: Construct) => {
|
|
25
|
+
return new Function(scope, "papyrus-content-actions", {
|
|
26
|
+
runtime: Runtime.PYTHON_3_12,
|
|
27
|
+
architecture: Architecture.ARM_64,
|
|
28
|
+
handler: "handler.handler",
|
|
29
|
+
timeout: Duration.seconds(60),
|
|
30
|
+
memorySize: 1024,
|
|
31
|
+
code: pythonLambdaCode(
|
|
32
|
+
{
|
|
33
|
+
functionDir: "content-actions",
|
|
34
|
+
modules: ["papyrus_content"],
|
|
35
|
+
requirements: contentActionsRequirements,
|
|
36
|
+
noDeps: true,
|
|
37
|
+
checkoutExtras: installCheckoutRequirements,
|
|
38
|
+
},
|
|
39
|
+
[
|
|
40
|
+
"bash",
|
|
41
|
+
"-c",
|
|
42
|
+
[
|
|
43
|
+
"set -euo pipefail",
|
|
44
|
+
"mkdir -p /asset-output/papyrus_content",
|
|
45
|
+
"cp amplify/functions/content-actions/handler.py /asset-output/handler.py",
|
|
46
|
+
"cp -R src/papyrus_content/. /asset-output/papyrus_content/",
|
|
47
|
+
"python3 -m pip install -r amplify/functions/content-actions/requirements.txt -t /asset-output --no-cache-dir",
|
|
48
|
+
].join(" && "),
|
|
49
|
+
],
|
|
50
|
+
),
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
{ resourceGroupName: "data" },
|
|
54
|
+
);
|
|
@@ -58,9 +58,24 @@ function bundleFromPip(outputDir, options) {
|
|
|
58
58
|
// Older pip (e.g. a Python 3.9 build image) checks Requires-Python against the host interpreter, not --python-version.
|
|
59
59
|
"--ignore-requires-python",
|
|
60
60
|
...extra,
|
|
61
|
-
pipSpec(),
|
|
61
|
+
...(options.noDeps ? [] : [pipSpec()]),
|
|
62
62
|
...(options.requirements ?? []),
|
|
63
63
|
], { stdio: "inherit" });
|
|
64
|
+
if (options.noDeps) {
|
|
65
|
+
execFileSync(python, [
|
|
66
|
+
"-m", "pip", "install",
|
|
67
|
+
"--target", outputDir,
|
|
68
|
+
"--no-cache-dir",
|
|
69
|
+
"--no-deps",
|
|
70
|
+
"--platform", "manylinux2014_aarch64",
|
|
71
|
+
"--implementation", "cp",
|
|
72
|
+
"--python-version", "3.12",
|
|
73
|
+
"--only-binary=:all:",
|
|
74
|
+
"--ignore-requires-python",
|
|
75
|
+
...extra,
|
|
76
|
+
pipSpec(),
|
|
77
|
+
], { stdio: "inherit" });
|
|
78
|
+
}
|
|
64
79
|
fs.copyFileSync(path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"), path.join(outputDir, "handler.py"));
|
|
65
80
|
if (options.corpora) {
|
|
66
81
|
const corporaDir = path.join(outputDir, "corpora");
|
|
@@ -74,7 +89,7 @@ function bundleFromPip(outputDir, options) {
|
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
|
-
function bundleFromCheckout(outputDir, options) {
|
|
92
|
+
export function bundleFromCheckout(outputDir, options) {
|
|
78
93
|
fs.copyFileSync(path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"), path.join(outputDir, "handler.py"));
|
|
79
94
|
for (const module of options.modules) {
|
|
80
95
|
fs.mkdirSync(path.join(outputDir, module), { recursive: true });
|
|
@@ -53,6 +53,8 @@ export type PythonBundleOptions = {
|
|
|
53
53
|
checkoutExtras?: (outputDir: string) => void;
|
|
54
54
|
/** Extra pip requirements in package mode, beyond `papyrus-newsroom`. */
|
|
55
55
|
requirements?: string[];
|
|
56
|
+
/** Package mode: install `papyrus-newsroom` with `--no-deps`, then only `requirements`. */
|
|
57
|
+
noDeps?: boolean;
|
|
56
58
|
/** Copy `<cwd>/corpora/*.yml` into the asset (publication-owned steering config). */
|
|
57
59
|
corpora?: boolean;
|
|
58
60
|
};
|
|
@@ -77,11 +79,30 @@ function bundleFromPip(outputDir: string, options: PythonBundleOptions): void {
|
|
|
77
79
|
// Older pip (e.g. a Python 3.9 build image) checks Requires-Python against the host interpreter, not --python-version.
|
|
78
80
|
"--ignore-requires-python",
|
|
79
81
|
...extra,
|
|
80
|
-
pipSpec(),
|
|
82
|
+
...(options.noDeps ? [] : [pipSpec()]),
|
|
81
83
|
...(options.requirements ?? []),
|
|
82
84
|
],
|
|
83
85
|
{ stdio: "inherit" },
|
|
84
86
|
);
|
|
87
|
+
if (options.noDeps) {
|
|
88
|
+
execFileSync(
|
|
89
|
+
python,
|
|
90
|
+
[
|
|
91
|
+
"-m", "pip", "install",
|
|
92
|
+
"--target", outputDir,
|
|
93
|
+
"--no-cache-dir",
|
|
94
|
+
"--no-deps",
|
|
95
|
+
"--platform", "manylinux2014_aarch64",
|
|
96
|
+
"--implementation", "cp",
|
|
97
|
+
"--python-version", "3.12",
|
|
98
|
+
"--only-binary=:all:",
|
|
99
|
+
"--ignore-requires-python",
|
|
100
|
+
...extra,
|
|
101
|
+
pipSpec(),
|
|
102
|
+
],
|
|
103
|
+
{ stdio: "inherit" },
|
|
104
|
+
);
|
|
105
|
+
}
|
|
85
106
|
fs.copyFileSync(
|
|
86
107
|
path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"),
|
|
87
108
|
path.join(outputDir, "handler.py"),
|
|
@@ -98,7 +119,7 @@ function bundleFromPip(outputDir: string, options: PythonBundleOptions): void {
|
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
121
|
|
|
101
|
-
function bundleFromCheckout(outputDir: string, options: PythonBundleOptions): void {
|
|
122
|
+
export function bundleFromCheckout(outputDir: string, options: PythonBundleOptions): void {
|
|
102
123
|
fs.copyFileSync(
|
|
103
124
|
path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"),
|
|
104
125
|
path.join(outputDir, "handler.py"),
|
package/amplify/site-backend.js
CHANGED
|
@@ -18,6 +18,7 @@ import { assignmentAction } from "./functions/assignment-action/resource.js";
|
|
|
18
18
|
import { categoryAction } from "./functions/category-action/resource.js";
|
|
19
19
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
|
|
20
20
|
import { graphqlJwtAuthorizer } from "./functions/graphql-jwt-authorizer/resource.js";
|
|
21
|
+
import { contentActions } from "./functions/content-actions/resource.js";
|
|
21
22
|
import { knowledgeQuery } from "./functions/knowledge-query/resource.js";
|
|
22
23
|
import { manageUserRole } from "./functions/manage-user-role/resource.js";
|
|
23
24
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource.js";
|
|
@@ -79,6 +80,7 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
79
80
|
assignmentAction,
|
|
80
81
|
auth: defineSiteAuth(site.auth ?? authConfigFromEnv()),
|
|
81
82
|
categoryAction,
|
|
83
|
+
contentActions,
|
|
82
84
|
data,
|
|
83
85
|
graphqlJwtAuthorizer,
|
|
84
86
|
knowledgeQuery,
|
|
@@ -310,6 +312,7 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
310
312
|
for (const resource of [
|
|
311
313
|
backend.assignmentAction,
|
|
312
314
|
backend.categoryAction,
|
|
315
|
+
backend.contentActions,
|
|
313
316
|
backend.emailSubmissionProcessor,
|
|
314
317
|
backend.knowledgeQuery,
|
|
315
318
|
backend.modelAttachmentUpload,
|
|
@@ -323,6 +326,13 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
323
326
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda);
|
|
324
327
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda);
|
|
325
328
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
|
|
329
|
+
const contentActionsLambda = backend.contentActions.resources.lambda;
|
|
330
|
+
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
331
|
+
contentActionsLambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
|
|
332
|
+
contentActionsLambda.addToRolePolicy(new PolicyStatement({
|
|
333
|
+
actions: ["appsync:GraphQL"],
|
|
334
|
+
resources: ["*"],
|
|
335
|
+
}));
|
|
326
336
|
grantNewsroomReadWriteDelete(backend.modelAttachmentUpload.resources.lambda);
|
|
327
337
|
grantMediaReadWriteDelete(backend.modelAttachmentUpload.resources.lambda);
|
|
328
338
|
if (backend.sesInboundReceive) {
|
package/amplify/site-backend.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { assignmentAction } from "./functions/assignment-action/resource";
|
|
|
19
19
|
import { categoryAction } from "./functions/category-action/resource";
|
|
20
20
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
21
21
|
import { graphqlJwtAuthorizer } from "./functions/graphql-jwt-authorizer/resource";
|
|
22
|
+
import { contentActions } from "./functions/content-actions/resource";
|
|
22
23
|
import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
23
24
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
24
25
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
@@ -90,6 +91,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
90
91
|
assignmentAction,
|
|
91
92
|
auth: defineSiteAuth(site.auth ?? authConfigFromEnv()),
|
|
92
93
|
categoryAction,
|
|
94
|
+
contentActions,
|
|
93
95
|
data,
|
|
94
96
|
graphqlJwtAuthorizer,
|
|
95
97
|
knowledgeQuery,
|
|
@@ -386,6 +388,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
386
388
|
for (const resource of [
|
|
387
389
|
backend.assignmentAction,
|
|
388
390
|
backend.categoryAction,
|
|
391
|
+
backend.contentActions,
|
|
389
392
|
backend.emailSubmissionProcessor,
|
|
390
393
|
backend.knowledgeQuery,
|
|
391
394
|
backend.modelAttachmentUpload,
|
|
@@ -399,6 +402,18 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
399
402
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
|
|
400
403
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
|
|
401
404
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
|
|
405
|
+
const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
|
|
406
|
+
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
407
|
+
contentActionsLambda.addEnvironment(
|
|
408
|
+
"PAPYRUS_GRAPHQL_ENDPOINT",
|
|
409
|
+
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
|
|
410
|
+
);
|
|
411
|
+
contentActionsLambda.addToRolePolicy(
|
|
412
|
+
new PolicyStatement({
|
|
413
|
+
actions: ["appsync:GraphQL"],
|
|
414
|
+
resources: ["*"],
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
402
417
|
grantNewsroomReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
403
418
|
grantMediaReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
404
419
|
if (backend.sesInboundReceive) {
|
|
@@ -52,8 +52,8 @@ export default async function DateScopedArticlePage({ params }: DateScopedArticl
|
|
|
52
52
|
editionFooter={{
|
|
53
53
|
editionBasePath,
|
|
54
54
|
entries: buildPresentationFooterEntries(content),
|
|
55
|
-
subtitle: SITE_BRAND.
|
|
56
|
-
title: SITE_BRAND.
|
|
55
|
+
subtitle: SITE_BRAND.footerSubtitleOverride ?? (content.description?.trim() || "Inside Papyrus"),
|
|
56
|
+
title: SITE_BRAND.footerTitle,
|
|
57
57
|
}}
|
|
58
58
|
item={item}
|
|
59
59
|
backHref={`${editionBasePath}#${item.slug}`}
|
package/app/globals.css
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
@import "
|
|
2
|
-
@import "../publications/papyrus/theme.css";
|
|
3
|
-
@import "../publications/pilobol_us/theme.css";
|
|
4
|
-
@import "../publications/anth_us/theme.css";
|
|
1
|
+
@import "./papyrus-theme.css";
|
|
5
2
|
|
|
6
3
|
@import "tailwindcss";
|
|
7
4
|
@import "tw-animate-css";
|
package/app/layout.tsx
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import { Playfair_Display, Plus_Jakarta_Sans } from "next/font/google";
|
|
3
|
-
import { cookies } from "next/headers";
|
|
4
3
|
import Script from "next/script";
|
|
5
4
|
import { AmplifyClientProvider } from "../components/amplify-client-provider";
|
|
6
5
|
import { PapyrusConsoleShell } from "../components/papyrus-console-shell";
|
|
7
6
|
import { TooltipProvider } from "../components/ui/tooltip";
|
|
8
|
-
import { getPresentationChoices, getSiteBrand
|
|
7
|
+
import { getPresentationChoices, getSiteBrand } from "../lib/site-brand";
|
|
9
8
|
import { getSiteStack } from "../lib/site-stack";
|
|
10
9
|
import "./tailwind.css";
|
|
11
10
|
import "./globals.css";
|
|
11
|
+
import "papyrus-site-theme";
|
|
12
12
|
|
|
13
13
|
const playfairDisplay = Playfair_Display({
|
|
14
14
|
subsets: ["latin"],
|
|
@@ -22,16 +22,8 @@ const plusJakartaSans = Plus_Jakarta_Sans({
|
|
|
22
22
|
|
|
23
23
|
const faviconVersion = "20260517-1";
|
|
24
24
|
const defaultTheme = "system";
|
|
25
|
-
const BRAND_OVERRIDE_COOKIE = "papyrus-site-brand-override";
|
|
26
|
-
|
|
27
|
-
async function resolveLayoutSiteBrand() {
|
|
28
|
-
const cookieStore = await cookies();
|
|
29
|
-
const brandId = resolveRuntimeSiteBrandId(cookieStore.get(BRAND_OVERRIDE_COOKIE)?.value ?? null);
|
|
30
|
-
return getSiteBrand(brandId);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
25
|
export async function generateMetadata(): Promise<Metadata> {
|
|
34
|
-
const siteBrand =
|
|
26
|
+
const siteBrand = getSiteBrand();
|
|
35
27
|
return {
|
|
36
28
|
title: siteBrand.appTitle,
|
|
37
29
|
description: siteBrand.appDescription,
|
|
@@ -45,7 +37,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
48
|
-
const siteBrand =
|
|
40
|
+
const siteBrand = getSiteBrand();
|
|
49
41
|
const presentationChoices = getPresentationChoices(siteBrand);
|
|
50
42
|
const siteStack = getSiteStack(siteBrand);
|
|
51
43
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { cookies } from "next/headers";
|
|
2
1
|
import { NewsDeskWorkspace, type NewsDeskTab } from "./topic-steering-workspace";
|
|
3
2
|
import {
|
|
4
3
|
createDemoCategorySteeringDashboard,
|
|
@@ -7,7 +6,6 @@ import {
|
|
|
7
6
|
loadConfiguredCorpusSummaries,
|
|
8
7
|
} from "../lib/category-repository";
|
|
9
8
|
import {
|
|
10
|
-
BRAND_OVERRIDE_COOKIE,
|
|
11
9
|
getBrandAnalysisProfilesPath,
|
|
12
10
|
getBrandNewsroomSectionsConfigPath,
|
|
13
11
|
getBrandSteeringConfigPath,
|
|
@@ -65,8 +63,7 @@ export async function NewsDeskPage({ section: routeSection, sectionPageId, selec
|
|
|
65
63
|
proposal: getFirstSearchParam(resolvedSearchParams, "proposal"),
|
|
66
64
|
};
|
|
67
65
|
const useDemoDashboard = getFirstSearchParam(resolvedSearchParams, "demo") === "1";
|
|
68
|
-
const
|
|
69
|
-
const brand = resolveActiveSiteBrand(brandOverride);
|
|
66
|
+
const brand = resolveActiveSiteBrand();
|
|
70
67
|
const analysisProfiles = await loadAnalysisProfileSummaries(getBrandAnalysisProfilesPath(brand));
|
|
71
68
|
const configuredCorpora = await loadConfiguredCorpusSummaries(getBrandSteeringConfigPath(brand));
|
|
72
69
|
const dashboard = useDemoDashboard
|