@anthusai/papyrus 5.20.0 → 5.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/data/schema.js +11 -25
- package/amplify/data/schema.ts +13 -26
- package/amplify/functions/content-actions/handler.py +17 -2
- 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 +52 -1
- package/amplify/plugins/reader-steering/config.ts +91 -1
- package/amplify/plugins/reader-steering/function/ai_moderation.py +230 -0
- package/amplify/plugins/reader-steering/function/handler.py +829 -10
- 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 +1 -1
- package/amplify/plugins/reader-steering/function/resource.ts +1 -1
- package/amplify/plugins/reader-steering/papyrus.plugin.js +40 -4
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +43 -4
- package/amplify/plugins/reader-steering/schema.js +101 -1
- package/amplify/plugins/reader-steering/schema.ts +101 -1
- package/amplify/plugins/registry.js +2 -0
- package/amplify/plugins/registry.ts +2 -0
- package/amplify/plugins/types.ts +2 -0
- 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/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/report/route.ts +9 -0
- package/app/api/reader/submit/route.ts +9 -0
- package/components/moderation-panel.tsx +248 -0
- package/components/newsletter-panel.tsx +474 -0
- package/components/newsletter-sections.tsx +163 -0
- package/components/newsroom-organizer.css +14 -0
- package/components/topic-steering-workspace.tsx +42 -5
- package/lib/newsletter-api.ts +391 -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 +264 -0
- package/lib/reader-api.ts +7 -5
- package/lib/reader-moderation.ts +220 -0
- package/package.json +19 -1
- package/routes.manifest.json +103 -1
package/lib/reader-api.ts
CHANGED
|
@@ -7,10 +7,12 @@ import { SiteOriginNotConfiguredError, publicSiteOrigin, siteOriginNotConfigured
|
|
|
7
7
|
|
|
8
8
|
export const READER_ACTIONS_FUNCTION_ENV = "PAPYRUS_READER_ACTIONS_FUNCTION";
|
|
9
9
|
export const READER_MAXIMUM_BODY_BYTES = 4096;
|
|
10
|
+
/** Larger bodies for actions that carry reader text: a URL, a title and a note; a comment of 2,000 characters. */
|
|
11
|
+
export const READER_BODY_BYTES_BY_ACTION: Partial<Record<ReaderAction, number>> = { submit: 8192, comment: 12288, "comment-edit": 12288 };
|
|
10
12
|
export const READER_MAXIMUM_SUBJECTS = 50;
|
|
11
13
|
export const READER_SUBJECT_KEY = /^[A-Za-z0-9._:-]{1,200}$/;
|
|
12
14
|
|
|
13
|
-
export type ReaderAction = "me" | "handle" | "vote" | "rating";
|
|
15
|
+
export type ReaderAction = "me" | "handle" | "vote" | "rating" | "submit" | "comment" | "comment-edit" | "comment-delete" | "report";
|
|
14
16
|
|
|
15
17
|
export interface ReaderActionEvent {
|
|
16
18
|
publication: string;
|
|
@@ -65,13 +67,13 @@ export async function invokeReaderActionsLambda(functionName: string, event: Rea
|
|
|
65
67
|
return { statusCode: result.statusCode, body: result.body as Record<string, unknown> };
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
async function readJsonBody(request: NextRequest): Promise<{ payload: Record<string, unknown> } | { response: NextResponse }> {
|
|
70
|
+
async function readJsonBody(request: NextRequest, maximumBytes: number): Promise<{ payload: Record<string, unknown> } | { response: NextResponse }> {
|
|
69
71
|
const contentType = (request.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
70
72
|
if (contentType !== "application/json") return { response: failure(415, "unsupported_media_type", "Send application/json.") };
|
|
71
73
|
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
72
|
-
if (Number.isFinite(declared) && declared >
|
|
74
|
+
if (Number.isFinite(declared) && declared > maximumBytes) return { response: failure(413, "payload_too_large", "The request body is too large.") };
|
|
73
75
|
const text = await request.text();
|
|
74
|
-
if (Buffer.byteLength(text, "utf8") >
|
|
76
|
+
if (Buffer.byteLength(text, "utf8") > maximumBytes) return { response: failure(413, "payload_too_large", "The request body is too large.") };
|
|
75
77
|
try {
|
|
76
78
|
const parsed = JSON.parse(text) as unknown;
|
|
77
79
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("not an object");
|
|
@@ -120,7 +122,7 @@ export async function handleReaderRequest(request: NextRequest, action: ReaderAc
|
|
|
120
122
|
parsed = subjectsFromQuery(request);
|
|
121
123
|
} else {
|
|
122
124
|
if (request.headers.get("origin") !== origin) return failure(403, "origin_mismatch", "This request did not come from this site.");
|
|
123
|
-
parsed = await readJsonBody(request);
|
|
125
|
+
parsed = await readJsonBody(request, READER_BODY_BYTES_BY_ACTION[action] ?? READER_MAXIMUM_BODY_BYTES);
|
|
124
126
|
}
|
|
125
127
|
if ("response" in parsed) return parsed.response;
|
|
126
128
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data side of the Administration "Moderation" panel for the reader-steering plugin: rows from the staff-readable
|
|
3
|
+
* models, shaped into a queue, a hidden list and the recent action log. Pure functions, no network: the panel loads the
|
|
4
|
+
* rows and calls the `moderateReader` mutation; everything it shows is decided here so it can be tested.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ModerationTarget = "submission" | "comment" | "reader";
|
|
8
|
+
export type ModerationActionName = "approve" | "hide" | "unhide" | "ban" | "unban" | "limit" | "promote" | "undo";
|
|
9
|
+
|
|
10
|
+
export type SubmissionRow = {
|
|
11
|
+
id: string;
|
|
12
|
+
canonicalUrl: string;
|
|
13
|
+
domain: string;
|
|
14
|
+
title: string;
|
|
15
|
+
kind?: string | null;
|
|
16
|
+
note?: string | null;
|
|
17
|
+
readerId: string;
|
|
18
|
+
state: string;
|
|
19
|
+
moderation?: string | null;
|
|
20
|
+
reportCount?: number | null;
|
|
21
|
+
signals?: unknown;
|
|
22
|
+
itemId?: string | null;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type CommentRow = {
|
|
27
|
+
id: string;
|
|
28
|
+
subjectKey: string;
|
|
29
|
+
readerId: string;
|
|
30
|
+
body: string;
|
|
31
|
+
parentId?: string | null;
|
|
32
|
+
state: string;
|
|
33
|
+
moderation?: string | null;
|
|
34
|
+
reportCount?: number | null;
|
|
35
|
+
signals?: unknown;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ReportRow = { id: string; targetType: string; target: string; reporterReaderId: string; reason: string; state: string; createdAt: string };
|
|
40
|
+
|
|
41
|
+
export type ActionRow = {
|
|
42
|
+
id: string;
|
|
43
|
+
by: string;
|
|
44
|
+
actorSub: string;
|
|
45
|
+
action: string;
|
|
46
|
+
targetType: string;
|
|
47
|
+
target: string;
|
|
48
|
+
reason?: string | null;
|
|
49
|
+
fromState: string;
|
|
50
|
+
toState: string;
|
|
51
|
+
undoOf?: string | null;
|
|
52
|
+
undoneBy?: string | null;
|
|
53
|
+
at: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type Signal = { source: string; policy: string; detector: string; severity: string; excerpt: string };
|
|
57
|
+
|
|
58
|
+
export type ModerationRow = {
|
|
59
|
+
kind: "submission" | "comment";
|
|
60
|
+
id: string;
|
|
61
|
+
readerId: string;
|
|
62
|
+
handle: string | null;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
state: string;
|
|
65
|
+
title: string;
|
|
66
|
+
url: string | null;
|
|
67
|
+
text: string;
|
|
68
|
+
subject: string | null;
|
|
69
|
+
reports: ReportRow[];
|
|
70
|
+
signals: Signal[];
|
|
71
|
+
canPromote: boolean;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type ActionEntry = ActionRow & { label: string; undoable: boolean; byLabel: string };
|
|
75
|
+
|
|
76
|
+
const SEVERITY_ORDER: Record<string, number> = { error: 0, warn: 1, info: 2 };
|
|
77
|
+
|
|
78
|
+
export function parseSignals(value: unknown): Signal[] {
|
|
79
|
+
let parsed: unknown = value;
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
try {
|
|
82
|
+
parsed = JSON.parse(value);
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!Array.isArray(parsed)) return [];
|
|
88
|
+
return parsed
|
|
89
|
+
.filter((entry): entry is Record<string, unknown> => typeof entry === "object" && entry !== null)
|
|
90
|
+
.map((entry) => ({
|
|
91
|
+
source: String(entry.source ?? ""),
|
|
92
|
+
policy: String(entry.policy ?? ""),
|
|
93
|
+
detector: String(entry.detector ?? ""),
|
|
94
|
+
severity: String(entry.severity ?? "info"),
|
|
95
|
+
excerpt: String(entry.excerpt ?? ""),
|
|
96
|
+
}))
|
|
97
|
+
.sort((left, right) => (SEVERITY_ORDER[left.severity] ?? 3) - (SEVERITY_ORDER[right.severity] ?? 3));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** An address a moderator may follow: http or https only, never `javascript:` or `data:`. */
|
|
101
|
+
export function safeHttpUrl(value: string | null | undefined): string | null {
|
|
102
|
+
if (!value) return null;
|
|
103
|
+
try {
|
|
104
|
+
const url = new URL(value);
|
|
105
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.href : null;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function newestFirst<T extends { createdAt: string; id: string }>(rows: T[]): T[] {
|
|
112
|
+
return [...rows].sort((left, right) => (left.createdAt === right.createdAt ? right.id.localeCompare(left.id) : right.createdAt.localeCompare(left.createdAt)));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function reportsByTarget(reports: ReportRow[]): Map<string, ReportRow[]> {
|
|
116
|
+
const grouped = new Map<string, ReportRow[]>();
|
|
117
|
+
for (const report of reports) {
|
|
118
|
+
if (report.state !== "open") continue;
|
|
119
|
+
grouped.set(report.target, [...(grouped.get(report.target) ?? []), report]);
|
|
120
|
+
}
|
|
121
|
+
return grouped;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function submissionRow(row: SubmissionRow, handles: Record<string, string>, reports: Map<string, ReportRow[]>): ModerationRow {
|
|
125
|
+
return {
|
|
126
|
+
kind: "submission",
|
|
127
|
+
id: row.id,
|
|
128
|
+
readerId: row.readerId,
|
|
129
|
+
handle: handles[row.readerId] ?? null,
|
|
130
|
+
createdAt: row.createdAt,
|
|
131
|
+
state: row.state,
|
|
132
|
+
title: row.title,
|
|
133
|
+
url: safeHttpUrl(row.canonicalUrl),
|
|
134
|
+
text: row.note ?? "",
|
|
135
|
+
subject: row.itemId ?? row.id,
|
|
136
|
+
reports: reports.get(row.id) ?? [],
|
|
137
|
+
signals: parseSignals(row.signals),
|
|
138
|
+
canPromote: row.state === "pending",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function commentRow(row: CommentRow, handles: Record<string, string>, reports: Map<string, ReportRow[]>): ModerationRow {
|
|
143
|
+
return {
|
|
144
|
+
kind: "comment",
|
|
145
|
+
id: row.id,
|
|
146
|
+
readerId: row.readerId,
|
|
147
|
+
handle: handles[row.readerId] ?? null,
|
|
148
|
+
createdAt: row.createdAt,
|
|
149
|
+
state: row.state,
|
|
150
|
+
title: "",
|
|
151
|
+
url: null,
|
|
152
|
+
text: row.body,
|
|
153
|
+
subject: row.subjectKey,
|
|
154
|
+
reports: reports.get(row.id) ?? [],
|
|
155
|
+
signals: parseSignals(row.signals),
|
|
156
|
+
canPromote: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Everything waiting for a decision: new items and reported ones, newest first. Deleted comments are the author's own and are not in it. */
|
|
161
|
+
export function buildQueue(input: { submissions: SubmissionRow[]; comments: CommentRow[]; reports: ReportRow[]; handles: Record<string, string> }): ModerationRow[] {
|
|
162
|
+
const reports = reportsByTarget(input.reports);
|
|
163
|
+
const rows = [
|
|
164
|
+
...input.submissions.filter((row) => (row.moderation ?? "new") === "new").map((row) => submissionRow(row, input.handles, reports)),
|
|
165
|
+
...input.comments.filter((row) => (row.moderation ?? "new") === "new" && row.state !== "deleted").map((row) => commentRow(row, input.handles, reports)),
|
|
166
|
+
];
|
|
167
|
+
return newestFirst(rows);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Items a moderator or a pre-moderation hold keeps out of the public files, newest first, so they can be brought back. */
|
|
171
|
+
export function buildHidden(input: { submissions: SubmissionRow[]; comments: CommentRow[]; reports: ReportRow[]; handles: Record<string, string> }): ModerationRow[] {
|
|
172
|
+
const reports = reportsByTarget(input.reports);
|
|
173
|
+
return newestFirst([
|
|
174
|
+
...input.submissions.filter((row) => row.state === "hidden").map((row) => submissionRow(row, input.handles, reports)),
|
|
175
|
+
...input.comments.filter((row) => row.state === "hidden").map((row) => commentRow(row, input.handles, reports)),
|
|
176
|
+
]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const ACTION_LABELS: Record<string, string> = {
|
|
180
|
+
approve: "Approved",
|
|
181
|
+
hide: "Hidden",
|
|
182
|
+
unhide: "Shown again",
|
|
183
|
+
ban: "Banned",
|
|
184
|
+
unban: "Unbanned",
|
|
185
|
+
limit: "Limited",
|
|
186
|
+
promote: "Promoted to a draft",
|
|
187
|
+
undo: "Undid an action",
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** The recent log, newest first. An action can be undone while nothing has undone it, and a promotion never. */
|
|
191
|
+
export function buildActionLog(actions: ActionRow[], limit = 50): ActionEntry[] {
|
|
192
|
+
return [...actions]
|
|
193
|
+
.sort((left, right) => (left.at === right.at ? right.id.localeCompare(left.id) : right.at.localeCompare(left.at)))
|
|
194
|
+
.slice(0, limit)
|
|
195
|
+
.map((entry) => ({
|
|
196
|
+
...entry,
|
|
197
|
+
label: ACTION_LABELS[entry.action] ?? entry.action,
|
|
198
|
+
byLabel: entry.by === "ai" ? "AI moderator" : "A moderator",
|
|
199
|
+
undoable: !entry.undoneBy && entry.action !== "promote",
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type Receipt = { status?: string | null; id?: string | null; error?: string | null; message?: string | null };
|
|
204
|
+
|
|
205
|
+
export function receiptMessage(receipt: Receipt | null | undefined): string {
|
|
206
|
+
if (!receipt) return "The action did not answer. Reload and check.";
|
|
207
|
+
if (receipt.status === "ok") return "Done.";
|
|
208
|
+
return receipt.message || "The action was refused.";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function moderationModeText(mode: unknown): string {
|
|
212
|
+
if (mode === "off") return "Moderation is off for this site: nothing waits in a queue. You can still hide content and ban readers.";
|
|
213
|
+
if (mode === "human") return "New submissions and comments are public at once and wait here for a decision, unless this site holds them until approved.";
|
|
214
|
+
if (mode === "ai") return "An AI moderator reads each new submission and comment and allows or hides it, with a reason in the log. Its decisions are public. You can undo any of them, and what you decide it never judges again.";
|
|
215
|
+
return "Moderation is on.";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function shortReader(readerId: string, handle: string | null): string {
|
|
219
|
+
return handle ? `${handle} (${readerId.slice(0, 8)})` : `reader ${readerId.slice(0, 8)}`;
|
|
220
|
+
}
|
package/package.json
CHANGED
|
@@ -85,11 +85,14 @@
|
|
|
85
85
|
"./components/item-stream-view": "./components/item-stream-view.tsx",
|
|
86
86
|
"./components/markus-body-editor": "./components/markus-body-editor.tsx",
|
|
87
87
|
"./components/markus-ir-preview": "./components/markus-ir-preview.tsx",
|
|
88
|
+
"./components/moderation-panel": "./components/moderation-panel.tsx",
|
|
88
89
|
"./components/monitoring-overview-view": "./components/monitoring-overview-view.tsx",
|
|
89
90
|
"./components/monitoring-panel": "./components/monitoring-panel.tsx",
|
|
90
91
|
"./components/news-desk-client-provider": "./components/news-desk-client-provider.tsx",
|
|
91
92
|
"./components/news-desk-page": "./components/news-desk-page.tsx",
|
|
92
93
|
"./components/news-desk-taxonomy-client": "./components/news-desk-taxonomy-client.ts",
|
|
94
|
+
"./components/newsletter-panel": "./components/newsletter-panel.tsx",
|
|
95
|
+
"./components/newsletter-sections": "./components/newsletter-sections.tsx",
|
|
93
96
|
"./components/newspaper": "./components/newspaper.tsx",
|
|
94
97
|
"./components/newsroom-access-gate": "./components/newsroom-access-gate.tsx",
|
|
95
98
|
"./components/newsroom-article-editor": "./components/newsroom-article-editor.tsx",
|
|
@@ -233,6 +236,11 @@
|
|
|
233
236
|
"./lib/monitoring-overview-fixture": "./lib/monitoring-overview-fixture.ts",
|
|
234
237
|
"./lib/monitoring-overview": "./lib/monitoring-overview.ts",
|
|
235
238
|
"./lib/news-desk-session": "./lib/news-desk-session.ts",
|
|
239
|
+
"./lib/newsletter-api": "./lib/newsletter-api.ts",
|
|
240
|
+
"./lib/newsletter-client": "./lib/newsletter-client.ts",
|
|
241
|
+
"./lib/newsletter-fixture": "./lib/newsletter-fixture.ts",
|
|
242
|
+
"./lib/newsletter-model": "./lib/newsletter-model.ts",
|
|
243
|
+
"./lib/newsletter-pages": "./lib/newsletter-pages.ts",
|
|
236
244
|
"./lib/newspaper-layout": "./lib/newspaper-layout.ts",
|
|
237
245
|
"./lib/newsroom-articles": "./lib/newsroom-articles.ts",
|
|
238
246
|
"./lib/newsroom-assignments": "./lib/newsroom-assignments.ts",
|
|
@@ -276,6 +284,7 @@
|
|
|
276
284
|
"./lib/reader-api": "./lib/reader-api.ts",
|
|
277
285
|
"./lib/reader-base-path": "./lib/reader-base-path.ts",
|
|
278
286
|
"./lib/reader-cache-lifetime": "./lib/reader-cache-lifetime.ts",
|
|
287
|
+
"./lib/reader-moderation": "./lib/reader-moderation.ts",
|
|
279
288
|
"./lib/reader-retirement-route": "./lib/reader-retirement-route.ts",
|
|
280
289
|
"./lib/reader-route-config": "./lib/reader-route-config.ts",
|
|
281
290
|
"./lib/reader-static-params": "./lib/reader-static-params.ts",
|
|
@@ -342,13 +351,22 @@
|
|
|
342
351
|
"./app/api/local-preview/derive/route": "./app/api/local-preview/derive/route.ts",
|
|
343
352
|
"./app/api/local-preview/media/route": "./app/api/local-preview/media/route.ts",
|
|
344
353
|
"./app/api/media/[...path]/route": "./app/api/media/[...path]/route.ts",
|
|
354
|
+
"./app/api/newsletter/confirm/route": "./app/api/newsletter/confirm/route.ts",
|
|
355
|
+
"./app/api/newsletter/preferences/route": "./app/api/newsletter/preferences/route.ts",
|
|
356
|
+
"./app/api/newsletter/subscribe/route": "./app/api/newsletter/subscribe/route.ts",
|
|
357
|
+
"./app/api/newsletter/unsubscribe/route": "./app/api/newsletter/unsubscribe/route.ts",
|
|
345
358
|
"./app/api/newsroom/curation/context/route": "./app/api/newsroom/curation/context/route.ts",
|
|
346
359
|
"./app/api/newsroom/curation/priming/route": "./app/api/newsroom/curation/priming/route.ts",
|
|
347
360
|
"./app/api/newsroom/curation/publication/route": "./app/api/newsroom/curation/publication/route.ts",
|
|
361
|
+
"./app/api/reader/comment/delete/route": "./app/api/reader/comment/delete/route.ts",
|
|
362
|
+
"./app/api/reader/comment/edit/route": "./app/api/reader/comment/edit/route.ts",
|
|
363
|
+
"./app/api/reader/comment/route": "./app/api/reader/comment/route.ts",
|
|
348
364
|
"./app/api/reader/handle/route": "./app/api/reader/handle/route.ts",
|
|
349
365
|
"./app/api/reader/me/route": "./app/api/reader/me/route.ts",
|
|
350
366
|
"./app/api/reader/rating/route": "./app/api/reader/rating/route.ts",
|
|
367
|
+
"./app/api/reader/report/route": "./app/api/reader/report/route.ts",
|
|
351
368
|
"./app/api/reader/retired/route": "./app/api/reader/retired/route.ts",
|
|
369
|
+
"./app/api/reader/submit/route": "./app/api/reader/submit/route.ts",
|
|
352
370
|
"./app/api/reader/vote/route": "./app/api/reader/vote/route.ts",
|
|
353
371
|
"./app/api/revalidate/route": "./app/api/revalidate/route.ts",
|
|
354
372
|
"./app/archive/page": "./app/archive/page.tsx",
|
|
@@ -415,7 +433,7 @@
|
|
|
415
433
|
"optional": true
|
|
416
434
|
}
|
|
417
435
|
},
|
|
418
|
-
"version": "5.
|
|
436
|
+
"version": "5.22.0",
|
|
419
437
|
"dependencies": {
|
|
420
438
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
421
439
|
"@aws-crypto/sha256-js": "5.2.0",
|
package/routes.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "5.
|
|
2
|
+
"version": "5.22.0",
|
|
3
3
|
"routes": [
|
|
4
4
|
{
|
|
5
5
|
"file": "app/%5F_preview/[[...path]]/route.ts",
|
|
@@ -170,6 +170,53 @@
|
|
|
170
170
|
"dynamic": "\"force-dynamic\""
|
|
171
171
|
}
|
|
172
172
|
},
|
|
173
|
+
{
|
|
174
|
+
"file": "app/api/newsletter/confirm/route.ts",
|
|
175
|
+
"hasDefault": false,
|
|
176
|
+
"exports": [
|
|
177
|
+
"GET",
|
|
178
|
+
"POST"
|
|
179
|
+
],
|
|
180
|
+
"config": {
|
|
181
|
+
"dynamic": "\"force-dynamic\"",
|
|
182
|
+
"runtime": "\"nodejs\""
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"file": "app/api/newsletter/preferences/route.ts",
|
|
187
|
+
"hasDefault": false,
|
|
188
|
+
"exports": [
|
|
189
|
+
"GET",
|
|
190
|
+
"POST"
|
|
191
|
+
],
|
|
192
|
+
"config": {
|
|
193
|
+
"dynamic": "\"force-dynamic\"",
|
|
194
|
+
"runtime": "\"nodejs\""
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"file": "app/api/newsletter/subscribe/route.ts",
|
|
199
|
+
"hasDefault": false,
|
|
200
|
+
"exports": [
|
|
201
|
+
"POST"
|
|
202
|
+
],
|
|
203
|
+
"config": {
|
|
204
|
+
"dynamic": "\"force-dynamic\"",
|
|
205
|
+
"runtime": "\"nodejs\""
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"file": "app/api/newsletter/unsubscribe/route.ts",
|
|
210
|
+
"hasDefault": false,
|
|
211
|
+
"exports": [
|
|
212
|
+
"GET",
|
|
213
|
+
"POST"
|
|
214
|
+
],
|
|
215
|
+
"config": {
|
|
216
|
+
"dynamic": "\"force-dynamic\"",
|
|
217
|
+
"runtime": "\"nodejs\""
|
|
218
|
+
}
|
|
219
|
+
},
|
|
173
220
|
{
|
|
174
221
|
"file": "app/api/newsroom/curation/context/route.ts",
|
|
175
222
|
"hasDefault": false,
|
|
@@ -203,6 +250,39 @@
|
|
|
203
250
|
"runtime": "\"nodejs\""
|
|
204
251
|
}
|
|
205
252
|
},
|
|
253
|
+
{
|
|
254
|
+
"file": "app/api/reader/comment/delete/route.ts",
|
|
255
|
+
"hasDefault": false,
|
|
256
|
+
"exports": [
|
|
257
|
+
"POST"
|
|
258
|
+
],
|
|
259
|
+
"config": {
|
|
260
|
+
"dynamic": "\"force-dynamic\"",
|
|
261
|
+
"runtime": "\"nodejs\""
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"file": "app/api/reader/comment/edit/route.ts",
|
|
266
|
+
"hasDefault": false,
|
|
267
|
+
"exports": [
|
|
268
|
+
"POST"
|
|
269
|
+
],
|
|
270
|
+
"config": {
|
|
271
|
+
"dynamic": "\"force-dynamic\"",
|
|
272
|
+
"runtime": "\"nodejs\""
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"file": "app/api/reader/comment/route.ts",
|
|
277
|
+
"hasDefault": false,
|
|
278
|
+
"exports": [
|
|
279
|
+
"POST"
|
|
280
|
+
],
|
|
281
|
+
"config": {
|
|
282
|
+
"dynamic": "\"force-dynamic\"",
|
|
283
|
+
"runtime": "\"nodejs\""
|
|
284
|
+
}
|
|
285
|
+
},
|
|
206
286
|
{
|
|
207
287
|
"file": "app/api/reader/handle/route.ts",
|
|
208
288
|
"hasDefault": false,
|
|
@@ -236,6 +316,17 @@
|
|
|
236
316
|
"runtime": "\"nodejs\""
|
|
237
317
|
}
|
|
238
318
|
},
|
|
319
|
+
{
|
|
320
|
+
"file": "app/api/reader/report/route.ts",
|
|
321
|
+
"hasDefault": false,
|
|
322
|
+
"exports": [
|
|
323
|
+
"POST"
|
|
324
|
+
],
|
|
325
|
+
"config": {
|
|
326
|
+
"dynamic": "\"force-dynamic\"",
|
|
327
|
+
"runtime": "\"nodejs\""
|
|
328
|
+
}
|
|
329
|
+
},
|
|
239
330
|
{
|
|
240
331
|
"file": "app/api/reader/retired/route.ts",
|
|
241
332
|
"hasDefault": false,
|
|
@@ -247,6 +338,17 @@
|
|
|
247
338
|
"runtime": "\"nodejs\""
|
|
248
339
|
}
|
|
249
340
|
},
|
|
341
|
+
{
|
|
342
|
+
"file": "app/api/reader/submit/route.ts",
|
|
343
|
+
"hasDefault": false,
|
|
344
|
+
"exports": [
|
|
345
|
+
"POST"
|
|
346
|
+
],
|
|
347
|
+
"config": {
|
|
348
|
+
"dynamic": "\"force-dynamic\"",
|
|
349
|
+
"runtime": "\"nodejs\""
|
|
350
|
+
}
|
|
351
|
+
},
|
|
250
352
|
{
|
|
251
353
|
"file": "app/api/reader/vote/route.ts",
|
|
252
354
|
"hasDefault": false,
|