@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/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
- assertApiKey(req.header("token"), opts.apiKey);
12
+ assertRequestApiKey(req, opts.apiKey, true);
11
13
  const body = req.body;
12
- if (!body?.id || !body?.title) {
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
- // read optional ?action=draft|publish
16
- const action = req.query.action;
17
- const saver = makeThumbSaver(opts.imageDir);
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
- if (!opts.getSiteInfo) {
27
- return res.status(404).json({ error: "Site info not configured" });
28
- }
29
- const apikey = req.query.apikey;
30
- assertApiKey(apikey, opts.apiKey);
31
- const info = typeof opts.getSiteInfo === "function"
32
- ? await Promise.resolve(opts.getSiteInfo())
33
- : opts.getSiteInfo;
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;
@@ -1 +1 @@
1
- {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
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: string;
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?: "blog" | "news";
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
- * Exact shape Monok expects from GET /info
28
- */
29
- export interface SiteInfo {
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: string;
41
- publicUrl: string;
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?: "draft" | "publish" | undefined;
102
+ action?: MonokArticleAction | undefined;
57
103
  }) => Promise<void> | void;
58
104
  /**
59
- * Optional. If not provided, GET /info will respond with 404.
105
+ * Optional. If omitted, GET /info returns a minimal Custom CMS fallback.
60
106
  */
61
- getSiteInfo?: SiteInfo | (() => Promise<SiteInfo> | SiteInfo);
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
@@ -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;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,SAAS,GAAG,UAAU,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,EAAE,CACT,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE;QACH,cAAc,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACvE,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;KAC1C,KACE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAE1B;;OAEG;IACH,WAAW,CAAC,EAAE,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;IAE9D;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,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,CAOpF;AAED,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,IAChC,OAAO,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAUzE"}
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
- if (!headerValue || !timingSafeEqual(headerValue, expected)) {
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
- const bytes = Buffer.from(data, "base64");
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}.png`;
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,WAAW,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC5D,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QACtC,iCAAiC;QACjC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAiB;IAC9C,OAAO,KAAK,EAAE,IAAa,EAAE,IAAa,EAA+B,EAAE;QACzE,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC/E,MAAM,QAAQ,GAAG,GAAG,QAAQ,MAAM,CAAC;QACnC,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
@@ -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, publicUrl, title, articleTextSample = "", thumbnailUrl }, opts) {
6
- const endpoint = (opts?.monokBaseUrl ?? "https://monok.news") + "/api/v1/linkback";
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, article: articleTextSample },
8
+ puff: { title },
9
9
  id: monokId,
10
- url: publicUrl,
11
- source: new URL(publicUrl).hostname,
12
- userId: opts?.userId ?? new URL(publicUrl).hostname + "-CUSTOM",
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: { "Content-Type": "application/json" },
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) {
@@ -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,SAAS,EAAE,KAAK,EAAE,iBAAiB,GAAG,EAAE,EAAE,YAAY,EAAmB,EACpF,IAAiD;IAEjD,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,YAAY,IAAI,oBAAoB,CAAC,GAAG,kBAAkB,CAAC;IACnF,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE;QAC3C,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,SAAS;QACd,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ;QACnC,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,GAAG,SAAS;QAC/D,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,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,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"}
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"}
@@ -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
- assertApiKey(req.header("token"), opts.apiKey);
12
+ assertRequestApiKey(req, opts.apiKey, true);
11
13
  const body = req.body;
12
- if (!body?.id || !body?.title) {
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
- // read optional ?action=draft|publish
16
- const action = req.query.action;
17
- const saver = makeThumbSaver(opts.imageDir);
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
- if (!opts.getSiteInfo) {
27
- return res.status(404).json({ error: "Site info not configured" });
28
- }
29
- const apikey = req.query.apikey;
30
- assertApiKey(apikey, opts.apiKey);
31
- const info = typeof opts.getSiteInfo === "function"
32
- ? await Promise.resolve(opts.getSiteInfo())
33
- : opts.getSiteInfo;
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;