@monok/server 1.2.0 → 1.4.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/AGENTS.md +318 -0
- package/README.md +58 -15
- package/dist/client.d.ts +2 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +12 -7
- package/dist/client.js.map +1 -1
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js +115 -15
- package/dist/express.js.map +1 -1
- package/dist/types.d.ts +70 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +40 -4
- package/dist/utils.js.map +1 -1
- package/dist-cjs/client.js +12 -7
- package/dist-cjs/client.js.map +1 -1
- package/dist-cjs/express.js +115 -15
- package/dist-cjs/express.js.map +1 -1
- package/dist-cjs/utils.js +40 -4
- package/dist-cjs/utils.js.map +1 -1
- package/package.json +3 -1
package/dist/express.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Router, json } from "express";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { assertApiKey, makeThumbSaver } from "./utils.js";
|
|
4
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
3
5
|
export function createMonokRouter(opts) {
|
|
4
6
|
const r = Router();
|
|
5
7
|
const base = opts.basePath ?? "";
|
|
@@ -7,34 +9,132 @@ export function createMonokRouter(opts) {
|
|
|
7
9
|
r.use(json({ limit: "20mb" }));
|
|
8
10
|
// POST /push
|
|
9
11
|
r.post(base + "/push", wrap(async (req, res) => {
|
|
10
|
-
|
|
12
|
+
assertRequestApiKey(req, opts.apiKey, true);
|
|
11
13
|
const body = req.body;
|
|
12
|
-
if (
|
|
14
|
+
if (body?.id === undefined
|
|
15
|
+
|| body.id === null
|
|
16
|
+
|| (typeof body.id === "string" && body.id.trim() === "")
|
|
17
|
+
|| typeof body.title !== "string"
|
|
18
|
+
|| body.title.trim() === "") {
|
|
13
19
|
return res.status(400).json({ error: "Invalid payload" });
|
|
14
20
|
}
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
const
|
|
21
|
+
// Monok's API integration sends action in the body. Retain query support
|
|
22
|
+
// for older callers and give an explicit query value precedence.
|
|
23
|
+
const action = parseAction(firstQueryValue(req.query.action) ?? body.action);
|
|
24
|
+
const saver = makeThumbSaver(opts.imageDir, opts.maxImageBytes);
|
|
18
25
|
await Promise.resolve(opts.onArticle(body, {
|
|
19
|
-
saveThumbImage: async (fileNameBase) => saver(fileNameBase ?? body.slug ?? body.id, body.thumbImageData),
|
|
26
|
+
saveThumbImage: async (fileNameBase) => saver(fileNameBase ?? body.slug ?? String(body.id), body.thumbImageData),
|
|
20
27
|
action
|
|
21
28
|
}));
|
|
22
|
-
res.json({ ok: true, monokId: body.id });
|
|
29
|
+
res.json({ ok: true, status: "accepted", monokId: body.id });
|
|
23
30
|
}));
|
|
24
31
|
// GET /info
|
|
25
32
|
r.get(base + "/info", wrap(async (req, res) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
setPrivateNoCacheHeaders(res);
|
|
34
|
+
assertRequestApiKey(req, opts.apiKey);
|
|
35
|
+
const configuredInfo = !opts.getSiteInfo
|
|
36
|
+
? undefined
|
|
37
|
+
: typeof opts.getSiteInfo === "function"
|
|
38
|
+
? await Promise.resolve(opts.getSiteInfo())
|
|
39
|
+
: opts.getSiteInfo;
|
|
40
|
+
const info = configuredInfo
|
|
41
|
+
? { ...configuredInfo, version: PACKAGE_VERSION }
|
|
42
|
+
: defaultSiteInfo();
|
|
34
43
|
res.json(info);
|
|
35
44
|
}));
|
|
45
|
+
// GET /postfilter
|
|
46
|
+
r.get(base + "/postfilter", wrap(async (req, res) => {
|
|
47
|
+
setPrivateNoCacheHeaders(res);
|
|
48
|
+
if (!opts.searchArticles) {
|
|
49
|
+
return res.status(404).json({ error: "Article search not configured" });
|
|
50
|
+
}
|
|
51
|
+
assertRequestApiKey(req, opts.apiKey);
|
|
52
|
+
const amount = parsePositiveInteger(firstQueryValue(req.query.amount), 10, "amount");
|
|
53
|
+
const page = parsePositiveInteger(firstQueryValue(req.query.page), 1, "page");
|
|
54
|
+
const query = {
|
|
55
|
+
postType: firstQueryValue(req.query.post_type)?.trim() || "post",
|
|
56
|
+
amount,
|
|
57
|
+
page,
|
|
58
|
+
...optionalTrimmed("section", firstQueryValue(req.query.section)),
|
|
59
|
+
...optionalTrimmed("tag", firstQueryValue(req.query.tag)),
|
|
60
|
+
...optionalTrimmed("query", firstQueryValue(req.query.q))
|
|
61
|
+
};
|
|
62
|
+
const articles = await Promise.resolve(opts.searchArticles(query));
|
|
63
|
+
if (!Array.isArray(articles)) {
|
|
64
|
+
throw httpError(500, "searchArticles must return an array");
|
|
65
|
+
}
|
|
66
|
+
res.json(articles);
|
|
67
|
+
}));
|
|
36
68
|
return r;
|
|
37
69
|
}
|
|
70
|
+
function assertRequestApiKey(req, expected, allowToken = false) {
|
|
71
|
+
const provided = ((allowToken ? req.header("token") : undefined)
|
|
72
|
+
?? req.header("x-api-key")
|
|
73
|
+
?? firstQueryValue(req.query.apikey));
|
|
74
|
+
assertApiKey(provided, expected);
|
|
75
|
+
}
|
|
76
|
+
function parseAction(value) {
|
|
77
|
+
if (value === undefined || value === null || value === "")
|
|
78
|
+
return undefined;
|
|
79
|
+
if (value === "draft" || value === "publish")
|
|
80
|
+
return value;
|
|
81
|
+
throw httpError(400, "action must be either draft or publish");
|
|
82
|
+
}
|
|
83
|
+
function parsePositiveInteger(value, fallback, name) {
|
|
84
|
+
if (value === undefined || value === "")
|
|
85
|
+
return fallback;
|
|
86
|
+
if (!/^\d+$/.test(value))
|
|
87
|
+
throw httpError(400, `${name} must be a positive integer`);
|
|
88
|
+
const parsed = Number(value);
|
|
89
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
90
|
+
throw httpError(400, `${name} must be a positive integer`);
|
|
91
|
+
}
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
function firstQueryValue(value) {
|
|
95
|
+
if (typeof value === "string")
|
|
96
|
+
return value;
|
|
97
|
+
if (Array.isArray(value) && typeof value[0] === "string")
|
|
98
|
+
return value[0];
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
function optionalTrimmed(key, value) {
|
|
102
|
+
const trimmed = value?.trim();
|
|
103
|
+
return trimmed ? { [key]: trimmed } : {};
|
|
104
|
+
}
|
|
105
|
+
function setPrivateNoCacheHeaders(res) {
|
|
106
|
+
res.set({
|
|
107
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, private",
|
|
108
|
+
"Pragma": "no-cache",
|
|
109
|
+
"Expires": "0",
|
|
110
|
+
"Surrogate-Control": "no-store",
|
|
111
|
+
"Vary": "Accept-Encoding"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function defaultSiteInfo() {
|
|
115
|
+
return {
|
|
116
|
+
js_scripts: "Disabled",
|
|
117
|
+
version: PACKAGE_VERSION,
|
|
118
|
+
name: "Custom CMS",
|
|
119
|
+
logo: "",
|
|
120
|
+
categories: [],
|
|
121
|
+
post_types: ["post"],
|
|
122
|
+
tags: [],
|
|
123
|
+
authors: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function readPackageVersion() {
|
|
127
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
128
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
129
|
+
throw new Error("@monok/server package version is missing");
|
|
130
|
+
}
|
|
131
|
+
return packageJson.version;
|
|
132
|
+
}
|
|
133
|
+
function httpError(status, message) {
|
|
134
|
+
const error = new Error(message);
|
|
135
|
+
error.status = status;
|
|
136
|
+
return error;
|
|
137
|
+
}
|
|
38
138
|
function wrap(fn) {
|
|
39
139
|
return (req, res, next) => fn(req, res, next).catch((err) => {
|
|
40
140
|
const status = typeof err?.status === "number" ? err.status : 500;
|
package/dist/express.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAkD,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAkD,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQvC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE1D,MAAM,eAAe,GAAG,kBAAkB,EAAE,CAAC;AAE7C,MAAM,UAAU,iBAAiB,CAAC,IAAyB;IACzD,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC;IACnB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IAEjC,kEAAkE;IAClE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAE/B,aAAa;IACb,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAChE,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAE5C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAoB,CAAC;QACtC,IACE,IAAI,EAAE,EAAE,KAAK,SAAS;eACnB,IAAI,CAAC,EAAE,KAAK,IAAI;eAChB,CAAC,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;eACtD,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;eAC9B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAC3B,CAAC;YACD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC5D,CAAC;QAED,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;QAE7E,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAChE,MAAM,OAAO,CAAC,OAAO,CACnB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;YACnB,cAAc,EAAE,KAAK,EAAE,YAAqB,EAAE,EAAE,CAC9C,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC;YAC1E,MAAM;SACP,CAAC,CACH,CAAC;QAEF,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC,CAAC;IAEJ,YAAY;IACZ,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAC/D,wBAAwB,CAAC,GAAG,CAAC,CAAC;QAC9B,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEtC,MAAM,cAAc,GAAG,CAAC,IAAI,CAAC,WAAW;YACtC,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU;gBACtC,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC3C,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;QACvB,MAAM,IAAI,GAAa,cAAc;YACnC,CAAC,CAAC,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE;YACjD,CAAC,CAAC,eAAe,EAAE,CAAC;QAEtB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC,CAAC;IAEJ,kBAAkB;IAClB,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,aAAa,EAAE,IAAI,CAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QACrE,wBAAwB,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAC;QAC1E,CAAC;QAED,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEtC,MAAM,MAAM,GAAG,oBAAoB,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,oBAAoB,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC9E,MAAM,KAAK,GAAoB;YAC7B,QAAQ,EAAE,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,MAAM;YAChE,MAAM;YACN,IAAI;YACJ,GAAG,eAAe,CAAC,SAAS,EAAE,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACjE,GAAG,eAAe,CAAC,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzD,GAAG,eAAe,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC1D,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,MAAM,SAAS,CAAC,GAAG,EAAE,qCAAqC,CAAC,CAAC;QAC9D,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC,CAAC;IAEJ,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAY,EAAE,QAAgB,EAAE,UAAU,GAAG,KAAK;IAC7E,MAAM,QAAQ,GAAG,CACf,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;WAC3C,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;WACvB,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CACrC,CAAC;IACF,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC5E,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC3D,MAAM,SAAS,CAAC,GAAG,EAAE,wCAAwC,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAyB,EAAE,QAAgB,EAAE,IAAY;IACrF,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IACzD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,6BAA6B,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,6BAA6B,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAqB,GAAQ,EAAE,KAAc;IACnE,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAyB,CAAC,CAAC,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,wBAAwB,CAAC,GAAa;IAC7C,GAAG,CAAC,GAAG,CAAC;QACN,eAAe,EAAE,yDAAyD;QAC1E,QAAQ,EAAE,UAAU;QACpB,SAAS,EAAE,GAAG;QACd,mBAAmB,EAAE,UAAU;QAC/B,MAAM,EAAE,iBAAiB;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe;IACtB,OAAO;QACL,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,eAAe;QACxB,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE;QACR,UAAU,EAAE,EAAE;QACd,UAAU,EAAE,CAAC,MAAM,CAAC;QACpB,IAAI,EAAE,EAAE;QACR,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CACzC,CAAC;IAE3B,IAAI,OAAO,WAAW,CAAC,OAAO,KAAK,QAAQ,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC;AAC7B,CAAC;AAED,SAAS,SAAS,CAAC,MAAc,EAAE,OAAe;IAChD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,CAA+B,CAAC;IAC/D,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,IAAI,CACX,EAAyE;IAEzE,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE,CACzD,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAQ,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,OAAO,GAAG,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;QAClE,IAAI,MAAM,IAAI,GAAG;YAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,IAAI,cAAc,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
export type VFImage = Record<string, unknown>;
|
|
2
|
+
export type MonokArticleId = string | number;
|
|
3
|
+
export type MonokArticleAction = "draft" | "publish";
|
|
4
|
+
export interface MonokArticleAuthor {
|
|
5
|
+
id: string | number;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
export interface MonokArticleCategory {
|
|
9
|
+
name: string;
|
|
10
|
+
weight?: number;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
2
13
|
export interface MonokArticle {
|
|
3
14
|
title: string;
|
|
4
15
|
shortTitle?: string;
|
|
@@ -6,15 +17,21 @@ export interface MonokArticle {
|
|
|
6
17
|
content?: string;
|
|
7
18
|
text?: string;
|
|
8
19
|
slug?: string;
|
|
9
|
-
id:
|
|
20
|
+
id: MonokArticleId;
|
|
21
|
+
action?: MonokArticleAction;
|
|
22
|
+
author?: MonokArticleAuthor;
|
|
23
|
+
categories?: MonokArticleCategory[];
|
|
24
|
+
thumbnail?: string;
|
|
10
25
|
thumbImageData?: string;
|
|
26
|
+
thumbAlt?: string;
|
|
11
27
|
thumbImageDescription?: string;
|
|
12
28
|
language?: string;
|
|
13
|
-
type?:
|
|
29
|
+
type?: string;
|
|
14
30
|
keywords?: string[];
|
|
15
|
-
tags?: string[];
|
|
31
|
+
tags?: string[] | null;
|
|
16
32
|
vfImage?: VFImage;
|
|
17
|
-
sections?: string;
|
|
33
|
+
sections?: string[];
|
|
34
|
+
[key: string]: unknown;
|
|
18
35
|
}
|
|
19
36
|
export interface SiteAuthor {
|
|
20
37
|
name: string;
|
|
@@ -23,24 +40,53 @@ export interface SiteAuthor {
|
|
|
23
40
|
avatar: string;
|
|
24
41
|
roles: string[];
|
|
25
42
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
export interface SiteLanguage {
|
|
44
|
+
id?: string | number;
|
|
45
|
+
name?: string;
|
|
46
|
+
slug?: string;
|
|
47
|
+
locale?: string;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
export type SiteLanguages = SiteLanguage[] | Record<string, SiteLanguage>;
|
|
51
|
+
interface SiteInfoFields {
|
|
30
52
|
js_scripts: "Enabled" | "Disabled";
|
|
31
|
-
version: string;
|
|
32
53
|
name: string;
|
|
33
54
|
logo: string;
|
|
34
55
|
categories: string[];
|
|
35
56
|
post_types: string[];
|
|
36
57
|
tags: string[];
|
|
37
58
|
authors: SiteAuthor[];
|
|
59
|
+
languages?: SiteLanguages;
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
}
|
|
62
|
+
/** Site metadata supplied by the host. The router owns the package version. */
|
|
63
|
+
export interface SiteInfoInput extends SiteInfoFields {
|
|
64
|
+
version?: never;
|
|
65
|
+
}
|
|
66
|
+
/** Exact response shape Monok receives from GET /info. */
|
|
67
|
+
export interface SiteInfo extends SiteInfoFields {
|
|
68
|
+
version: string;
|
|
69
|
+
}
|
|
70
|
+
export interface PostFilterQuery {
|
|
71
|
+
postType: string;
|
|
72
|
+
section?: string;
|
|
73
|
+
tag?: string;
|
|
74
|
+
query?: string;
|
|
75
|
+
amount: number;
|
|
76
|
+
page: number;
|
|
77
|
+
}
|
|
78
|
+
/** Exact result shape consumed by Monok's covered-article and link search. */
|
|
79
|
+
export interface PostFilterResult {
|
|
80
|
+
canonical_url: string;
|
|
81
|
+
post_id: string | number;
|
|
82
|
+
title: string | null;
|
|
83
|
+
description: string | null;
|
|
38
84
|
}
|
|
39
85
|
export interface LinkbackPayload {
|
|
40
|
-
monokId:
|
|
41
|
-
|
|
86
|
+
monokId: MonokArticleId;
|
|
87
|
+
externalId?: string | number;
|
|
88
|
+
url: string;
|
|
42
89
|
title: string;
|
|
43
|
-
articleTextSample?: string;
|
|
44
90
|
thumbnailUrl?: string;
|
|
45
91
|
}
|
|
46
92
|
export interface CreateRouterOptions {
|
|
@@ -53,19 +99,28 @@ export interface CreateRouterOptions {
|
|
|
53
99
|
*/
|
|
54
100
|
onArticle: (article: MonokArticle, ctx: {
|
|
55
101
|
saveThumbImage: (fileNameBase?: string) => Promise<string | undefined>;
|
|
56
|
-
action?:
|
|
102
|
+
action?: MonokArticleAction | undefined;
|
|
57
103
|
}) => Promise<void> | void;
|
|
58
104
|
/**
|
|
59
|
-
* Optional. If
|
|
105
|
+
* Optional. If omitted, GET /info returns a minimal Custom CMS fallback.
|
|
60
106
|
*/
|
|
61
|
-
getSiteInfo?:
|
|
107
|
+
getSiteInfo?: SiteInfoInput | (() => Promise<SiteInfoInput> | SiteInfoInput);
|
|
108
|
+
/**
|
|
109
|
+
* Optional. Enables GET /postfilter for article discovery and search.
|
|
110
|
+
*/
|
|
111
|
+
searchArticles?: (query: PostFilterQuery) => Promise<PostFilterResult[]> | PostFilterResult[];
|
|
62
112
|
/**
|
|
63
113
|
* Directory where a decoded `thumbImageData` will be saved if present.
|
|
64
114
|
*/
|
|
65
115
|
imageDir?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Maximum decoded JPEG size. Defaults to 15 MiB.
|
|
118
|
+
*/
|
|
119
|
+
maxImageBytes?: number;
|
|
66
120
|
/**
|
|
67
121
|
* Customize route base path (defaults to "").
|
|
68
122
|
*/
|
|
69
123
|
basePath?: string;
|
|
70
124
|
}
|
|
125
|
+
export {};
|
|
71
126
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare function timingSafeEqual(a: string, b: string): boolean;
|
|
2
2
|
export declare function assertApiKey(headerValue: string | undefined, expected: string): void;
|
|
3
|
-
export declare function makeThumbSaver(imageDir?: string): (base?: string, data?: string) => Promise<string | undefined>;
|
|
3
|
+
export declare function makeThumbSaver(imageDir?: string, maxImageBytes?: number): (base?: string, data?: string) => Promise<string | undefined>;
|
|
4
4
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK7D;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CASpF;AAED,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,aAAa,SAA0B,IACzE,OAAO,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4BzE"}
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs/promises";
|
|
4
|
+
const DEFAULT_MAX_IMAGE_BYTES = 15 * 1024 * 1024;
|
|
4
5
|
export function timingSafeEqual(a, b) {
|
|
5
6
|
const abuf = Buffer.from(a);
|
|
6
7
|
const bbuf = Buffer.from(b);
|
|
@@ -9,24 +10,59 @@ export function timingSafeEqual(a, b) {
|
|
|
9
10
|
return crypto.timingSafeEqual(abuf, bbuf);
|
|
10
11
|
}
|
|
11
12
|
export function assertApiKey(headerValue, expected) {
|
|
12
|
-
|
|
13
|
+
const candidate = headerValue?.trim();
|
|
14
|
+
const configured = expected.trim();
|
|
15
|
+
if (!candidate || !configured || !timingSafeEqual(candidate, configured)) {
|
|
13
16
|
const err = new Error("Unauthorized");
|
|
14
17
|
// @ts-expect-error custom status
|
|
15
18
|
err.status = 401;
|
|
16
19
|
throw err;
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
|
-
export function makeThumbSaver(imageDir) {
|
|
22
|
+
export function makeThumbSaver(imageDir, maxImageBytes = DEFAULT_MAX_IMAGE_BYTES) {
|
|
20
23
|
return async (base, data) => {
|
|
21
24
|
if (!imageDir || !data)
|
|
22
25
|
return undefined;
|
|
23
|
-
|
|
26
|
+
if (!Number.isSafeInteger(maxImageBytes) || maxImageBytes <= 0) {
|
|
27
|
+
throw httpError(500, "maxImageBytes must be a positive safe integer");
|
|
28
|
+
}
|
|
29
|
+
const encoded = normalizeJpegBase64(data);
|
|
30
|
+
const estimatedBytes = Math.floor((encoded.length * 3) / 4);
|
|
31
|
+
if (estimatedBytes > maxImageBytes) {
|
|
32
|
+
throw httpError(413, `Thumbnail exceeds the ${maxImageBytes}-byte limit`);
|
|
33
|
+
}
|
|
34
|
+
const bytes = Buffer.from(encoded, "base64");
|
|
35
|
+
if (bytes.length === 0 || bytes.length > maxImageBytes) {
|
|
36
|
+
throw httpError(413, `Thumbnail exceeds the ${maxImageBytes}-byte limit`);
|
|
37
|
+
}
|
|
38
|
+
if (bytes.length < 3 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes[2] !== 0xff) {
|
|
39
|
+
throw httpError(400, "thumbImageData is not a valid JPEG image");
|
|
40
|
+
}
|
|
24
41
|
const safeName = (base || crypto.randomUUID()).replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
25
|
-
const fileName = `${safeName}.
|
|
42
|
+
const fileName = `${safeName}.jpg`;
|
|
26
43
|
await fs.mkdir(imageDir, { recursive: true });
|
|
27
44
|
const full = path.join(imageDir, fileName);
|
|
28
45
|
await fs.writeFile(full, bytes);
|
|
29
46
|
return full;
|
|
30
47
|
};
|
|
31
48
|
}
|
|
49
|
+
function normalizeJpegBase64(data) {
|
|
50
|
+
const trimmed = data.trim();
|
|
51
|
+
const dataUrl = /^data:image\/(?:jpeg|jpg);base64,/i;
|
|
52
|
+
if (/^data:/i.test(trimmed) && !dataUrl.test(trimmed)) {
|
|
53
|
+
throw httpError(400, "thumbImageData must be a JPEG data URL");
|
|
54
|
+
}
|
|
55
|
+
const encoded = trimmed.replace(dataUrl, "").replace(/\s+/g, "");
|
|
56
|
+
if (encoded.length === 0
|
|
57
|
+
|| encoded.length % 4 === 1
|
|
58
|
+
|| !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) {
|
|
59
|
+
throw httpError(400, "thumbImageData is not valid base64");
|
|
60
|
+
}
|
|
61
|
+
return encoded;
|
|
62
|
+
}
|
|
63
|
+
function httpError(status, message) {
|
|
64
|
+
const error = new Error(message);
|
|
65
|
+
error.status = status;
|
|
66
|
+
return error;
|
|
67
|
+
}
|
|
32
68
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,aAAa,CAAC;AAE7B,MAAM,UAAU,eAAe,CAAC,CAAS,EAAE,CAAS;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,WAA+B,EAAE,QAAgB;IAC5E,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/dist-cjs/client.js
CHANGED
|
@@ -2,19 +2,24 @@ import fetch from "node-fetch";
|
|
|
2
2
|
/**
|
|
3
3
|
* Send a linkback to Monok after you publish the article.
|
|
4
4
|
*/
|
|
5
|
-
export async function sendMonokLinkback({ monokId,
|
|
6
|
-
const endpoint = (opts?.monokBaseUrl ?? "https://monok.
|
|
5
|
+
export async function sendMonokLinkback({ monokId, externalId, url, title, thumbnailUrl }, opts) {
|
|
6
|
+
const endpoint = (opts?.monokBaseUrl ?? "https://www.monok.com") + "/api/v1/linkback";
|
|
7
7
|
const payload = {
|
|
8
|
-
puff: { title
|
|
8
|
+
puff: { title },
|
|
9
9
|
id: monokId,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
...(externalId !== undefined && externalId !== null
|
|
11
|
+
? { externalId: String(externalId) }
|
|
12
|
+
: {}),
|
|
13
|
+
url,
|
|
14
|
+
source: new URL(url).hostname,
|
|
13
15
|
...(thumbnailUrl ? { thumbnail: thumbnailUrl } : {})
|
|
14
16
|
};
|
|
15
17
|
const resp = await fetch(endpoint, {
|
|
16
18
|
method: "POST",
|
|
17
|
-
headers: {
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
...(opts?.apiKey ? { "x-api-key": opts.apiKey } : {})
|
|
22
|
+
},
|
|
18
23
|
body: JSON.stringify(payload)
|
|
19
24
|
});
|
|
20
25
|
if (!resp.ok) {
|
package/dist-cjs/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAmB,EAClE,IAAiD;IAEjD,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,YAAY,IAAI,uBAAuB,CAAC,GAAG,kBAAkB,CAAC;IACtF,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,EAAE,KAAK,EAAE;QACf,EAAE,EAAE,OAAO;QACX,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI;YACjD,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE;YACpC,CAAC,CAAC,EAAE,CAAC;QACP,GAAG;QACH,MAAM,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ;QAC7B,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrD,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtD;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KAC9B,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC"}
|
package/dist-cjs/express.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Router, json } from "express";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { assertApiKey, makeThumbSaver } from "./utils.js";
|
|
4
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
3
5
|
export function createMonokRouter(opts) {
|
|
4
6
|
const r = Router();
|
|
5
7
|
const base = opts.basePath ?? "";
|
|
@@ -7,34 +9,132 @@ export function createMonokRouter(opts) {
|
|
|
7
9
|
r.use(json({ limit: "20mb" }));
|
|
8
10
|
// POST /push
|
|
9
11
|
r.post(base + "/push", wrap(async (req, res) => {
|
|
10
|
-
|
|
12
|
+
assertRequestApiKey(req, opts.apiKey, true);
|
|
11
13
|
const body = req.body;
|
|
12
|
-
if (
|
|
14
|
+
if (body?.id === undefined
|
|
15
|
+
|| body.id === null
|
|
16
|
+
|| (typeof body.id === "string" && body.id.trim() === "")
|
|
17
|
+
|| typeof body.title !== "string"
|
|
18
|
+
|| body.title.trim() === "") {
|
|
13
19
|
return res.status(400).json({ error: "Invalid payload" });
|
|
14
20
|
}
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
const
|
|
21
|
+
// Monok's API integration sends action in the body. Retain query support
|
|
22
|
+
// for older callers and give an explicit query value precedence.
|
|
23
|
+
const action = parseAction(firstQueryValue(req.query.action) ?? body.action);
|
|
24
|
+
const saver = makeThumbSaver(opts.imageDir, opts.maxImageBytes);
|
|
18
25
|
await Promise.resolve(opts.onArticle(body, {
|
|
19
|
-
saveThumbImage: async (fileNameBase) => saver(fileNameBase ?? body.slug ?? body.id, body.thumbImageData),
|
|
26
|
+
saveThumbImage: async (fileNameBase) => saver(fileNameBase ?? body.slug ?? String(body.id), body.thumbImageData),
|
|
20
27
|
action
|
|
21
28
|
}));
|
|
22
|
-
res.json({ ok: true, monokId: body.id });
|
|
29
|
+
res.json({ ok: true, status: "accepted", monokId: body.id });
|
|
23
30
|
}));
|
|
24
31
|
// GET /info
|
|
25
32
|
r.get(base + "/info", wrap(async (req, res) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
setPrivateNoCacheHeaders(res);
|
|
34
|
+
assertRequestApiKey(req, opts.apiKey);
|
|
35
|
+
const configuredInfo = !opts.getSiteInfo
|
|
36
|
+
? undefined
|
|
37
|
+
: typeof opts.getSiteInfo === "function"
|
|
38
|
+
? await Promise.resolve(opts.getSiteInfo())
|
|
39
|
+
: opts.getSiteInfo;
|
|
40
|
+
const info = configuredInfo
|
|
41
|
+
? { ...configuredInfo, version: PACKAGE_VERSION }
|
|
42
|
+
: defaultSiteInfo();
|
|
34
43
|
res.json(info);
|
|
35
44
|
}));
|
|
45
|
+
// GET /postfilter
|
|
46
|
+
r.get(base + "/postfilter", wrap(async (req, res) => {
|
|
47
|
+
setPrivateNoCacheHeaders(res);
|
|
48
|
+
if (!opts.searchArticles) {
|
|
49
|
+
return res.status(404).json({ error: "Article search not configured" });
|
|
50
|
+
}
|
|
51
|
+
assertRequestApiKey(req, opts.apiKey);
|
|
52
|
+
const amount = parsePositiveInteger(firstQueryValue(req.query.amount), 10, "amount");
|
|
53
|
+
const page = parsePositiveInteger(firstQueryValue(req.query.page), 1, "page");
|
|
54
|
+
const query = {
|
|
55
|
+
postType: firstQueryValue(req.query.post_type)?.trim() || "post",
|
|
56
|
+
amount,
|
|
57
|
+
page,
|
|
58
|
+
...optionalTrimmed("section", firstQueryValue(req.query.section)),
|
|
59
|
+
...optionalTrimmed("tag", firstQueryValue(req.query.tag)),
|
|
60
|
+
...optionalTrimmed("query", firstQueryValue(req.query.q))
|
|
61
|
+
};
|
|
62
|
+
const articles = await Promise.resolve(opts.searchArticles(query));
|
|
63
|
+
if (!Array.isArray(articles)) {
|
|
64
|
+
throw httpError(500, "searchArticles must return an array");
|
|
65
|
+
}
|
|
66
|
+
res.json(articles);
|
|
67
|
+
}));
|
|
36
68
|
return r;
|
|
37
69
|
}
|
|
70
|
+
function assertRequestApiKey(req, expected, allowToken = false) {
|
|
71
|
+
const provided = ((allowToken ? req.header("token") : undefined)
|
|
72
|
+
?? req.header("x-api-key")
|
|
73
|
+
?? firstQueryValue(req.query.apikey));
|
|
74
|
+
assertApiKey(provided, expected);
|
|
75
|
+
}
|
|
76
|
+
function parseAction(value) {
|
|
77
|
+
if (value === undefined || value === null || value === "")
|
|
78
|
+
return undefined;
|
|
79
|
+
if (value === "draft" || value === "publish")
|
|
80
|
+
return value;
|
|
81
|
+
throw httpError(400, "action must be either draft or publish");
|
|
82
|
+
}
|
|
83
|
+
function parsePositiveInteger(value, fallback, name) {
|
|
84
|
+
if (value === undefined || value === "")
|
|
85
|
+
return fallback;
|
|
86
|
+
if (!/^\d+$/.test(value))
|
|
87
|
+
throw httpError(400, `${name} must be a positive integer`);
|
|
88
|
+
const parsed = Number(value);
|
|
89
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
90
|
+
throw httpError(400, `${name} must be a positive integer`);
|
|
91
|
+
}
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
function firstQueryValue(value) {
|
|
95
|
+
if (typeof value === "string")
|
|
96
|
+
return value;
|
|
97
|
+
if (Array.isArray(value) && typeof value[0] === "string")
|
|
98
|
+
return value[0];
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
function optionalTrimmed(key, value) {
|
|
102
|
+
const trimmed = value?.trim();
|
|
103
|
+
return trimmed ? { [key]: trimmed } : {};
|
|
104
|
+
}
|
|
105
|
+
function setPrivateNoCacheHeaders(res) {
|
|
106
|
+
res.set({
|
|
107
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, private",
|
|
108
|
+
"Pragma": "no-cache",
|
|
109
|
+
"Expires": "0",
|
|
110
|
+
"Surrogate-Control": "no-store",
|
|
111
|
+
"Vary": "Accept-Encoding"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function defaultSiteInfo() {
|
|
115
|
+
return {
|
|
116
|
+
js_scripts: "Disabled",
|
|
117
|
+
version: PACKAGE_VERSION,
|
|
118
|
+
name: "Custom CMS",
|
|
119
|
+
logo: "",
|
|
120
|
+
categories: [],
|
|
121
|
+
post_types: ["post"],
|
|
122
|
+
tags: [],
|
|
123
|
+
authors: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function readPackageVersion() {
|
|
127
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
128
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
129
|
+
throw new Error("@monok/server package version is missing");
|
|
130
|
+
}
|
|
131
|
+
return packageJson.version;
|
|
132
|
+
}
|
|
133
|
+
function httpError(status, message) {
|
|
134
|
+
const error = new Error(message);
|
|
135
|
+
error.status = status;
|
|
136
|
+
return error;
|
|
137
|
+
}
|
|
38
138
|
function wrap(fn) {
|
|
39
139
|
return (req, res, next) => fn(req, res, next).catch((err) => {
|
|
40
140
|
const status = typeof err?.status === "number" ? err.status : 500;
|