@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.
- package/README.md +78 -0
- package/dist/auth/auth.d.ts +87 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +90 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/routes.admin.d.ts +22 -0
- package/dist/auth/routes.admin.d.ts.map +1 -0
- package/dist/auth/routes.admin.js +106 -0
- package/dist/auth/routes.admin.js.map +1 -0
- package/dist/auth/schema.d.ts +1395 -0
- package/dist/auth/schema.d.ts.map +1 -0
- package/dist/auth/schema.js +76 -0
- package/dist/auth/schema.js.map +1 -0
- package/dist/env.d.ts +64 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +48 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +170 -0
- package/dist/index.js.map +1 -0
- package/dist/keys/middleware.d.ts +54 -0
- package/dist/keys/middleware.d.ts.map +1 -0
- package/dist/keys/middleware.js +84 -0
- package/dist/keys/middleware.js.map +1 -0
- package/dist/keys/routes.admin.d.ts +16 -0
- package/dist/keys/routes.admin.d.ts.map +1 -0
- package/dist/keys/routes.admin.js +34 -0
- package/dist/keys/routes.admin.js.map +1 -0
- package/dist/keys/service.d.ts +36 -0
- package/dist/keys/service.d.ts.map +1 -0
- package/dist/keys/service.js +120 -0
- package/dist/keys/service.js.map +1 -0
- package/dist/lib/bots.d.ts +24 -0
- package/dist/lib/bots.d.ts.map +1 -0
- package/dist/lib/bots.js +79 -0
- package/dist/lib/bots.js.map +1 -0
- package/dist/lib/email.d.ts +62 -0
- package/dist/lib/email.d.ts.map +1 -0
- package/dist/lib/email.js +64 -0
- package/dist/lib/email.js.map +1 -0
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +23 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/notify.d.ts +20 -0
- package/dist/lib/notify.d.ts.map +1 -0
- package/dist/lib/notify.js +60 -0
- package/dist/lib/notify.js.map +1 -0
- package/dist/lib/retention.d.ts +15 -0
- package/dist/lib/retention.d.ts.map +1 -0
- package/dist/lib/retention.js +40 -0
- package/dist/lib/retention.js.map +1 -0
- package/dist/lib/sanitize.d.ts +14 -0
- package/dist/lib/sanitize.d.ts.map +1 -0
- package/dist/lib/sanitize.js +145 -0
- package/dist/lib/sanitize.js.map +1 -0
- package/dist/lib/turnstile.d.ts +7 -0
- package/dist/lib/turnstile.d.ts.map +1 -0
- package/dist/lib/turnstile.js +32 -0
- package/dist/lib/turnstile.js.map +1 -0
- package/dist/lib/validate.d.ts +11 -0
- package/dist/lib/validate.d.ts.map +1 -0
- package/dist/lib/validate.js +22 -0
- package/dist/lib/validate.js.map +1 -0
- package/dist/lib/word-count.d.ts +7 -0
- package/dist/lib/word-count.d.ts.map +1 -0
- package/dist/lib/word-count.js +14 -0
- package/dist/lib/word-count.js.map +1 -0
- package/dist/middleware/auth.d.ts +46 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +60 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +33 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +55 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/module-gate.d.ts +11 -0
- package/dist/middleware/module-gate.d.ts.map +1 -0
- package/dist/middleware/module-gate.js +18 -0
- package/dist/middleware/module-gate.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +13 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +30 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-id.d.ts +8 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +11 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/modules/aeo/checks.d.ts +41 -0
- package/dist/modules/aeo/checks.d.ts.map +1 -0
- package/dist/modules/aeo/checks.js +127 -0
- package/dist/modules/aeo/checks.js.map +1 -0
- package/dist/modules/aeo/llms.d.ts +24 -0
- package/dist/modules/aeo/llms.d.ts.map +1 -0
- package/dist/modules/aeo/llms.js +92 -0
- package/dist/modules/aeo/llms.js.map +1 -0
- package/dist/modules/aeo/routes.admin.d.ts +12 -0
- package/dist/modules/aeo/routes.admin.d.ts.map +1 -0
- package/dist/modules/aeo/routes.admin.js +37 -0
- package/dist/modules/aeo/routes.admin.js.map +1 -0
- package/dist/modules/analytics/db.d.ts +24 -0
- package/dist/modules/analytics/db.d.ts.map +1 -0
- package/dist/modules/analytics/db.js +24 -0
- package/dist/modules/analytics/db.js.map +1 -0
- package/dist/modules/analytics/routes.admin.d.ts +6 -0
- package/dist/modules/analytics/routes.admin.d.ts.map +1 -0
- package/dist/modules/analytics/routes.admin.js +21 -0
- package/dist/modules/analytics/routes.admin.js.map +1 -0
- package/dist/modules/analytics/routes.public.d.ts +6 -0
- package/dist/modules/analytics/routes.public.d.ts.map +1 -0
- package/dist/modules/analytics/routes.public.js +33 -0
- package/dist/modules/analytics/routes.public.js.map +1 -0
- package/dist/modules/analytics/service.d.ts +16 -0
- package/dist/modules/analytics/service.d.ts.map +1 -0
- package/dist/modules/analytics/service.js +148 -0
- package/dist/modules/analytics/service.js.map +1 -0
- package/dist/modules/authors/routes.admin.d.ts +6 -0
- package/dist/modules/authors/routes.admin.d.ts.map +1 -0
- package/dist/modules/authors/routes.admin.js +29 -0
- package/dist/modules/authors/routes.admin.js.map +1 -0
- package/dist/modules/authors/routes.public.d.ts +6 -0
- package/dist/modules/authors/routes.public.d.ts.map +1 -0
- package/dist/modules/authors/routes.public.js +11 -0
- package/dist/modules/authors/routes.public.js.map +1 -0
- package/dist/modules/authors/service.d.ts +26 -0
- package/dist/modules/authors/service.d.ts.map +1 -0
- package/dist/modules/authors/service.js +116 -0
- package/dist/modules/authors/service.js.map +1 -0
- package/dist/modules/content/db.d.ts +127 -0
- package/dist/modules/content/db.d.ts.map +1 -0
- package/dist/modules/content/db.js +203 -0
- package/dist/modules/content/db.js.map +1 -0
- package/dist/modules/content/publish.d.ts +38 -0
- package/dist/modules/content/publish.d.ts.map +1 -0
- package/dist/modules/content/publish.js +184 -0
- package/dist/modules/content/publish.js.map +1 -0
- package/dist/modules/content/routes.admin.d.ts +8 -0
- package/dist/modules/content/routes.admin.d.ts.map +1 -0
- package/dist/modules/content/routes.admin.js +122 -0
- package/dist/modules/content/routes.admin.js.map +1 -0
- package/dist/modules/content/routes.public.d.ts +6 -0
- package/dist/modules/content/routes.public.d.ts.map +1 -0
- package/dist/modules/content/routes.public.js +56 -0
- package/dist/modules/content/routes.public.js.map +1 -0
- package/dist/modules/content/service.d.ts +137 -0
- package/dist/modules/content/service.d.ts.map +1 -0
- package/dist/modules/content/service.js +348 -0
- package/dist/modules/content/service.js.map +1 -0
- package/dist/modules/forms/db.d.ts +42 -0
- package/dist/modules/forms/db.d.ts.map +1 -0
- package/dist/modules/forms/db.js +62 -0
- package/dist/modules/forms/db.js.map +1 -0
- package/dist/modules/forms/forms-routes.admin.d.ts +6 -0
- package/dist/modules/forms/forms-routes.admin.d.ts.map +1 -0
- package/dist/modules/forms/forms-routes.admin.js +37 -0
- package/dist/modules/forms/forms-routes.admin.js.map +1 -0
- package/dist/modules/forms/forms-service.d.ts +18 -0
- package/dist/modules/forms/forms-service.d.ts.map +1 -0
- package/dist/modules/forms/forms-service.js +121 -0
- package/dist/modules/forms/forms-service.js.map +1 -0
- package/dist/modules/forms/routes.public.d.ts +6 -0
- package/dist/modules/forms/routes.public.d.ts.map +1 -0
- package/dist/modules/forms/routes.public.js +41 -0
- package/dist/modules/forms/routes.public.js.map +1 -0
- package/dist/modules/forms/submissions-routes.admin.d.ts +6 -0
- package/dist/modules/forms/submissions-routes.admin.d.ts.map +1 -0
- package/dist/modules/forms/submissions-routes.admin.js +46 -0
- package/dist/modules/forms/submissions-routes.admin.js.map +1 -0
- package/dist/modules/forms/submissions-service.d.ts +45 -0
- package/dist/modules/forms/submissions-service.d.ts.map +1 -0
- package/dist/modules/forms/submissions-service.js +174 -0
- package/dist/modules/forms/submissions-service.js.map +1 -0
- package/dist/modules/media/db.d.ts +15 -0
- package/dist/modules/media/db.d.ts.map +1 -0
- package/dist/modules/media/db.js +24 -0
- package/dist/modules/media/db.js.map +1 -0
- package/dist/modules/media/routes.admin.d.ts +6 -0
- package/dist/modules/media/routes.admin.d.ts.map +1 -0
- package/dist/modules/media/routes.admin.js +30 -0
- package/dist/modules/media/routes.admin.js.map +1 -0
- package/dist/modules/media/routes.public.d.ts +6 -0
- package/dist/modules/media/routes.public.d.ts.map +1 -0
- package/dist/modules/media/routes.public.js +35 -0
- package/dist/modules/media/routes.public.js.map +1 -0
- package/dist/modules/media/service.d.ts +32 -0
- package/dist/modules/media/service.d.ts.map +1 -0
- package/dist/modules/media/service.js +99 -0
- package/dist/modules/media/service.js.map +1 -0
- package/dist/modules/openapi/spec.d.ts +21 -0
- package/dist/modules/openapi/spec.d.ts.map +1 -0
- package/dist/modules/openapi/spec.js +468 -0
- package/dist/modules/openapi/spec.js.map +1 -0
- package/dist/modules/seo/jsonld.d.ts +25 -0
- package/dist/modules/seo/jsonld.d.ts.map +1 -0
- package/dist/modules/seo/jsonld.js +67 -0
- package/dist/modules/seo/jsonld.js.map +1 -0
- package/dist/modules/seo/routes.public.d.ts +6 -0
- package/dist/modules/seo/routes.public.d.ts.map +1 -0
- package/dist/modules/seo/routes.public.js +39 -0
- package/dist/modules/seo/routes.public.js.map +1 -0
- package/dist/modules/seo/sitemap.d.ts +3 -0
- package/dist/modules/seo/sitemap.d.ts.map +1 -0
- package/dist/modules/seo/sitemap.js +39 -0
- package/dist/modules/seo/sitemap.js.map +1 -0
- package/dist/modules/settings/routes.admin.d.ts +12 -0
- package/dist/modules/settings/routes.admin.d.ts.map +1 -0
- package/dist/modules/settings/routes.admin.js +27 -0
- package/dist/modules/settings/routes.admin.js.map +1 -0
- package/dist/modules/site-documents/routes.admin.d.ts +10 -0
- package/dist/modules/site-documents/routes.admin.d.ts.map +1 -0
- package/dist/modules/site-documents/routes.admin.js +29 -0
- package/dist/modules/site-documents/routes.admin.js.map +1 -0
- package/dist/modules/site-documents/routes.public.d.ts +6 -0
- package/dist/modules/site-documents/routes.public.d.ts.map +1 -0
- package/dist/modules/site-documents/routes.public.js +16 -0
- package/dist/modules/site-documents/routes.public.js.map +1 -0
- package/dist/modules/site-documents/service.d.ts +11 -0
- package/dist/modules/site-documents/service.d.ts.map +1 -0
- package/dist/modules/site-documents/service.js +42 -0
- package/dist/modules/site-documents/service.js.map +1 -0
- package/dist/modules/webhooks/dispatch.d.ts +14 -0
- package/dist/modules/webhooks/dispatch.d.ts.map +1 -0
- package/dist/modules/webhooks/dispatch.js +84 -0
- package/dist/modules/webhooks/dispatch.js.map +1 -0
- package/dist/shared/constants.d.ts +79 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +89 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/envelope.d.ts +37 -0
- package/dist/shared/envelope.d.ts.map +1 -0
- package/dist/shared/envelope.js +46 -0
- package/dist/shared/envelope.js.map +1 -0
- package/dist/shared/hash.d.ts +2 -0
- package/dist/shared/hash.d.ts.map +1 -0
- package/dist/shared/hash.js +8 -0
- package/dist/shared/hash.js.map +1 -0
- package/dist/shared/index.d.ts +13 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +13 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/schemas/aeo.d.ts +182 -0
- package/dist/shared/schemas/aeo.d.ts.map +1 -0
- package/dist/shared/schemas/aeo.js +43 -0
- package/dist/shared/schemas/aeo.js.map +1 -0
- package/dist/shared/schemas/analytics.d.ts +277 -0
- package/dist/shared/schemas/analytics.d.ts.map +1 -0
- package/dist/shared/schemas/analytics.js +74 -0
- package/dist/shared/schemas/analytics.js.map +1 -0
- package/dist/shared/schemas/api-key.d.ts +77 -0
- package/dist/shared/schemas/api-key.d.ts.map +1 -0
- package/dist/shared/schemas/api-key.js +27 -0
- package/dist/shared/schemas/api-key.js.map +1 -0
- package/dist/shared/schemas/author.d.ts +147 -0
- package/dist/shared/schemas/author.d.ts.map +1 -0
- package/dist/shared/schemas/author.js +27 -0
- package/dist/shared/schemas/author.js.map +1 -0
- package/dist/shared/schemas/form.d.ts +341 -0
- package/dist/shared/schemas/form.d.ts.map +1 -0
- package/dist/shared/schemas/form.js +117 -0
- package/dist/shared/schemas/form.js.map +1 -0
- package/dist/shared/schemas/index.d.ts +12 -0
- package/dist/shared/schemas/index.d.ts.map +1 -0
- package/dist/shared/schemas/index.js +12 -0
- package/dist/shared/schemas/index.js.map +1 -0
- package/dist/shared/schemas/media.d.ts +42 -0
- package/dist/shared/schemas/media.d.ts.map +1 -0
- package/dist/shared/schemas/media.js +16 -0
- package/dist/shared/schemas/media.js.map +1 -0
- package/dist/shared/schemas/pagination.d.ts +34 -0
- package/dist/shared/schemas/pagination.d.ts.map +1 -0
- package/dist/shared/schemas/pagination.js +21 -0
- package/dist/shared/schemas/pagination.js.map +1 -0
- package/dist/shared/schemas/post.d.ts +862 -0
- package/dist/shared/schemas/post.d.ts.map +1 -0
- package/dist/shared/schemas/post.js +187 -0
- package/dist/shared/schemas/post.js.map +1 -0
- package/dist/shared/schemas/settings.d.ts +839 -0
- package/dist/shared/schemas/settings.d.ts.map +1 -0
- package/dist/shared/schemas/settings.js +202 -0
- package/dist/shared/schemas/settings.js.map +1 -0
- package/dist/shared/schemas/site-document.d.ts +38 -0
- package/dist/shared/schemas/site-document.d.ts.map +1 -0
- package/dist/shared/schemas/site-document.js +21 -0
- package/dist/shared/schemas/site-document.js.map +1 -0
- package/dist/shared/schemas/webhook.d.ts +39 -0
- package/dist/shared/schemas/webhook.d.ts.map +1 -0
- package/dist/shared/schemas/webhook.js +20 -0
- package/dist/shared/schemas/webhook.js.map +1 -0
- package/dist/spa.d.ts +9 -0
- package/dist/spa.d.ts.map +1 -0
- package/dist/spa.js +57 -0
- package/dist/spa.js.map +1 -0
- package/migrations/0001_settings.sql +39 -0
- package/migrations/0002_content.sql +135 -0
- package/migrations/0003_forms.sql +35 -0
- package/migrations/0004_analytics.sql +42 -0
- package/migrations/0005_media.sql +17 -0
- package/migrations/0006_site_documents.sql +18 -0
- package/migrations/0007_auth.sql +81 -0
- package/migrations/0008_api_keys.sql +20 -0
- package/package.json +68 -0
- package/scripts/bundle-admin.mjs +54 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|