@we8/cms 0.1.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.
Files changed (304) hide show
  1. package/README.md +78 -0
  2. package/dist/auth/auth.d.ts +87 -0
  3. package/dist/auth/auth.d.ts.map +1 -0
  4. package/dist/auth/auth.js +90 -0
  5. package/dist/auth/auth.js.map +1 -0
  6. package/dist/auth/routes.admin.d.ts +22 -0
  7. package/dist/auth/routes.admin.d.ts.map +1 -0
  8. package/dist/auth/routes.admin.js +106 -0
  9. package/dist/auth/routes.admin.js.map +1 -0
  10. package/dist/auth/schema.d.ts +1395 -0
  11. package/dist/auth/schema.d.ts.map +1 -0
  12. package/dist/auth/schema.js +76 -0
  13. package/dist/auth/schema.js.map +1 -0
  14. package/dist/env.d.ts +64 -0
  15. package/dist/env.d.ts.map +1 -0
  16. package/dist/env.js +48 -0
  17. package/dist/env.js.map +1 -0
  18. package/dist/index.d.ts +22 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +170 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/keys/middleware.d.ts +54 -0
  23. package/dist/keys/middleware.d.ts.map +1 -0
  24. package/dist/keys/middleware.js +84 -0
  25. package/dist/keys/middleware.js.map +1 -0
  26. package/dist/keys/routes.admin.d.ts +16 -0
  27. package/dist/keys/routes.admin.d.ts.map +1 -0
  28. package/dist/keys/routes.admin.js +34 -0
  29. package/dist/keys/routes.admin.js.map +1 -0
  30. package/dist/keys/service.d.ts +36 -0
  31. package/dist/keys/service.d.ts.map +1 -0
  32. package/dist/keys/service.js +120 -0
  33. package/dist/keys/service.js.map +1 -0
  34. package/dist/lib/bots.d.ts +24 -0
  35. package/dist/lib/bots.d.ts.map +1 -0
  36. package/dist/lib/bots.js +79 -0
  37. package/dist/lib/bots.js.map +1 -0
  38. package/dist/lib/email.d.ts +62 -0
  39. package/dist/lib/email.d.ts.map +1 -0
  40. package/dist/lib/email.js +64 -0
  41. package/dist/lib/email.js.map +1 -0
  42. package/dist/lib/errors.d.ts +12 -0
  43. package/dist/lib/errors.d.ts.map +1 -0
  44. package/dist/lib/errors.js +23 -0
  45. package/dist/lib/errors.js.map +1 -0
  46. package/dist/lib/notify.d.ts +20 -0
  47. package/dist/lib/notify.d.ts.map +1 -0
  48. package/dist/lib/notify.js +60 -0
  49. package/dist/lib/notify.js.map +1 -0
  50. package/dist/lib/retention.d.ts +15 -0
  51. package/dist/lib/retention.d.ts.map +1 -0
  52. package/dist/lib/retention.js +40 -0
  53. package/dist/lib/retention.js.map +1 -0
  54. package/dist/lib/sanitize.d.ts +14 -0
  55. package/dist/lib/sanitize.d.ts.map +1 -0
  56. package/dist/lib/sanitize.js +145 -0
  57. package/dist/lib/sanitize.js.map +1 -0
  58. package/dist/lib/turnstile.d.ts +7 -0
  59. package/dist/lib/turnstile.d.ts.map +1 -0
  60. package/dist/lib/turnstile.js +32 -0
  61. package/dist/lib/turnstile.js.map +1 -0
  62. package/dist/lib/validate.d.ts +11 -0
  63. package/dist/lib/validate.d.ts.map +1 -0
  64. package/dist/lib/validate.js +22 -0
  65. package/dist/lib/validate.js.map +1 -0
  66. package/dist/lib/word-count.d.ts +7 -0
  67. package/dist/lib/word-count.d.ts.map +1 -0
  68. package/dist/lib/word-count.js +14 -0
  69. package/dist/lib/word-count.js.map +1 -0
  70. package/dist/middleware/auth.d.ts +46 -0
  71. package/dist/middleware/auth.d.ts.map +1 -0
  72. package/dist/middleware/auth.js +60 -0
  73. package/dist/middleware/auth.js.map +1 -0
  74. package/dist/middleware/cors.d.ts +33 -0
  75. package/dist/middleware/cors.d.ts.map +1 -0
  76. package/dist/middleware/cors.js +55 -0
  77. package/dist/middleware/cors.js.map +1 -0
  78. package/dist/middleware/module-gate.d.ts +11 -0
  79. package/dist/middleware/module-gate.d.ts.map +1 -0
  80. package/dist/middleware/module-gate.js +18 -0
  81. package/dist/middleware/module-gate.js.map +1 -0
  82. package/dist/middleware/rate-limit.d.ts +13 -0
  83. package/dist/middleware/rate-limit.d.ts.map +1 -0
  84. package/dist/middleware/rate-limit.js +30 -0
  85. package/dist/middleware/rate-limit.js.map +1 -0
  86. package/dist/middleware/request-id.d.ts +8 -0
  87. package/dist/middleware/request-id.d.ts.map +1 -0
  88. package/dist/middleware/request-id.js +11 -0
  89. package/dist/middleware/request-id.js.map +1 -0
  90. package/dist/modules/aeo/checks.d.ts +41 -0
  91. package/dist/modules/aeo/checks.d.ts.map +1 -0
  92. package/dist/modules/aeo/checks.js +127 -0
  93. package/dist/modules/aeo/checks.js.map +1 -0
  94. package/dist/modules/aeo/llms.d.ts +24 -0
  95. package/dist/modules/aeo/llms.d.ts.map +1 -0
  96. package/dist/modules/aeo/llms.js +92 -0
  97. package/dist/modules/aeo/llms.js.map +1 -0
  98. package/dist/modules/aeo/routes.admin.d.ts +12 -0
  99. package/dist/modules/aeo/routes.admin.d.ts.map +1 -0
  100. package/dist/modules/aeo/routes.admin.js +37 -0
  101. package/dist/modules/aeo/routes.admin.js.map +1 -0
  102. package/dist/modules/analytics/db.d.ts +24 -0
  103. package/dist/modules/analytics/db.d.ts.map +1 -0
  104. package/dist/modules/analytics/db.js +24 -0
  105. package/dist/modules/analytics/db.js.map +1 -0
  106. package/dist/modules/analytics/routes.admin.d.ts +6 -0
  107. package/dist/modules/analytics/routes.admin.d.ts.map +1 -0
  108. package/dist/modules/analytics/routes.admin.js +21 -0
  109. package/dist/modules/analytics/routes.admin.js.map +1 -0
  110. package/dist/modules/analytics/routes.public.d.ts +6 -0
  111. package/dist/modules/analytics/routes.public.d.ts.map +1 -0
  112. package/dist/modules/analytics/routes.public.js +33 -0
  113. package/dist/modules/analytics/routes.public.js.map +1 -0
  114. package/dist/modules/analytics/service.d.ts +16 -0
  115. package/dist/modules/analytics/service.d.ts.map +1 -0
  116. package/dist/modules/analytics/service.js +148 -0
  117. package/dist/modules/analytics/service.js.map +1 -0
  118. package/dist/modules/authors/routes.admin.d.ts +6 -0
  119. package/dist/modules/authors/routes.admin.d.ts.map +1 -0
  120. package/dist/modules/authors/routes.admin.js +29 -0
  121. package/dist/modules/authors/routes.admin.js.map +1 -0
  122. package/dist/modules/authors/routes.public.d.ts +6 -0
  123. package/dist/modules/authors/routes.public.d.ts.map +1 -0
  124. package/dist/modules/authors/routes.public.js +11 -0
  125. package/dist/modules/authors/routes.public.js.map +1 -0
  126. package/dist/modules/authors/service.d.ts +26 -0
  127. package/dist/modules/authors/service.d.ts.map +1 -0
  128. package/dist/modules/authors/service.js +116 -0
  129. package/dist/modules/authors/service.js.map +1 -0
  130. package/dist/modules/content/db.d.ts +127 -0
  131. package/dist/modules/content/db.d.ts.map +1 -0
  132. package/dist/modules/content/db.js +203 -0
  133. package/dist/modules/content/db.js.map +1 -0
  134. package/dist/modules/content/publish.d.ts +38 -0
  135. package/dist/modules/content/publish.d.ts.map +1 -0
  136. package/dist/modules/content/publish.js +184 -0
  137. package/dist/modules/content/publish.js.map +1 -0
  138. package/dist/modules/content/routes.admin.d.ts +8 -0
  139. package/dist/modules/content/routes.admin.d.ts.map +1 -0
  140. package/dist/modules/content/routes.admin.js +122 -0
  141. package/dist/modules/content/routes.admin.js.map +1 -0
  142. package/dist/modules/content/routes.public.d.ts +6 -0
  143. package/dist/modules/content/routes.public.d.ts.map +1 -0
  144. package/dist/modules/content/routes.public.js +56 -0
  145. package/dist/modules/content/routes.public.js.map +1 -0
  146. package/dist/modules/content/service.d.ts +137 -0
  147. package/dist/modules/content/service.d.ts.map +1 -0
  148. package/dist/modules/content/service.js +348 -0
  149. package/dist/modules/content/service.js.map +1 -0
  150. package/dist/modules/forms/db.d.ts +42 -0
  151. package/dist/modules/forms/db.d.ts.map +1 -0
  152. package/dist/modules/forms/db.js +62 -0
  153. package/dist/modules/forms/db.js.map +1 -0
  154. package/dist/modules/forms/forms-routes.admin.d.ts +6 -0
  155. package/dist/modules/forms/forms-routes.admin.d.ts.map +1 -0
  156. package/dist/modules/forms/forms-routes.admin.js +37 -0
  157. package/dist/modules/forms/forms-routes.admin.js.map +1 -0
  158. package/dist/modules/forms/forms-service.d.ts +18 -0
  159. package/dist/modules/forms/forms-service.d.ts.map +1 -0
  160. package/dist/modules/forms/forms-service.js +121 -0
  161. package/dist/modules/forms/forms-service.js.map +1 -0
  162. package/dist/modules/forms/routes.public.d.ts +6 -0
  163. package/dist/modules/forms/routes.public.d.ts.map +1 -0
  164. package/dist/modules/forms/routes.public.js +41 -0
  165. package/dist/modules/forms/routes.public.js.map +1 -0
  166. package/dist/modules/forms/submissions-routes.admin.d.ts +6 -0
  167. package/dist/modules/forms/submissions-routes.admin.d.ts.map +1 -0
  168. package/dist/modules/forms/submissions-routes.admin.js +46 -0
  169. package/dist/modules/forms/submissions-routes.admin.js.map +1 -0
  170. package/dist/modules/forms/submissions-service.d.ts +45 -0
  171. package/dist/modules/forms/submissions-service.d.ts.map +1 -0
  172. package/dist/modules/forms/submissions-service.js +174 -0
  173. package/dist/modules/forms/submissions-service.js.map +1 -0
  174. package/dist/modules/media/db.d.ts +15 -0
  175. package/dist/modules/media/db.d.ts.map +1 -0
  176. package/dist/modules/media/db.js +24 -0
  177. package/dist/modules/media/db.js.map +1 -0
  178. package/dist/modules/media/routes.admin.d.ts +6 -0
  179. package/dist/modules/media/routes.admin.d.ts.map +1 -0
  180. package/dist/modules/media/routes.admin.js +30 -0
  181. package/dist/modules/media/routes.admin.js.map +1 -0
  182. package/dist/modules/media/routes.public.d.ts +6 -0
  183. package/dist/modules/media/routes.public.d.ts.map +1 -0
  184. package/dist/modules/media/routes.public.js +35 -0
  185. package/dist/modules/media/routes.public.js.map +1 -0
  186. package/dist/modules/media/service.d.ts +32 -0
  187. package/dist/modules/media/service.d.ts.map +1 -0
  188. package/dist/modules/media/service.js +99 -0
  189. package/dist/modules/media/service.js.map +1 -0
  190. package/dist/modules/openapi/spec.d.ts +21 -0
  191. package/dist/modules/openapi/spec.d.ts.map +1 -0
  192. package/dist/modules/openapi/spec.js +468 -0
  193. package/dist/modules/openapi/spec.js.map +1 -0
  194. package/dist/modules/seo/jsonld.d.ts +25 -0
  195. package/dist/modules/seo/jsonld.d.ts.map +1 -0
  196. package/dist/modules/seo/jsonld.js +67 -0
  197. package/dist/modules/seo/jsonld.js.map +1 -0
  198. package/dist/modules/seo/routes.public.d.ts +6 -0
  199. package/dist/modules/seo/routes.public.d.ts.map +1 -0
  200. package/dist/modules/seo/routes.public.js +39 -0
  201. package/dist/modules/seo/routes.public.js.map +1 -0
  202. package/dist/modules/seo/sitemap.d.ts +3 -0
  203. package/dist/modules/seo/sitemap.d.ts.map +1 -0
  204. package/dist/modules/seo/sitemap.js +39 -0
  205. package/dist/modules/seo/sitemap.js.map +1 -0
  206. package/dist/modules/settings/routes.admin.d.ts +12 -0
  207. package/dist/modules/settings/routes.admin.d.ts.map +1 -0
  208. package/dist/modules/settings/routes.admin.js +27 -0
  209. package/dist/modules/settings/routes.admin.js.map +1 -0
  210. package/dist/modules/site-documents/routes.admin.d.ts +10 -0
  211. package/dist/modules/site-documents/routes.admin.d.ts.map +1 -0
  212. package/dist/modules/site-documents/routes.admin.js +29 -0
  213. package/dist/modules/site-documents/routes.admin.js.map +1 -0
  214. package/dist/modules/site-documents/routes.public.d.ts +6 -0
  215. package/dist/modules/site-documents/routes.public.d.ts.map +1 -0
  216. package/dist/modules/site-documents/routes.public.js +16 -0
  217. package/dist/modules/site-documents/routes.public.js.map +1 -0
  218. package/dist/modules/site-documents/service.d.ts +11 -0
  219. package/dist/modules/site-documents/service.d.ts.map +1 -0
  220. package/dist/modules/site-documents/service.js +42 -0
  221. package/dist/modules/site-documents/service.js.map +1 -0
  222. package/dist/modules/webhooks/dispatch.d.ts +14 -0
  223. package/dist/modules/webhooks/dispatch.d.ts.map +1 -0
  224. package/dist/modules/webhooks/dispatch.js +84 -0
  225. package/dist/modules/webhooks/dispatch.js.map +1 -0
  226. package/dist/shared/constants.d.ts +79 -0
  227. package/dist/shared/constants.d.ts.map +1 -0
  228. package/dist/shared/constants.js +89 -0
  229. package/dist/shared/constants.js.map +1 -0
  230. package/dist/shared/envelope.d.ts +37 -0
  231. package/dist/shared/envelope.d.ts.map +1 -0
  232. package/dist/shared/envelope.js +46 -0
  233. package/dist/shared/envelope.js.map +1 -0
  234. package/dist/shared/hash.d.ts +2 -0
  235. package/dist/shared/hash.d.ts.map +1 -0
  236. package/dist/shared/hash.js +8 -0
  237. package/dist/shared/hash.js.map +1 -0
  238. package/dist/shared/index.d.ts +13 -0
  239. package/dist/shared/index.d.ts.map +1 -0
  240. package/dist/shared/index.js +13 -0
  241. package/dist/shared/index.js.map +1 -0
  242. package/dist/shared/schemas/aeo.d.ts +182 -0
  243. package/dist/shared/schemas/aeo.d.ts.map +1 -0
  244. package/dist/shared/schemas/aeo.js +43 -0
  245. package/dist/shared/schemas/aeo.js.map +1 -0
  246. package/dist/shared/schemas/analytics.d.ts +277 -0
  247. package/dist/shared/schemas/analytics.d.ts.map +1 -0
  248. package/dist/shared/schemas/analytics.js +74 -0
  249. package/dist/shared/schemas/analytics.js.map +1 -0
  250. package/dist/shared/schemas/api-key.d.ts +77 -0
  251. package/dist/shared/schemas/api-key.d.ts.map +1 -0
  252. package/dist/shared/schemas/api-key.js +27 -0
  253. package/dist/shared/schemas/api-key.js.map +1 -0
  254. package/dist/shared/schemas/author.d.ts +147 -0
  255. package/dist/shared/schemas/author.d.ts.map +1 -0
  256. package/dist/shared/schemas/author.js +27 -0
  257. package/dist/shared/schemas/author.js.map +1 -0
  258. package/dist/shared/schemas/form.d.ts +341 -0
  259. package/dist/shared/schemas/form.d.ts.map +1 -0
  260. package/dist/shared/schemas/form.js +117 -0
  261. package/dist/shared/schemas/form.js.map +1 -0
  262. package/dist/shared/schemas/index.d.ts +12 -0
  263. package/dist/shared/schemas/index.d.ts.map +1 -0
  264. package/dist/shared/schemas/index.js +12 -0
  265. package/dist/shared/schemas/index.js.map +1 -0
  266. package/dist/shared/schemas/media.d.ts +42 -0
  267. package/dist/shared/schemas/media.d.ts.map +1 -0
  268. package/dist/shared/schemas/media.js +16 -0
  269. package/dist/shared/schemas/media.js.map +1 -0
  270. package/dist/shared/schemas/pagination.d.ts +34 -0
  271. package/dist/shared/schemas/pagination.d.ts.map +1 -0
  272. package/dist/shared/schemas/pagination.js +21 -0
  273. package/dist/shared/schemas/pagination.js.map +1 -0
  274. package/dist/shared/schemas/post.d.ts +862 -0
  275. package/dist/shared/schemas/post.d.ts.map +1 -0
  276. package/dist/shared/schemas/post.js +187 -0
  277. package/dist/shared/schemas/post.js.map +1 -0
  278. package/dist/shared/schemas/settings.d.ts +839 -0
  279. package/dist/shared/schemas/settings.d.ts.map +1 -0
  280. package/dist/shared/schemas/settings.js +202 -0
  281. package/dist/shared/schemas/settings.js.map +1 -0
  282. package/dist/shared/schemas/site-document.d.ts +38 -0
  283. package/dist/shared/schemas/site-document.d.ts.map +1 -0
  284. package/dist/shared/schemas/site-document.js +21 -0
  285. package/dist/shared/schemas/site-document.js.map +1 -0
  286. package/dist/shared/schemas/webhook.d.ts +39 -0
  287. package/dist/shared/schemas/webhook.d.ts.map +1 -0
  288. package/dist/shared/schemas/webhook.js +20 -0
  289. package/dist/shared/schemas/webhook.js.map +1 -0
  290. package/dist/spa.d.ts +9 -0
  291. package/dist/spa.d.ts.map +1 -0
  292. package/dist/spa.js +57 -0
  293. package/dist/spa.js.map +1 -0
  294. package/migrations/0001_settings.sql +39 -0
  295. package/migrations/0002_content.sql +135 -0
  296. package/migrations/0003_forms.sql +35 -0
  297. package/migrations/0004_analytics.sql +42 -0
  298. package/migrations/0005_media.sql +17 -0
  299. package/migrations/0006_site_documents.sql +18 -0
  300. package/migrations/0007_auth.sql +81 -0
  301. package/migrations/0008_api_keys.sql +20 -0
  302. package/package.json +68 -0
  303. package/scripts/bundle-admin.mjs +54 -0
  304. package/seed/local.sql +63 -0
@@ -0,0 +1,11 @@
1
+ import { createMiddleware } from 'hono/factory';
2
+ /** Assigns a request id and adds X-Request-Id / X-Response-Time to every response. */
3
+ export const requestId = createMiddleware(async (c, next) => {
4
+ const id = crypto.randomUUID();
5
+ const start = Date.now();
6
+ c.set('requestId', id);
7
+ await next();
8
+ c.res.headers.set('X-Request-Id', id);
9
+ c.res.headers.set('X-Response-Time', `${Date.now() - start}ms`);
10
+ });
11
+ //# sourceMappingURL=request-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-id.js","sourceRoot":"","sources":["../../src/middleware/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMhD,sFAAsF;AACtF,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAoC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IAC7F,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAEvB,MAAM,IAAI,EAAE,CAAC;IAEb,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { type AeoContentCheck, type AeoCrawlerStats, type AeoReport, type PostStatus, type PostType } from '../../shared/index.js';
2
+ interface CheckablePostRow {
3
+ id: string;
4
+ slug: string;
5
+ title: string;
6
+ type: PostType;
7
+ status: PostStatus;
8
+ excerpt: string | null;
9
+ og_description: string | null;
10
+ answer_summary: string | null;
11
+ question_heading: string | null;
12
+ current_version_id: string | null;
13
+ }
14
+ /**
15
+ * The findings for one post.
16
+ *
17
+ * Every check is a concrete, locally verifiable absence. Nothing here scores a
18
+ * page, guesses at ranking, or calls an external service: an AEO tool that
19
+ * tells a site owner they are "72 percent optimized" is telling them nothing,
20
+ * whereas "this post has no answer summary and is not in your sitemap" is a
21
+ * fact they can act on in a minute.
22
+ *
23
+ * `publisherConfigured` and `seoEnabled` are passed in rather than read here
24
+ * because they are site-wide and this runs once per post.
25
+ */
26
+ export declare function checkPost(row: CheckablePostRow, options: {
27
+ publisherConfigured: boolean;
28
+ seoEnabled: boolean;
29
+ }): AeoContentCheck;
30
+ /** The whole site's report: every post, every document, and the totals. */
31
+ export declare function buildAeoReport(db: D1Database): Promise<AeoReport>;
32
+ /** One post's findings, for the editor's own AEO panel. 404s on an unknown id. */
33
+ export declare function checkOnePost(db: D1Database, postId: string): Promise<AeoContentCheck>;
34
+ /**
35
+ * Crawler activity, read straight out of the visit log's user-agent class.
36
+ * This is the other half of AEO: the checks say what the content is missing,
37
+ * this says who actually came to read it.
38
+ */
39
+ export declare function getCrawlerStats(db: D1Database, days: number): Promise<AeoCrawlerStats>;
40
+ export {};
41
+ //# sourceMappingURL=checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../../src/modules/aeo/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,eAAe,EAGpB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,QAAQ,EAEd,MAAM,uBAAuB,CAAC;AAK/B,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE;IAAE,mBAAmB,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAC7D,eAAe,CAqCjB;AAED,2EAA2E;AAC3E,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAgCvE;AAED,kFAAkF;AAClF,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAY3F;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA4C5F"}
@@ -0,0 +1,127 @@
1
+ import { AEO_FINDINGS, } from '../../shared/index.js';
2
+ import { getSettings } from '../../env.js';
3
+ import { HttpError } from '../../lib/errors.js';
4
+ import { listDocuments } from '../site-documents/service.js';
5
+ const POST_SELECT = `SELECT id, slug, title, type, status, excerpt, og_description,
6
+ answer_summary, question_heading, current_version_id
7
+ FROM posts`;
8
+ function isBlank(value) {
9
+ return value === null || value.trim().length === 0;
10
+ }
11
+ /**
12
+ * The findings for one post.
13
+ *
14
+ * Every check is a concrete, locally verifiable absence. Nothing here scores a
15
+ * page, guesses at ranking, or calls an external service: an AEO tool that
16
+ * tells a site owner they are "72 percent optimized" is telling them nothing,
17
+ * whereas "this post has no answer summary and is not in your sitemap" is a
18
+ * fact they can act on in a minute.
19
+ *
20
+ * `publisherConfigured` and `seoEnabled` are passed in rather than read here
21
+ * because they are site-wide and this runs once per post.
22
+ */
23
+ export function checkPost(row, options) {
24
+ const findings = [];
25
+ // Something has to describe the page. An engine that finds neither an
26
+ // excerpt nor an OG description has to invent one from the body.
27
+ if (isBlank(row.excerpt) && isBlank(row.og_description)) {
28
+ findings.push('missing_description');
29
+ }
30
+ if (isBlank(row.answer_summary)) {
31
+ findings.push('missing_answer_summary');
32
+ }
33
+ if (isBlank(row.question_heading)) {
34
+ findings.push('missing_question_heading');
35
+ }
36
+ // JSON-LD is emitted on the public post response, but only for a published
37
+ // post, and its publisher block only exists when the site has configured
38
+ // one. Both absences are reported as the same missing structured data,
39
+ // because the effect on a crawler is the same.
40
+ const published = row.status === 'published' && row.current_version_id !== null;
41
+ if (!published || !options.publisherConfigured) {
42
+ findings.push('missing_json_ld');
43
+ }
44
+ // The sitemap lists published posts, and only when the seo module is on.
45
+ if (!published || !options.seoEnabled) {
46
+ findings.push('not_in_sitemap');
47
+ }
48
+ return {
49
+ id: row.id,
50
+ slug: row.slug,
51
+ title: row.title,
52
+ type: row.type,
53
+ status: row.status,
54
+ findings,
55
+ };
56
+ }
57
+ /** The whole site's report: every post, every document, and the totals. */
58
+ export async function buildAeoReport(db) {
59
+ const settings = await getSettings(db);
60
+ const options = {
61
+ publisherConfigured: settings.publisher !== undefined,
62
+ seoEnabled: settings.modules.seo,
63
+ };
64
+ const { results } = await db
65
+ .prepare(`${POST_SELECT} ORDER BY created_at DESC`)
66
+ .all();
67
+ const posts = results.map((row) => checkPost(row, options));
68
+ const documents = (await listDocuments(db))
69
+ .filter((document) => document.content.trim().length > 0)
70
+ .map((document) => ({
71
+ kind: document.kind,
72
+ findings: isBlank(document.answerSummary) ? ['missing_answer_summary'] : [],
73
+ }));
74
+ // Seeded with every finding at zero, so the admin renders a stable set of
75
+ // counters instead of a shape that changes with the data.
76
+ const totals = Object.fromEntries(AEO_FINDINGS.map((finding) => [finding, 0]));
77
+ for (const check of [...posts, ...documents]) {
78
+ for (const finding of check.findings) {
79
+ totals[finding] += 1;
80
+ }
81
+ }
82
+ return { posts, documents, totals, checkedAt: new Date().toISOString() };
83
+ }
84
+ /** One post's findings, for the editor's own AEO panel. 404s on an unknown id. */
85
+ export async function checkOnePost(db, postId) {
86
+ const row = await db
87
+ .prepare(`${POST_SELECT} WHERE id = ?1`)
88
+ .bind(postId)
89
+ .first();
90
+ if (!row)
91
+ throw new HttpError('not_found', 'post not found');
92
+ const settings = await getSettings(db);
93
+ return checkPost(row, {
94
+ publisherConfigured: settings.publisher !== undefined,
95
+ seoEnabled: settings.modules.seo,
96
+ });
97
+ }
98
+ /**
99
+ * Crawler activity, read straight out of the visit log's user-agent class.
100
+ * This is the other half of AEO: the checks say what the content is missing,
101
+ * this says who actually came to read it.
102
+ */
103
+ export async function getCrawlerStats(db, days) {
104
+ const since = `-${days} days`;
105
+ const byClass = (await db
106
+ .prepare(`SELECT user_agent_class as userAgentClass, COUNT(*) as count FROM page_visits
107
+ WHERE visited_at > datetime('now', ?1) AND user_agent_class != 'human'
108
+ GROUP BY user_agent_class ORDER BY count DESC`)
109
+ .bind(since)
110
+ .all()).results;
111
+ const topCrawlers = (await db
112
+ .prepare(`SELECT bot_name as botName, user_agent_class as userAgentClass,
113
+ COUNT(*) as count, MAX(visited_at) as lastSeenAt
114
+ FROM page_visits
115
+ WHERE visited_at > datetime('now', ?1) AND bot_name IS NOT NULL
116
+ GROUP BY bot_name, user_agent_class ORDER BY count DESC LIMIT 20`)
117
+ .bind(since)
118
+ .all()).results;
119
+ const daily = (await db
120
+ .prepare(`SELECT date(visited_at) as date, COUNT(*) as count FROM page_visits
121
+ WHERE visited_at > datetime('now', ?1) AND user_agent_class != 'human'
122
+ GROUP BY date(visited_at) ORDER BY date ASC`)
123
+ .bind(since)
124
+ .all()).results;
125
+ return { byClass, topCrawlers, daily };
126
+ }
127
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../../src/modules/aeo/checks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Builds /llms.txt from the site's own settings and its published content.
3
+ *
4
+ * The format follows the llmstxt.org convention: an H1 with the site name, a
5
+ * blockquote holding the one-sentence summary, then link sections. Nothing
6
+ * here calls out to anything: the document is a plain projection of what this
7
+ * database already holds, which is the whole point. An answer engine that
8
+ * fetches it gets the site's own account of itself rather than whatever it
9
+ * managed to scrape.
10
+ *
11
+ * Ordering is newest first and the list is capped by settings, so a site with
12
+ * ten thousand posts publishes an index rather than a dump.
13
+ */
14
+ export declare function buildLlmsTxt(db: D1Database, siteUrl: string): Promise<string>;
15
+ /**
16
+ * The document served at /llms.txt, or null when the site has turned it off.
17
+ *
18
+ * An author-written `llms-txt` site document wins outright: a site owner who
19
+ * has taken the trouble to write the file by hand means it, and silently
20
+ * regenerating over them would be the CMS overruling its own user. Everything
21
+ * else falls through to generation.
22
+ */
23
+ export declare function resolveLlmsTxt(db: D1Database, siteUrl: string): Promise<string | null>;
24
+ //# sourceMappingURL=llms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llms.d.ts","sourceRoot":"","sources":["../../../src/modules/aeo/llms.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDnF;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAM5F"}
@@ -0,0 +1,92 @@
1
+ import { resolveContentPath } from '../../shared/index.js';
2
+ import { getSettings } from '../../env.js';
3
+ import { getDocument } from '../site-documents/service.js';
4
+ /**
5
+ * One llms.txt line per post. The description is the answer summary when the
6
+ * author wrote one, because that is the sentence written to be quoted; the
7
+ * excerpt is the fallback, and a post with neither gets a bare link rather
8
+ * than an invented description.
9
+ */
10
+ function postLine(siteUrl, row, path) {
11
+ const url = `${siteUrl}${path}/${row.slug}`;
12
+ const label = row.question_heading ?? row.title;
13
+ const description = row.answer_summary ?? row.excerpt;
14
+ return description ? `- [${label}](${url}): ${description}` : `- [${label}](${url})`;
15
+ }
16
+ /**
17
+ * Builds /llms.txt from the site's own settings and its published content.
18
+ *
19
+ * The format follows the llmstxt.org convention: an H1 with the site name, a
20
+ * blockquote holding the one-sentence summary, then link sections. Nothing
21
+ * here calls out to anything: the document is a plain projection of what this
22
+ * database already holds, which is the whole point. An answer engine that
23
+ * fetches it gets the site's own account of itself rather than whatever it
24
+ * managed to scrape.
25
+ *
26
+ * Ordering is newest first and the list is capped by settings, so a site with
27
+ * ten thousand posts publishes an index rather than a dump.
28
+ */
29
+ export async function buildLlmsTxt(db, siteUrl) {
30
+ const settings = await getSettings(db);
31
+ const name = settings.publisher?.name ?? settings.seo.defaultTitle ?? siteUrl;
32
+ const summary = settings.aeo.siteSummary ?? settings.seo.metaDescription ?? null;
33
+ const lines = [`# ${name}`, ''];
34
+ if (summary) {
35
+ lines.push(`> ${summary}`, '');
36
+ }
37
+ lines.push(`Site: ${siteUrl}`, '');
38
+ const { results } = await db
39
+ .prepare(`SELECT posts.slug, posts.type, pv.title, pv.excerpt, pv.answer_summary, pv.question_heading, pv.published_at
40
+ FROM posts JOIN post_versions pv ON pv.id = posts.current_version_id
41
+ WHERE posts.status = 'published'
42
+ ORDER BY pv.published_at DESC
43
+ LIMIT ?1`)
44
+ .bind(settings.aeo.llmsTxtMaxPosts)
45
+ .all();
46
+ // Grouped by content type so a section header carries meaning ("Research",
47
+ // "News") instead of one flat list of everything the site has ever written.
48
+ const byType = new Map();
49
+ for (const row of results) {
50
+ const bucket = byType.get(row.type);
51
+ if (bucket)
52
+ bucket.push(row);
53
+ else
54
+ byType.set(row.type, [row]);
55
+ }
56
+ for (const [type, rows] of byType) {
57
+ lines.push(`## ${type}`, '');
58
+ const path = resolveContentPath(type, settings);
59
+ for (const row of rows) {
60
+ lines.push(postLine(siteUrl, row, path));
61
+ }
62
+ lines.push('');
63
+ }
64
+ const design = await getDocument(db, 'design-md');
65
+ if (design.content.length > 0) {
66
+ lines.push('## Documents', '');
67
+ const description = design.answerSummary;
68
+ const link = `- [design.md](${siteUrl}/design.md)`;
69
+ lines.push(description ? `${link}: ${description}` : link);
70
+ lines.push('');
71
+ }
72
+ lines.push(`## Sitemap`, '', `- [sitemap.xml](${siteUrl}/sitemap.xml)`, '');
73
+ return lines.join('\n');
74
+ }
75
+ /**
76
+ * The document served at /llms.txt, or null when the site has turned it off.
77
+ *
78
+ * An author-written `llms-txt` site document wins outright: a site owner who
79
+ * has taken the trouble to write the file by hand means it, and silently
80
+ * regenerating over them would be the CMS overruling its own user. Everything
81
+ * else falls through to generation.
82
+ */
83
+ export async function resolveLlmsTxt(db, siteUrl) {
84
+ const settings = await getSettings(db);
85
+ const authored = await getDocument(db, 'llms-txt');
86
+ if (authored.content.trim().length > 0)
87
+ return authored.content;
88
+ if (!settings.aeo.llmsTxtEnabled)
89
+ return null;
90
+ return buildLlmsTxt(db, siteUrl);
91
+ }
92
+ //# sourceMappingURL=llms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llms.js","sourceRoot":"","sources":["../../../src/modules/aeo/llms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { Hono } from 'hono';
2
+ import type { Bindings } from '../../env.js';
3
+ /**
4
+ * The admin side of the AEO surface: what the content is missing, who has been
5
+ * crawling it, and a preview of the document that gets published. Open to a
6
+ * member, like the rest of the editorial admin API; nothing here is personal
7
+ * data or configuration.
8
+ */
9
+ export declare const aeoAdminRoutes: Hono<{
10
+ Bindings: Bindings;
11
+ }, import("hono/types").BlankSchema, "/">;
12
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../../src/modules/aeo/routes.admin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAK7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc;cAAwB,QAAQ;yCAAK,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { ok, statsQuerySchema, VISIT_STATS_MAX_DAYS } from '../../shared/index.js';
2
+ import { Hono } from 'hono';
3
+ import { parseOrThrow } from '../../lib/validate.js';
4
+ import { buildAeoReport, checkOnePost, getCrawlerStats } from './checks.js';
5
+ import { buildLlmsTxt } from './llms.js';
6
+ /**
7
+ * The admin side of the AEO surface: what the content is missing, who has been
8
+ * crawling it, and a preview of the document that gets published. Open to a
9
+ * member, like the rest of the editorial admin API; nothing here is personal
10
+ * data or configuration.
11
+ */
12
+ export const aeoAdminRoutes = new Hono();
13
+ aeoAdminRoutes.get('/checks', async (c) => {
14
+ const report = await buildAeoReport(c.env.DB);
15
+ return c.json(ok({ report }));
16
+ });
17
+ // Registered BEFORE /checks/:postId would be reached by anything literal.
18
+ aeoAdminRoutes.get('/checks/:postId', async (c) => {
19
+ const check = await checkOnePost(c.env.DB, c.req.param('postId'));
20
+ return c.json(ok({ check }));
21
+ });
22
+ aeoAdminRoutes.get('/crawlers', async (c) => {
23
+ const query = parseOrThrow(statsQuerySchema(VISIT_STATS_MAX_DAYS), c.req.query());
24
+ const stats = await getCrawlerStats(c.env.DB, query.days);
25
+ return c.json(ok(stats));
26
+ });
27
+ /**
28
+ * The generated document, as the public route would serve it. Deliberately
29
+ * ignores both the enabled flag and the authored override, because this is the
30
+ * "show me what generation produces" view the admin needs while deciding
31
+ * whether to publish it at all.
32
+ */
33
+ aeoAdminRoutes.get('/llms.txt', async (c) => {
34
+ const body = await buildLlmsTxt(c.env.DB, c.env.SITE_URL);
35
+ return c.body(body, 200, { 'Content-Type': 'text/plain; charset=UTF-8' });
36
+ });
37
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../../src/modules/aeo/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,IAAI,EAA0B,CAAC;AAEjE,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACxC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,0EAA0E;AAC1E,cAAc,CAAC,GAAG,CAAC,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAChD,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC;AAEH,cAAc,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,cAAc,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC1C,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,cAAc,EAAE,2BAA2B,EAAE,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { PageVisitRecord, UserAgentClass } from '../../shared/index.js';
2
+ export interface PageVisitRow {
3
+ id: number;
4
+ visited_at: string;
5
+ pathname: string;
6
+ referrer: string | null;
7
+ country: string | null;
8
+ ip_hash: string | null;
9
+ user_agent: string | null;
10
+ is_bot: number;
11
+ bot_name: string | null;
12
+ bot_score: number | null;
13
+ user_agent_class: UserAgentClass;
14
+ session_id: string | null;
15
+ }
16
+ export declare function toPageVisitRecord(row: PageVisitRow): PageVisitRecord;
17
+ /**
18
+ * All analytics reads go through this. The D1 Sessions API with
19
+ * `first-unconstrained` lets a read land on whichever replica is closest,
20
+ * which is the right trade for a visit log where a moment of staleness in an
21
+ * aggregate costs nothing.
22
+ */
23
+ export declare function analyticsSession(db: D1Database): D1DatabaseSession;
24
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,cAAc,CAAC;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,eAAe,CAapE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,UAAU,GAAG,iBAAiB,CAElE"}
@@ -0,0 +1,24 @@
1
+ export function toPageVisitRecord(row) {
2
+ return {
3
+ id: String(row.id),
4
+ visitedAt: row.visited_at,
5
+ pathname: row.pathname,
6
+ referrer: row.referrer,
7
+ country: row.country,
8
+ isBot: row.is_bot === 1,
9
+ botName: row.bot_name,
10
+ botScore: row.bot_score,
11
+ userAgentClass: row.user_agent_class,
12
+ sessionId: row.session_id,
13
+ };
14
+ }
15
+ /**
16
+ * All analytics reads go through this. The D1 Sessions API with
17
+ * `first-unconstrained` lets a read land on whichever replica is closest,
18
+ * which is the right trade for a visit log where a moment of staleness in an
19
+ * aggregate costs nothing.
20
+ */
21
+ export function analyticsSession(db) {
22
+ return db.withSession('first-unconstrained');
23
+ }
24
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/modules/analytics/db.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,GAAG,CAAC,MAAM,KAAK,CAAC;QACvB,OAAO,EAAE,GAAG,CAAC,QAAQ;QACrB,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,cAAc,EAAE,GAAG,CAAC,gBAAgB;QACpC,SAAS,EAAE,GAAG,CAAC,UAAU;KAC1B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAc;IAC7C,OAAO,EAAE,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Hono } from 'hono';
2
+ import type { Bindings } from '../../env.js';
3
+ export declare const analyticsAdminRoutes: Hono<{
4
+ Bindings: Bindings;
5
+ }, import("hono/types").BlankSchema, "/">;
6
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/routes.admin.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,eAAO,MAAM,oBAAoB;cAAwB,QAAQ;yCAAK,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { CONSENT_STATS_MAX_DAYS, ok, statsQuerySchema, VISIT_STATS_MAX_DAYS, visitListQuerySchema, } from '../../shared/index.js';
2
+ import { Hono } from 'hono';
3
+ import { parseOrThrow } from '../../lib/validate.js';
4
+ import { getConsentStats, getVisitStats, listVisitsAdmin } from './service.js';
5
+ export const analyticsAdminRoutes = new Hono();
6
+ analyticsAdminRoutes.get('/visits', async (c) => {
7
+ const query = parseOrThrow(visitListQuerySchema, c.req.query());
8
+ const { items, total } = await listVisitsAdmin(c.env.DB, query);
9
+ return c.json(ok({ items, page: query.page, pageSize: query.pageSize, total }));
10
+ });
11
+ analyticsAdminRoutes.get('/visits/stats', async (c) => {
12
+ const query = parseOrThrow(statsQuerySchema(VISIT_STATS_MAX_DAYS), c.req.query());
13
+ const stats = await getVisitStats(c.env.DB, query.days);
14
+ return c.json(ok(stats));
15
+ });
16
+ analyticsAdminRoutes.get('/consent/stats', async (c) => {
17
+ const query = parseOrThrow(statsQuerySchema(CONSENT_STATS_MAX_DAYS), c.req.query());
18
+ const stats = await getConsentStats(c.env.DB, query.days);
19
+ return c.json(ok(stats));
20
+ });
21
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../../src/modules/analytics/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,EAAE,EACF,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/E,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,IAAI,EAA0B,CAAC;AAEvE,oBAAoB,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC9C,MAAM,KAAK,GAAG,YAAY,CAAC,oBAAoB,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IAChE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAChE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC,CAAC,CAAC;AAEH,oBAAoB,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACpD,MAAM,KAAK,GAAG,YAAY,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,oBAAoB,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACrD,MAAM,KAAK,GAAG,YAAY,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3B,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Hono } from 'hono';
2
+ import type { Bindings } from '../../env.js';
3
+ export declare const analyticsPublicRoutes: Hono<{
4
+ Bindings: Bindings;
5
+ }, import("hono/types").BlankSchema, "/">;
6
+ //# sourceMappingURL=routes.public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.public.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/routes.public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAM7C,eAAO,MAAM,qBAAqB;cAAwB,QAAQ;yCAAK,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { consentEventInputSchema, ok, visitEventInputSchema } from '../../shared/index.js';
2
+ import { Hono } from 'hono';
3
+ import { parseOrThrow } from '../../lib/validate.js';
4
+ import { corsMiddleware, enforceOrigin } from '../../middleware/cors.js';
5
+ import { rateLimit } from '../../middleware/rate-limit.js';
6
+ import { recordConsent, recordVisit } from './service.js';
7
+ export const analyticsPublicRoutes = new Hono();
8
+ analyticsPublicRoutes.use('/visit', corsMiddleware('publicWrite'), enforceOrigin, rateLimit('engagement'));
9
+ analyticsPublicRoutes.use('/consent', corsMiddleware('publicWrite'), enforceOrigin, rateLimit('engagement'));
10
+ analyticsPublicRoutes.post('/visit', async (c) => {
11
+ const input = parseOrThrow(visitEventInputSchema, await c.req.json());
12
+ await recordVisit(c.env.DB, c.env.SITE_URL, input, {
13
+ country: c.req.header('CF-IPCountry') ?? null,
14
+ userAgent: c.req.header('User-Agent') ?? null,
15
+ ip: c.req.header('CF-Connecting-IP') ?? null,
16
+ // Request.cf is typed as a union covering both an incoming request's
17
+ // populated properties AND the much smaller shape used when
18
+ // constructing an outbound fetch() (RequestInitCfProperties). c.req.raw
19
+ // is always the actual incoming request here, so it's genuinely the
20
+ // former; the union just can't express that distinction statically.
21
+ cf: c.req.raw.cf,
22
+ });
23
+ return c.json(ok({}));
24
+ });
25
+ analyticsPublicRoutes.post('/consent', async (c) => {
26
+ const input = parseOrThrow(consentEventInputSchema, await c.req.json());
27
+ await recordConsent(c.env.DB, c.env.SITE_URL, input, {
28
+ ip: c.req.header('CF-Connecting-IP') ?? null,
29
+ userAgent: c.req.header('User-Agent') ?? null,
30
+ });
31
+ return c.json(ok({}));
32
+ });
33
+ //# sourceMappingURL=routes.public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.public.js","sourceRoot":"","sources":["../../../src/modules/analytics/routes.public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,EAAE,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,IAAI,EAA0B,CAAC;AAExE,qBAAqB,CAAC,GAAG,CACvB,QAAQ,EACR,cAAc,CAAC,aAAa,CAAC,EAC7B,aAAa,EACb,SAAS,CAAC,YAAY,CAAC,CACxB,CAAC;AACF,qBAAqB,CAAC,GAAG,CACvB,UAAU,EACV,cAAc,CAAC,aAAa,CAAC,EAC7B,aAAa,EACb,SAAS,CAAC,YAAY,CAAC,CACxB,CAAC;AAEF,qBAAqB,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACtE,MAAM,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE;QACjD,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI;QAC7C,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,IAAI;QAC7C,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,IAAI;QAC5C,qEAAqE;QACrE,4DAA4D;QAC5D,wEAAwE;QACxE,oEAAoE;QACpE,oEAAoE;QACpE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAA6C;KAC5D,CAAC,CAAC;IACH,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC;AAEH,qBAAqB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACjD,MAAM,KAAK,GAAG,YAAY,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE;QACnD,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,IAAI;QAC5C,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,IAAI;KAC9C,CAAC,CAAC;IACH,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type ConsentEventInput, type ConsentStats, type PageVisitRecord, type VisitEventInput, type VisitListQuery, type VisitStats } from '../../shared/index.js';
2
+ export interface RequestSignals {
3
+ country: string | null;
4
+ userAgent: string | null;
5
+ ip: string | null;
6
+ cf: IncomingRequestCfProperties | undefined;
7
+ }
8
+ export declare function recordVisit(db: D1Database, siteUrl: string, input: VisitEventInput, signals: RequestSignals): Promise<void>;
9
+ export declare function recordConsent(db: D1Database, siteUrl: string, input: ConsentEventInput, signals: Pick<RequestSignals, 'ip' | 'userAgent'>): Promise<void>;
10
+ export declare function listVisitsAdmin(db: D1Database, query: VisitListQuery): Promise<{
11
+ items: PageVisitRecord[];
12
+ total: number;
13
+ }>;
14
+ export declare function getVisitStats(db: D1Database, days: number): Promise<VisitStats>;
15
+ export declare function getConsentStats(db: D1Database, days: number): Promise<ConsentStats>;
16
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAI/B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,EAAE,EAAE,2BAA2B,GAAG,SAAS,CAAC;CAC7C;AAYD,wBAAsB,WAAW,CAC/B,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,WAAW,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA2CtD;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAwFrF;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAyCzF"}