@monok/server 1.3.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 ADDED
@@ -0,0 +1,318 @@
1
+ # Implementing `@monok/server` in a host application
2
+
3
+ This file is a self-contained implementation contract for AI coding assistants
4
+ and developers integrating Monok with an Express application or CMS. Follow the
5
+ requirements below rather than inventing alternate endpoint paths or response
6
+ shapes.
7
+
8
+ ## Objective
9
+
10
+ By default, expose a public HTTPS Monok integration at these routes:
11
+
12
+ - `POST /api/monok/push`
13
+ - `GET /api/monok/info`
14
+ - `GET /api/monok/postfilter` when article discovery is supported
15
+
16
+ Use `createMonokRouter` from `@monok/server`. Mount it at `/api/monok` for the
17
+ default API base, or at an intentional custom base such as
18
+ `/something/specialAPI/monok`. When connecting the integration, enter either the
19
+ site origin to select the default or the complete custom base URL. Monok appends
20
+ `/info`, `/push`, and `/postfilter` to that base.
21
+
22
+ ## Required implementation
23
+
24
+ Install the package and Express:
25
+
26
+ ```bash
27
+ npm install @monok/server express
28
+ ```
29
+
30
+ Create one router with the same secret API key configured for the Monok
31
+ workspace:
32
+
33
+ ```ts
34
+ import express from "express";
35
+ import {
36
+ createMonokRouter,
37
+ sendMonokLinkback,
38
+ type MonokArticle,
39
+ type PostFilterQuery,
40
+ type PostFilterResult,
41
+ type SiteInfoInput
42
+ } from "@monok/server";
43
+
44
+ const app = express();
45
+
46
+ app.use(
47
+ "/api/monok",
48
+ createMonokRouter({
49
+ apiKey: process.env.MONOK_API_KEY!,
50
+ imageDir: "./uploads/monok",
51
+ getSiteInfo: async (): Promise<SiteInfoInput> => loadSiteInfo(),
52
+ searchArticles: async (
53
+ filters: PostFilterQuery
54
+ ): Promise<PostFilterResult[]> => searchPublishedArticles(filters),
55
+ onArticle: async (article, context) => {
56
+ await upsertMonokArticle(article, context);
57
+ }
58
+ })
59
+ );
60
+ ```
61
+
62
+ `onArticle` and the application's persistence work are always required.
63
+ `getSiteInfo` is optional. The router always exposes authenticated `/info`; when
64
+ the callback is omitted it returns minimal `Custom CMS` metadata so the standard
65
+ Monok dashboard connection and sync flow can still detect the installation.
66
+ `searchArticles` and `imageDir` are optional; omitting article search disables
67
+ covered-article and backlink discovery.
68
+
69
+ Do not add another JSON body parser solely for these routes. The router already
70
+ parses JSON with a 20 MB limit. A reverse proxy in front of the application must
71
+ also permit the request size.
72
+
73
+ ## Receiving and storing articles
74
+
75
+ `onArticle` receives:
76
+
77
+ ```ts
78
+ async function onArticle(
79
+ article: MonokArticle,
80
+ context: {
81
+ action?: "draft" | "publish";
82
+ saveThumbImage(fileNameBase?: string): Promise<string | undefined>;
83
+ }
84
+ ): Promise<void>
85
+ ```
86
+
87
+ The host must:
88
+
89
+ 1. Validate any application-specific constraints.
90
+ 2. Upsert by `article.id`, or enqueue an idempotent job keyed by `article.id`, so
91
+ repeated delivery cannot create duplicate content.
92
+ 3. Preserve `article.id` with the CMS record or queued job for later linkbacks
93
+ and updates.
94
+ 4. Store `article.content` as the rendered HTML body when processing the job.
95
+ 5. Apply the supplied slug, author, categories, sections, tags, language, and
96
+ CMS post type when the host supports them.
97
+ 6. Treat the requested action as draft unless `context.action === "publish"`.
98
+ 7. Before returning, await either durable CMS persistence or durable queue
99
+ acceptance. Do not acknowledge an in-memory, fire-and-forget task.
100
+ 8. Throw when persistence or durable queueing fails.
101
+
102
+ The draft/publish action normally arrives in the JSON body. The package also
103
+ supports the legacy query parameter. Do not read `article.action` directly when
104
+ deciding publication state; use the validated `context.action` value.
105
+
106
+ `onArticle` returns `void` or `Promise<void>`. It must not construct an HTTP
107
+ response. After the callback durably saves or queues the work, the package
108
+ returns:
109
+
110
+ ```json
111
+ {
112
+ "ok": true,
113
+ "status": "accepted",
114
+ "monokId": "the-article-id"
115
+ }
116
+ ```
117
+
118
+ This response acknowledges delivery only. It does not promise that a draft URL,
119
+ CMS record ID, thumbnail URL, or public canonical URL already exists. Custom CMS
120
+ implementations may render, moderate, deploy, or publish asynchronously.
121
+
122
+ Thrown errors become JSON error responses. An error with a numeric `status`
123
+ property uses that HTTP status; other errors return `500`.
124
+
125
+ ## Article payload
126
+
127
+ The important `MonokArticle` fields are:
128
+
129
+ ```ts
130
+ interface MonokArticle {
131
+ id: string | number;
132
+ title: string;
133
+ shortTitle?: string;
134
+ description?: string;
135
+ content?: string;
136
+ text?: string;
137
+ slug?: string;
138
+ action?: "draft" | "publish";
139
+ author?: { id: string | number; name: string };
140
+ categories?: Array<{ name: string; weight?: number }>;
141
+ sections?: string[];
142
+ tags?: string[] | null;
143
+ language?: string;
144
+ type?: string;
145
+ thumbnail?: string;
146
+ thumbImageData?: string;
147
+ thumbAlt?: string;
148
+ thumbImageDescription?: string;
149
+ keywords?: string[];
150
+ }
151
+ ```
152
+
153
+ Treat unknown additional fields as forward-compatible metadata. Do not reject a
154
+ payload merely because it contains fields added by a newer Monok version.
155
+
156
+ ## JPEG thumbnail handling
157
+
158
+ Monok sends `thumbImageData` as JPEG base64. It is normally raw base64, while a
159
+ legacy path can wrap it in `data:image/jpeg;base64,...`.
160
+
161
+ When `imageDir` is configured, call `await context.saveThumbImage()` to validate
162
+ and save it. The return value is the local `.jpg` path, or `undefined` when no
163
+ image directory or image data was provided. The default decoded-image limit is
164
+ 15 MiB and can be changed with `maxImageBytes`.
165
+
166
+ Saving a file is not the same as making it public. If the CMS has a media API or
167
+ object storage, the host may instead process `article.thumbImageData` itself.
168
+ Preserve `thumbAlt` or fall back to `thumbImageDescription`, then the title, for
169
+ accessible alt text.
170
+
171
+ Do not label the saved image as PNG and do not log its base64 data.
172
+
173
+ ## Site information
174
+
175
+ Implement `getSiteInfo` when Monok should offer real CMS authors, categories,
176
+ tags, post types, or languages. Return this shape:
177
+
178
+ ```ts
179
+ const info: SiteInfoInput = {
180
+ js_scripts: "Enabled",
181
+ name: "Site name",
182
+ logo: "https://example.com/logo.jpg",
183
+ categories: ["News", "Analysis"],
184
+ post_types: ["post", "page"],
185
+ tags: ["Technology", "Business"],
186
+ authors: [
187
+ {
188
+ id: "author-1",
189
+ name: "Jane Doe",
190
+ profile: "Editor",
191
+ avatar: "https://example.com/jane.jpg",
192
+ roles: ["editor"]
193
+ }
194
+ ],
195
+ languages: {
196
+ en: { name: "English", slug: "en", locale: "en_US" }
197
+ }
198
+ };
199
+ ```
200
+
201
+ Do not return `version` from `getSiteInfo`. The router always injects the
202
+ installed `@monok/server` package version into the `/info` response.
203
+
204
+ Return empty arrays when a supported collection has no entries. Values may be
205
+ loaded dynamically on every request. The router sends private no-cache headers.
206
+ If `getSiteInfo` is omitted, `/info` returns this authenticated fallback:
207
+
208
+ ```json
209
+ {
210
+ "js_scripts": "Disabled",
211
+ "version": "1.4.0",
212
+ "name": "Custom CMS",
213
+ "logo": "",
214
+ "categories": [],
215
+ "post_types": ["post"],
216
+ "tags": [],
217
+ "authors": []
218
+ }
219
+ ```
220
+
221
+ ## Article discovery with `/postfilter`
222
+
223
+ Implement `searchArticles` to expose published host content to Monok. The
224
+ callback receives normalized values:
225
+
226
+ ```ts
227
+ interface PostFilterQuery {
228
+ postType: string; // defaults to "post"
229
+ section?: string;
230
+ tag?: string;
231
+ query?: string;
232
+ amount: number; // defaults to 10
233
+ page: number; // defaults to 1
234
+ }
235
+ ```
236
+
237
+ Apply all supplied filters, return only published/public articles, and paginate
238
+ deterministically. Return an array using these exact snake-case keys:
239
+
240
+ ```ts
241
+ const result: PostFilterResult = {
242
+ canonical_url: "https://example.com/articles/example",
243
+ post_id: "cms-record-id",
244
+ title: "Example article",
245
+ description: "A short plain-text description"
246
+ };
247
+ ```
248
+
249
+ Return `[]` when nothing matches. `title` and `description` may be `null`.
250
+ Do not return drafts, private content, article bodies, credentials, or internal
251
+ metadata. If `searchArticles` is omitted, `/postfilter` returns `404`.
252
+
253
+ ## Authentication and cache behavior
254
+
255
+ The router performs authentication. The host should not duplicate it inside
256
+ callbacks.
257
+
258
+ - `/push` accepts Monok's `token` header, `X-API-Key`, or legacy `?apikey=`.
259
+ - `/info` and `/postfilter` accept `X-API-Key` or legacy `?apikey=`.
260
+ - Comparisons are timing-safe.
261
+ - `/info` and `/postfilter` responses include private no-cache headers.
262
+
263
+ Keep `MONOK_API_KEY` server-side. Never expose it to browser JavaScript, commit
264
+ it, include it in a URL shown to users, or log it. Prefer `X-API-Key` over query
265
+ authentication for custom calls because URLs are commonly logged by proxies.
266
+
267
+ ## Linkback after publication
268
+
269
+ When an article is actually published, notify Monok only after the public
270
+ canonical URL exists. This applies both to a draft published later and to an
271
+ asynchronous `action: "publish"` job:
272
+
273
+ ```ts
274
+ await sendMonokLinkback({
275
+ monokId: storedMonokId,
276
+ externalId: cmsArticleId,
277
+ url: canonicalUrl,
278
+ title,
279
+ thumbnailUrl: publicThumbnailUrl
280
+ }, {
281
+ apiKey: process.env.MONOK_API_KEY
282
+ });
283
+ ```
284
+
285
+ Call this from the host's real publication lifecycle, not merely when `/push`
286
+ returns or when a job enters a queue. The linkback is the authoritative signal
287
+ that publication completed and a canonical URL exists. A retry should be safe.
288
+ When useful, `externalId` is the stable article ID assigned by the host CMS; it
289
+ is optional because the canonical URL also identifies the publication. When
290
+ supplied, `thumbnailUrl` is the public thumbnail URL.
291
+
292
+ ## Verification checklist
293
+
294
+ Before declaring the integration complete, verify all applicable items:
295
+
296
+ - `GET /api/monok/info` returns `401` without a key.
297
+ - The same route succeeds with `X-API-Key` and with legacy `?apikey=`.
298
+ - Info responses contain `Cache-Control: no-store`.
299
+ - `POST /api/monok/push` accepts a body containing `action: "draft"`.
300
+ - A body containing `action: "publish"` is durably persisted or queued with the
301
+ requested publication intent.
302
+ - Successful pushes return `ok: true`, `status: "accepted"`, and the same
303
+ `monokId`, without requiring a canonical URL.
304
+ - Repeating the same `article.id` updates one record instead of duplicating it.
305
+ - Invalid action values return `400`.
306
+ - JPEG data saves as `.jpg`, and invalid or oversized image data is rejected.
307
+ - `/postfilter` applies search, type, section, tag, amount, and page filters.
308
+ - `/postfilter` returns exact `canonical_url`, `post_id`, `title`, and
309
+ `description` fields.
310
+ - Synchronous or asynchronous publication sends a linkback only after the public
311
+ canonical URL exists.
312
+ - The integration is reachable through its production HTTPS reverse proxy.
313
+
314
+ Do not claim completion based only on TypeScript compilation. Exercise the HTTP
315
+ routes and confirm one durable draft or draft job, one publication or publication
316
+ job, one repeated delivery, and one discovery query against the host
317
+ application's persistence layer. For asynchronous publishing, also complete one
318
+ job and verify its eventual linkback.
package/README.md CHANGED
@@ -5,6 +5,9 @@
5
5
  Monok server integration helpers for Node.js/Express.
6
6
  Easily connect your backend to Monok to receive articles, expose site info, and send linkbacks.
7
7
 
8
+ Using an AI coding assistant? Give it [`AGENTS.md`](./AGENTS.md). The guide contains
9
+ the complete host-implementation contract, response shapes, and verification checklist.
10
+
8
11
  ---
9
12
 
10
13
  ## ⚡ Quick Start
@@ -16,7 +19,7 @@ import { createMonokRouter, type MonokArticle } from "@monok/server";
16
19
  const app = express();
17
20
 
18
21
  app.use(
19
- "/monok",
22
+ "/api/monok",
20
23
  createMonokRouter({
21
24
  apiKey: process.env.MONOK_API_KEY!,
22
25
  onArticle: async (article: MonokArticle, { saveThumbImage, action }) => {
@@ -59,19 +62,21 @@ import {
59
62
  const app = express();
60
63
 
61
64
  app.use(
62
- "/monok",
65
+ "/api/monok",
63
66
  createMonokRouter({
64
67
  apiKey: process.env.MONOK_API_KEY!,
65
68
  imageDir: "./uploads", // optional
66
69
  // Optional: provide site info if you want Monok to query it
67
70
  getSiteInfo: {
68
71
  js_scripts: "Enabled",
69
- version: "1.0.0",
70
72
  name: "My Site",
71
73
  logo: "https://example.com/logo.png",
72
74
  categories: ["Content", "News Articles", "Uncategorized"],
73
75
  post_types: ["post", "page"],
74
76
  tags: ["Tech", "Health", "News"],
77
+ languages: {
78
+ en: { name: "English", slug: "en", locale: "en_US" }
79
+ },
75
80
  authors: [
76
81
  {
77
82
  name: "Jane Doe",
@@ -82,6 +87,15 @@ app.use(
82
87
  }
83
88
  ]
84
89
  },
90
+ searchArticles: async ({ postType, section, tag, query, amount, page }) => {
91
+ // Search published content in your CMS and return the requested page.
92
+ return [{
93
+ canonical_url: "https://example.com/blog/an-article",
94
+ post_id: "cms-123",
95
+ title: "An article",
96
+ description: "Article summary"
97
+ }];
98
+ },
85
99
  onArticle: async (article: MonokArticle, { saveThumbImage, action }) => {
86
100
  const imagePath = await saveThumbImage();
87
101
  console.log("Received article:", article.title);
@@ -95,9 +109,9 @@ app.use(
95
109
  // when you publish later, notify Monok:
96
110
  // await sendMonokLinkback({
97
111
  // monokId: article.id,
98
- // publicUrl: "https://example.com/blog/" + (article.slug ?? article.id),
112
+ // externalId: savedArticle.id,
113
+ // url: "https://example.com/blog/" + (article.slug ?? article.id),
99
114
  // title: article.title,
100
- // articleTextSample: article.text?.slice(0, 500),
101
115
  // thumbnailUrl: imagePath
102
116
  // ? "https://example.com/uploads/" + require("path").basename(imagePath)
103
117
  // : undefined
@@ -118,7 +132,7 @@ const { createMonokRouter } = require("@monok/server");
118
132
  const app = express();
119
133
 
120
134
  app.use(
121
- "/monok",
135
+ "/api/monok",
122
136
  createMonokRouter({
123
137
  apiKey: process.env.MONOK_API_KEY,
124
138
  onArticle: async (article, { saveThumbImage, action }) => {
@@ -141,29 +155,56 @@ app.listen(3000);
141
155
 
142
156
  ## 🔑 Endpoints
143
157
 
144
- * **`POST /monok/push[?action=draft|publish]`**
158
+ Mounting the router at `/api/monok` uses Monok's default API base. A custom or
159
+ versioned base is also supported, for example:
160
+
161
+ ```ts
162
+ app.use("/something/specialAPI/monok", createMonokRouter({ /* ... */ }));
163
+ ```
164
+
165
+ Enter that complete base URL when connecting the integration in Monok. Monok
166
+ appends `/info`, `/push`, and `/postfilter` to the configured base.
167
+
168
+ * **`POST /api/monok/push`**
145
169
  Monok pushes new article data here.
146
170
  Header: `token: <your-api-key>`
147
171
  Body: JSON payload of the article.
148
172
 
149
- * `action` query param may be `draft` or `publish` depending on GUI dropdown selection.
173
+ * `action` is read from the JSON body and may be `draft` or `publish`.
174
+ * A legacy `?action=draft|publish` query parameter is also accepted.
175
+ * After `onArticle` durably saves or queues the work, the route acknowledges
176
+ it with `{ "ok": true, "status": "accepted", "monokId": "..." }`. This
177
+ does not claim that asynchronous publication has completed.
178
+
179
+ * **`GET /api/monok/info`**
180
+ Monok fetches this to authenticate and learn your site branding, categories, tags, and authors.
181
+ If `getSiteInfo` is not provided, the route remains available and returns a
182
+ minimal `Custom CMS` response with empty metadata collections.
150
183
 
151
- * **`GET /monok/info?apikey=<your-api-key>`**
152
- If `getSiteInfo` is configured, Monok fetches this to learn your site branding, categories, tags, and authors.
153
- If `getSiteInfo` is not provided, this route responds with `404`.
184
+ * **`GET /api/monok/postfilter`**
185
+ If `searchArticles` is configured, Monok uses this route to search published
186
+ content. Supported parameters are `post_type`, `section`, `tag`, `q`, `amount`,
187
+ and `page`. If it is not configured, the route responds with `404`.
188
+
189
+ `GET /info` and `GET /postfilter` accept the API key in the `X-API-Key` header
190
+ (preferred) or the legacy `?apikey=` query parameter. Their responses include
191
+ private no-cache headers. `POST /push` also accepts Monok's `token` header.
154
192
 
155
193
  ---
156
194
 
157
195
  ## 🔗 Linkback after publishing
158
196
 
197
+ The push acknowledgement confirms delivery only. If publication is asynchronous,
198
+ send the linkback later, after the public canonical URL actually exists.
199
+
159
200
  ```ts
160
201
  import { sendMonokLinkback } from "@monok/server";
161
202
 
162
203
  await sendMonokLinkback({
163
204
  monokId: article.id,
164
- publicUrl: "https://example.com/blog/" + (article.slug ?? article.id),
205
+ externalId: cmsArticle.id,
206
+ url: "https://example.com/blog/" + (article.slug ?? article.id),
165
207
  title: article.title,
166
- articleTextSample: article.text?.slice(0, 500),
167
208
  thumbnailUrl: "https://example.com/uploads/" + imageFile
168
209
  });
169
210
  ```
@@ -176,8 +217,10 @@ await sendMonokLinkback({
176
217
  | ------------- | ----------------------------------------- | -------- | --------------------------------------------------------------------------------- |
177
218
  | `apiKey` | `string` | ✅ | Token Monok sends in headers / query. |
178
219
  | `onArticle` | `(article, ctx) => void \| Promise<void>` | ✅ | Handle new article pushes. `ctx` includes `saveThumbImage` and optional `action`. |
179
- | `getSiteInfo` | `SiteInfo \| () => SiteInfo` | ❌ | Return site info in the Monok format. If omitted, `GET /info` responds 404. |
180
- | `imageDir` | `string` | ❌ | Directory to save `thumbImageData`. |
220
+ | `getSiteInfo` | `SiteInfoInput \| () => SiteInfoInput` | ❌ | Return host metadata; the router injects its package version. A minimal fallback is used when omitted. |
221
+ | `searchArticles` | `(query) => PostFilterResult[]` | ❌ | Enables authenticated article discovery through `GET /postfilter`. |
222
+ | `imageDir` | `string` | ❌ | Directory in which validated JPEG thumbnails are saved. |
223
+ | `maxImageBytes` | `number` | ❌ | Maximum decoded JPEG size; defaults to 15 MiB. |
181
224
  | `basePath` | `string` | ❌ | Custom prefix for routes (default ""). |
182
225
 
183
226
  ---
package/dist/client.d.ts CHANGED
@@ -2,9 +2,8 @@ import type { LinkbackPayload } from "./types.js";
2
2
  /**
3
3
  * Send a linkback to Monok after you publish the article.
4
4
  */
5
- export declare function sendMonokLinkback({ monokId, publicUrl, title, articleTextSample, thumbnailUrl }: LinkbackPayload, opts?: {
5
+ export declare function sendMonokLinkback({ monokId, externalId, url, title, thumbnailUrl }: LinkbackPayload, opts?: {
6
6
  monokBaseUrl?: string;
7
- userId?: string;
8
7
  apiKey?: string;
9
8
  }): Promise<void>;
10
9
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAsB,EAAE,YAAY,EAAE,EAAE,eAAe,EACpF,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,OAAO,CAAC,IAAI,CAAC,CAwBf"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,eAAe,EAClE,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
package/dist/client.js CHANGED
@@ -2,14 +2,16 @@ 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) {
5
+ export async function sendMonokLinkback({ monokId, externalId, url, title, thumbnailUrl }, opts) {
6
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, {
@@ -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,IAAkE;IAElE,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,YAAY,IAAI,uBAAuB,CAAC,GAAG,kBAAkB,CAAC;IACtF,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;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
+ {"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 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwD,MAAM,SAAS,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAA0B,MAAM,YAAY,CAAC;AAG9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAgDnE"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwD,MAAM,SAAS,CAAC;AAEvF,OAAO,KAAK,EACV,mBAAmB,EAKpB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAmFnE"}
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":"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: 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,14 +2,16 @@ 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) {
5
+ export async function sendMonokLinkback({ monokId, externalId, url, title, thumbnailUrl }, opts) {
6
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, {
@@ -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,IAAkE;IAElE,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,YAAY,IAAI,uBAAuB,CAAC,GAAG,kBAAkB,CAAC;IACtF,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;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
+ {"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;
@@ -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-cjs/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
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monok/server",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Monok server integration helpers (Express router + linkback client)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "dist",
19
19
  "dist-cjs",
20
+ "AGENTS.md",
20
21
  "README.md",
21
22
  "LICENSE"
22
23
  ],
@@ -26,6 +27,7 @@
26
27
  "sideEffects": false,
27
28
  "scripts": {
28
29
  "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json",
30
+ "test": "npm run build && node --test tests/*.test.mjs",
29
31
  "dev": "tsc -w -p tsconfig.json",
30
32
  "clean": "rimraf dist dist-cjs",
31
33
  "prepublishOnly": "npm run build"