@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,148 @@
|
|
|
1
|
+
import { sha256Hex, } from '../../shared/index.js';
|
|
2
|
+
import { detectBot } from '../../lib/bots.js';
|
|
3
|
+
import { analyticsSession, toPageVisitRecord } from './db.js';
|
|
4
|
+
/**
|
|
5
|
+
* The raw IP is never stored. It is hashed, and salted with the site URL, so
|
|
6
|
+
* the stored value cannot be looked up against a rainbow table of addresses
|
|
7
|
+
* and the same visitor hashes differently on a different site. What remains is
|
|
8
|
+
* enough to count unique visitors and nothing else.
|
|
9
|
+
*/
|
|
10
|
+
async function saltedIpHash(siteUrl, ip) {
|
|
11
|
+
return ip ? sha256Hex(`${siteUrl}:${ip}`) : null;
|
|
12
|
+
}
|
|
13
|
+
export async function recordVisit(db, siteUrl, input, signals) {
|
|
14
|
+
const detection = detectBot(signals.userAgent, signals.cf);
|
|
15
|
+
const ipHash = await saltedIpHash(siteUrl, signals.ip);
|
|
16
|
+
await db
|
|
17
|
+
.prepare(`INSERT INTO page_visits (pathname, referrer, country, ip_hash, user_agent, is_bot, bot_name, bot_score, user_agent_class, session_id)
|
|
18
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)`)
|
|
19
|
+
.bind(input.pathname, input.referrer ?? null, signals.country, ipHash, signals.userAgent, detection.isBot ? 1 : 0, detection.botName, detection.botScore, detection.userAgentClass, input.sessionId)
|
|
20
|
+
.run();
|
|
21
|
+
}
|
|
22
|
+
export async function recordConsent(db, siteUrl, input, signals) {
|
|
23
|
+
const ipHash = await saltedIpHash(siteUrl, signals.ip);
|
|
24
|
+
await db
|
|
25
|
+
.prepare(`INSERT INTO consent_records (session_id, ip_hash, decision, analytics, performance, user_agent)
|
|
26
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6)`)
|
|
27
|
+
.bind(input.sessionId, ipHash, input.decision, input.analytics ? 1 : 0, input.performance ? 1 : 0, signals.userAgent)
|
|
28
|
+
.run();
|
|
29
|
+
}
|
|
30
|
+
export async function listVisitsAdmin(db, query) {
|
|
31
|
+
const session = analyticsSession(db);
|
|
32
|
+
const offset = (query.page - 1) * query.pageSize;
|
|
33
|
+
const conditions = [];
|
|
34
|
+
const bindings = [];
|
|
35
|
+
if (query.isBot !== undefined) {
|
|
36
|
+
conditions.push(`is_bot = ?${bindings.length + 1}`);
|
|
37
|
+
bindings.push(query.isBot ? 1 : 0);
|
|
38
|
+
}
|
|
39
|
+
if (query.userAgentClass) {
|
|
40
|
+
conditions.push(`user_agent_class = ?${bindings.length + 1}`);
|
|
41
|
+
bindings.push(query.userAgentClass);
|
|
42
|
+
}
|
|
43
|
+
if (query.pathname) {
|
|
44
|
+
conditions.push(`pathname = ?${bindings.length + 1}`);
|
|
45
|
+
bindings.push(query.pathname);
|
|
46
|
+
}
|
|
47
|
+
if (query.country) {
|
|
48
|
+
conditions.push(`country = ?${bindings.length + 1}`);
|
|
49
|
+
bindings.push(query.country);
|
|
50
|
+
}
|
|
51
|
+
if (query.search) {
|
|
52
|
+
conditions.push(`(pathname LIKE ?${bindings.length + 1} OR user_agent LIKE ?${bindings.length + 1})`);
|
|
53
|
+
bindings.push(`%${query.search}%`);
|
|
54
|
+
}
|
|
55
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
56
|
+
const { results } = await session
|
|
57
|
+
.prepare(`SELECT * FROM page_visits ${where} ORDER BY visited_at DESC LIMIT ?${bindings.length + 1} OFFSET ?${bindings.length + 2}`)
|
|
58
|
+
.bind(...bindings, query.pageSize, offset)
|
|
59
|
+
.all();
|
|
60
|
+
const { results: countResults } = await session
|
|
61
|
+
.prepare(`SELECT COUNT(*) as count FROM page_visits ${where}`)
|
|
62
|
+
.bind(...bindings)
|
|
63
|
+
.all();
|
|
64
|
+
return { items: results.map(toPageVisitRecord), total: countResults[0]?.count ?? 0 };
|
|
65
|
+
}
|
|
66
|
+
export async function getVisitStats(db, days) {
|
|
67
|
+
const session = analyticsSession(db);
|
|
68
|
+
const since = `-${days} days`;
|
|
69
|
+
const totals = await session
|
|
70
|
+
.prepare(`SELECT
|
|
71
|
+
COUNT(*) as total,
|
|
72
|
+
SUM(CASE WHEN is_bot = 0 THEN 1 ELSE 0 END) as humans,
|
|
73
|
+
SUM(CASE WHEN is_bot = 1 THEN 1 ELSE 0 END) as bots
|
|
74
|
+
FROM page_visits WHERE visited_at > datetime('now', ?1)`)
|
|
75
|
+
.bind(since)
|
|
76
|
+
.first();
|
|
77
|
+
const topPages = (await session
|
|
78
|
+
.prepare(`SELECT pathname, COUNT(*) as count FROM page_visits WHERE visited_at > datetime('now', ?1)
|
|
79
|
+
GROUP BY pathname ORDER BY count DESC LIMIT 10`)
|
|
80
|
+
.bind(since)
|
|
81
|
+
.all()).results;
|
|
82
|
+
const topCountries = (await session
|
|
83
|
+
.prepare(`SELECT country, COUNT(*) as count FROM page_visits WHERE visited_at > datetime('now', ?1) AND country IS NOT NULL
|
|
84
|
+
GROUP BY country ORDER BY count DESC LIMIT 10`)
|
|
85
|
+
.bind(since)
|
|
86
|
+
.all()).results;
|
|
87
|
+
const topReferrers = (await session
|
|
88
|
+
.prepare(`SELECT referrer, COUNT(*) as count FROM page_visits WHERE visited_at > datetime('now', ?1) AND referrer IS NOT NULL
|
|
89
|
+
GROUP BY referrer ORDER BY count DESC LIMIT 10`)
|
|
90
|
+
.bind(since)
|
|
91
|
+
.all()).results;
|
|
92
|
+
const topBots = (await session
|
|
93
|
+
.prepare(`SELECT bot_name as botName, COUNT(*) as count FROM page_visits
|
|
94
|
+
WHERE visited_at > datetime('now', ?1) AND is_bot = 1 AND bot_name IS NOT NULL
|
|
95
|
+
GROUP BY bot_name ORDER BY count DESC LIMIT 10`)
|
|
96
|
+
.bind(since)
|
|
97
|
+
.all()).results;
|
|
98
|
+
const byUserAgentClass = (await session
|
|
99
|
+
.prepare(`SELECT user_agent_class as userAgentClass, COUNT(*) as count FROM page_visits
|
|
100
|
+
WHERE visited_at > datetime('now', ?1)
|
|
101
|
+
GROUP BY user_agent_class ORDER BY count DESC`)
|
|
102
|
+
.bind(since)
|
|
103
|
+
.all()).results;
|
|
104
|
+
const daily = (await session
|
|
105
|
+
.prepare(`SELECT date(visited_at) as date, COUNT(*) as count FROM page_visits WHERE visited_at > datetime('now', ?1)
|
|
106
|
+
GROUP BY date(visited_at) ORDER BY date ASC`)
|
|
107
|
+
.bind(since)
|
|
108
|
+
.all()).results;
|
|
109
|
+
return {
|
|
110
|
+
total: totals?.total ?? 0,
|
|
111
|
+
humans: totals?.humans ?? 0,
|
|
112
|
+
bots: totals?.bots ?? 0,
|
|
113
|
+
topPages,
|
|
114
|
+
topCountries,
|
|
115
|
+
topReferrers,
|
|
116
|
+
topBots,
|
|
117
|
+
byUserAgentClass,
|
|
118
|
+
daily,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export async function getConsentStats(db, days) {
|
|
122
|
+
const session = analyticsSession(db);
|
|
123
|
+
const since = `-${days} days`;
|
|
124
|
+
const totals = await session
|
|
125
|
+
.prepare(`SELECT
|
|
126
|
+
SUM(CASE WHEN decision = 'accepted' THEN 1 ELSE 0 END) as accepted,
|
|
127
|
+
SUM(CASE WHEN decision = 'partial' THEN 1 ELSE 0 END) as partial,
|
|
128
|
+
SUM(CASE WHEN decision = 'declined' THEN 1 ELSE 0 END) as declined,
|
|
129
|
+
SUM(CASE WHEN analytics = 1 THEN 1 ELSE 0 END) as analyticsOptIn,
|
|
130
|
+
SUM(CASE WHEN performance = 1 THEN 1 ELSE 0 END) as performanceOptIn
|
|
131
|
+
FROM consent_records WHERE recorded_at > datetime('now', ?1)`)
|
|
132
|
+
.bind(since)
|
|
133
|
+
.first();
|
|
134
|
+
const daily = (await session
|
|
135
|
+
.prepare(`SELECT date(recorded_at) as date, decision, COUNT(*) as count FROM consent_records
|
|
136
|
+
WHERE recorded_at > datetime('now', ?1) GROUP BY date(recorded_at), decision ORDER BY date ASC`)
|
|
137
|
+
.bind(since)
|
|
138
|
+
.all()).results;
|
|
139
|
+
return {
|
|
140
|
+
accepted: totals?.accepted ?? 0,
|
|
141
|
+
partial: totals?.partial ?? 0,
|
|
142
|
+
declined: totals?.declined ?? 0,
|
|
143
|
+
analyticsOptIn: totals?.analyticsOptIn ?? 0,
|
|
144
|
+
performanceOptIn: totals?.performanceOptIn ?? 0,
|
|
145
|
+
daily,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/modules/analytics/service.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAQV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAqB,MAAM,SAAS,CAAC;AASjF;;;;;GAKG;AACH,KAAK,UAAU,YAAY,CAAC,OAAe,EAAE,EAAiB;IAC5D,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,EAAc,EACd,OAAe,EACf,KAAsB,EACtB,OAAuB;IAEvB,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAEvD,MAAM,EAAE;SACL,OAAO,CACN;wDACkD,CACnD;SACA,IAAI,CACH,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,QAAQ,IAAI,IAAI,EACtB,OAAO,CAAC,OAAO,EACf,MAAM,EACN,OAAO,CAAC,SAAS,EACjB,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACvB,SAAS,CAAC,OAAO,EACjB,SAAS,CAAC,QAAQ,EAClB,SAAS,CAAC,cAAc,EACxB,KAAK,CAAC,SAAS,CAChB;SACA,GAAG,EAAE,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,EAAc,EACd,OAAe,EACf,KAAwB,EACxB,OAAiD;IAEjD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAEvD,MAAM,EAAE;SACL,OAAO,CACN;uCACiC,CAClC;SACA,IAAI,CACH,KAAK,CAAC,SAAS,EACf,MAAM,EACN,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACvB,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACzB,OAAO,CAAC,SAAS,CAClB;SACA,GAAG,EAAE,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,EAAc,EACd,KAAqB;IAErB,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC;IACjD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,UAAU,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;QACpD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QACzB,UAAU,CAAC,IAAI,CAAC,uBAAuB,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,eAAe,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;QACtD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,UAAU,CAAC,IAAI,CAAC,cAAc,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;QACrD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,UAAU,CAAC,IAAI,CACb,mBAAmB,QAAQ,CAAC,MAAM,GAAG,CAAC,wBAAwB,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,CACrF,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/E,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO;SAC9B,OAAO,CACN,6BAA6B,KAAK,oCAAoC,QAAQ,CAAC,MAAM,GAAG,CAAC,YAAY,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAC3H;SACA,IAAI,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC;SACzC,GAAG,EAAgB,CAAC;IAEvB,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,OAAO;SAC5C,OAAO,CAAC,6CAA6C,KAAK,EAAE,CAAC;SAC7D,IAAI,CAAC,GAAG,QAAQ,CAAC;SACjB,GAAG,EAAqB,CAAC;IAE5B,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;AACvF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAc,EAAE,IAAY;IAC9D,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,CAAC;IAE9B,MAAM,MAAM,GAAG,MAAM,OAAO;SACzB,OAAO,CACN;;;;+DAIyD,CAC1D;SACA,IAAI,CAAC,KAAK,CAAC;SACX,KAAK,EAAiE,CAAC;IAE1E,MAAM,QAAQ,GAAG,CACf,MAAM,OAAO;SACV,OAAO,CACN;wDACgD,CACjD;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAuC,CAC9C,CAAC,OAAO,CAAC;IAEV,MAAM,YAAY,GAAG,CACnB,MAAM,OAAO;SACV,OAAO,CACN;uDAC+C,CAChD;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAsC,CAC7C,CAAC,OAAO,CAAC;IAEV,MAAM,YAAY,GAAG,CACnB,MAAM,OAAO;SACV,OAAO,CACN;wDACgD,CACjD;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAuC,CAC9C,CAAC,OAAO,CAAC;IAEV,MAAM,OAAO,GAAG,CACd,MAAM,OAAO;SACV,OAAO,CACN;;wDAEgD,CACjD;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAsC,CAC7C,CAAC,OAAO,CAAC;IAEV,MAAM,gBAAgB,GAAG,CACvB,MAAM,OAAO;SACV,OAAO,CACN;;uDAE+C,CAChD;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAqD,CAC5D,CAAC,OAAO,CAAC;IAEV,MAAM,KAAK,GAAG,CACZ,MAAM,OAAO;SACV,OAAO,CACN;qDAC6C,CAC9C;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAmC,CAC1C,CAAC,OAAO,CAAC;IAEV,OAAO;QACL,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;QACzB,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;QAC3B,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC;QACvB,QAAQ;QACR,YAAY;QACZ,YAAY;QACZ,OAAO;QACP,gBAAgB;QAChB,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAc,EAAE,IAAY;IAChE,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,CAAC;IAE9B,MAAM,MAAM,GAAG,MAAM,OAAO;SACzB,OAAO,CACN;;;;;;oEAM8D,CAC/D;SACA,IAAI,CAAC,KAAK,CAAC;SACX,KAAK,EAMF,CAAC;IAEP,MAAM,KAAK,GAAG,CACZ,MAAM,OAAO;SACV,OAAO,CACN;wGACgG,CACjG;SACA,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAkF,CACzF,CAAC,OAAO,CAAC;IAEV,OAAO;QACL,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC;QAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,CAAC;QAC7B,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC;QAC/B,cAAc,EAAE,MAAM,EAAE,cAAc,IAAI,CAAC;QAC3C,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,IAAI,CAAC;QAC/C,KAAK;KACN,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../../src/modules/authors/routes.admin.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,eAAO,MAAM,kBAAkB;cAAwB,QAAQ;yCAAK,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createAuthorInputSchema, ok, paginationQuerySchema, updateAuthorInputSchema, } from '../../shared/index.js';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { parseOrThrow } from '../../lib/validate.js';
|
|
4
|
+
import { createAuthor, deleteAuthor, getAuthor, listAuthors, updateAuthor } from './service.js';
|
|
5
|
+
export const authorsAdminRoutes = new Hono();
|
|
6
|
+
authorsAdminRoutes.get('/', async (c) => {
|
|
7
|
+
const query = parseOrThrow(paginationQuerySchema, c.req.query());
|
|
8
|
+
const { items, total } = await listAuthors(c.env.DB, query);
|
|
9
|
+
return c.json(ok({ items, page: query.page, pageSize: query.pageSize, total }));
|
|
10
|
+
});
|
|
11
|
+
authorsAdminRoutes.post('/', async (c) => {
|
|
12
|
+
const input = parseOrThrow(createAuthorInputSchema, await c.req.json());
|
|
13
|
+
const author = await createAuthor(c.env.DB, input);
|
|
14
|
+
return c.json(ok({ author }), 201);
|
|
15
|
+
});
|
|
16
|
+
authorsAdminRoutes.get('/:id', async (c) => {
|
|
17
|
+
const author = await getAuthor(c.env.DB, c.req.param('id'));
|
|
18
|
+
return c.json(ok({ author }));
|
|
19
|
+
});
|
|
20
|
+
authorsAdminRoutes.put('/:id', async (c) => {
|
|
21
|
+
const patch = parseOrThrow(updateAuthorInputSchema, await c.req.json());
|
|
22
|
+
const author = await updateAuthor(c.env.DB, c.req.param('id'), patch);
|
|
23
|
+
return c.json(ok({ author }));
|
|
24
|
+
});
|
|
25
|
+
authorsAdminRoutes.delete('/:id', async (c) => {
|
|
26
|
+
await deleteAuthor(c.env.DB, c.req.param('id'));
|
|
27
|
+
return c.json(ok({}));
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=routes.admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../../src/modules/authors/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,EAAE,EACF,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhG,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,IAAI,EAA0B,CAAC;AAErE,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACtC,MAAM,KAAK,GAAG,YAAY,CAAC,qBAAqB,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACjE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5D,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,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACvC,MAAM,KAAK,GAAG,YAAY,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,kBAAkB,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACzC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,kBAAkB,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACzC,MAAM,KAAK,GAAG,YAAY,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;IACtE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC5C,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.public.d.ts","sourceRoot":"","sources":["../../../src/modules/authors/routes.public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,eAAO,MAAM,mBAAmB;cAAwB,QAAQ;yCAAK,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ok } from '../../shared/index.js';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { corsMiddleware } from '../../middleware/cors.js';
|
|
4
|
+
import { getAuthorBySlug } from './service.js';
|
|
5
|
+
export const authorsPublicRoutes = new Hono();
|
|
6
|
+
authorsPublicRoutes.use('/:slug', corsMiddleware('publicRead'));
|
|
7
|
+
authorsPublicRoutes.get('/:slug', async (c) => {
|
|
8
|
+
const author = await getAuthorBySlug(c.env.DB, c.req.param('slug'));
|
|
9
|
+
return c.json(ok({ author }));
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=routes.public.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.public.js","sourceRoot":"","sources":["../../../src/modules/authors/routes.public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,IAAI,EAA0B,CAAC;AAEtE,mBAAmB,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;AAEhE,mBAAmB,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC5C,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AuthorRecord, CreateAuthorInput, UpdateAuthorInput } from '../../shared/index.js';
|
|
2
|
+
export declare function createAuthor(db: D1Database, input: CreateAuthorInput): Promise<AuthorRecord>;
|
|
3
|
+
export declare function getAuthor(db: D1Database, id: string): Promise<AuthorRecord>;
|
|
4
|
+
export declare function getAuthorBySlug(db: D1Database, slug: string): Promise<AuthorRecord>;
|
|
5
|
+
/** Batch fetch preserving the input order (for rendering ordered bylines). Silently drops unknown ids. */
|
|
6
|
+
export declare function getAuthorsByIds(db: D1Database, ids: string[]): Promise<AuthorRecord[]>;
|
|
7
|
+
export declare function listAuthors(db: D1Database, query: {
|
|
8
|
+
page: number;
|
|
9
|
+
pageSize: number;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
items: AuthorRecord[];
|
|
12
|
+
total: number;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function updateAuthor(db: D1Database, id: string, patch: UpdateAuthorInput): Promise<AuthorRecord>;
|
|
15
|
+
/**
|
|
16
|
+
* Refuses to delete an author currently credited on any post OR referenced
|
|
17
|
+
* by any pending draft. This avoids silently orphaning post_authors rows,
|
|
18
|
+
* since D1 does not enforce the foreign key. The draft check matters
|
|
19
|
+
* because a draft's author_ids live only in post_drafts JSON until publish:
|
|
20
|
+
* without it, deleting an author that only a draft references succeeds, and
|
|
21
|
+
* publishing that draft then writes a post_authors row pointing at a
|
|
22
|
+
* nonexistent author. The LIKE pattern is safe because
|
|
23
|
+
* ids are UUIDs (no quote or wildcard characters).
|
|
24
|
+
*/
|
|
25
|
+
export declare function deleteAuthor(db: D1Database, id: string): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/authors/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAyC5G,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,YAAY,CAAC,CAyBvB;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAEjF;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAIzF;AAED,0GAA0G;AAC1G,wBAAsB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAW5F;AAED,wBAAsB,WAAW,CAC/B,EAAE,EAAE,UAAU,EACd,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAanD;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,YAAY,CAAC,CA6BvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB5E"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { HttpError } from '../../lib/errors.js';
|
|
2
|
+
function toAuthorRecord(row) {
|
|
3
|
+
return {
|
|
4
|
+
id: row.id,
|
|
5
|
+
slug: row.slug,
|
|
6
|
+
name: row.name,
|
|
7
|
+
type: row.type,
|
|
8
|
+
roleTitle: row.role_title ?? undefined,
|
|
9
|
+
avatarUrl: row.avatar_url ?? undefined,
|
|
10
|
+
bio: row.bio ?? undefined,
|
|
11
|
+
links: JSON.parse(row.links),
|
|
12
|
+
createdAt: row.created_at,
|
|
13
|
+
updatedAt: row.updated_at,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function fetchAuthorRow(db, id) {
|
|
17
|
+
const row = await db.prepare('SELECT * FROM authors WHERE id = ?1').bind(id).first();
|
|
18
|
+
if (!row)
|
|
19
|
+
throw new HttpError('not_found', 'author not found');
|
|
20
|
+
return row;
|
|
21
|
+
}
|
|
22
|
+
async function fetchAuthorRowBySlug(db, slug) {
|
|
23
|
+
return db.prepare('SELECT * FROM authors WHERE slug = ?1').bind(slug).first();
|
|
24
|
+
}
|
|
25
|
+
export async function createAuthor(db, input) {
|
|
26
|
+
const existing = await fetchAuthorRowBySlug(db, input.slug);
|
|
27
|
+
if (existing) {
|
|
28
|
+
throw new HttpError('validation_error', `slug "${input.slug}" is already in use`);
|
|
29
|
+
}
|
|
30
|
+
const id = crypto.randomUUID();
|
|
31
|
+
await db
|
|
32
|
+
.prepare(`INSERT INTO authors (id, slug, name, type, role_title, avatar_url, bio, links)
|
|
33
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)`)
|
|
34
|
+
.bind(id, input.slug, input.name, input.type, input.roleTitle ?? null, input.avatarUrl ?? null, input.bio ?? null, JSON.stringify(input.links))
|
|
35
|
+
.run();
|
|
36
|
+
return toAuthorRecord(await fetchAuthorRow(db, id));
|
|
37
|
+
}
|
|
38
|
+
export async function getAuthor(db, id) {
|
|
39
|
+
return toAuthorRecord(await fetchAuthorRow(db, id));
|
|
40
|
+
}
|
|
41
|
+
export async function getAuthorBySlug(db, slug) {
|
|
42
|
+
const row = await fetchAuthorRowBySlug(db, slug);
|
|
43
|
+
if (!row)
|
|
44
|
+
throw new HttpError('not_found', 'author not found');
|
|
45
|
+
return toAuthorRecord(row);
|
|
46
|
+
}
|
|
47
|
+
/** Batch fetch preserving the input order (for rendering ordered bylines). Silently drops unknown ids. */
|
|
48
|
+
export async function getAuthorsByIds(db, ids) {
|
|
49
|
+
if (ids.length === 0)
|
|
50
|
+
return [];
|
|
51
|
+
const placeholders = ids.map((_, i) => `?${i + 1}`).join(', ');
|
|
52
|
+
const { results } = await db
|
|
53
|
+
.prepare(`SELECT * FROM authors WHERE id IN (${placeholders})`)
|
|
54
|
+
.bind(...ids)
|
|
55
|
+
.all();
|
|
56
|
+
const byId = new Map(results.map((row) => [row.id, toAuthorRecord(row)]));
|
|
57
|
+
return ids
|
|
58
|
+
.map((id) => byId.get(id))
|
|
59
|
+
.filter((author) => author !== undefined);
|
|
60
|
+
}
|
|
61
|
+
export async function listAuthors(db, query) {
|
|
62
|
+
const offset = (query.page - 1) * query.pageSize;
|
|
63
|
+
const { results } = await db
|
|
64
|
+
.prepare('SELECT * FROM authors ORDER BY name ASC LIMIT ?1 OFFSET ?2')
|
|
65
|
+
.bind(query.pageSize, offset)
|
|
66
|
+
.all();
|
|
67
|
+
const { results: countResults } = await db
|
|
68
|
+
.prepare('SELECT COUNT(*) as count FROM authors')
|
|
69
|
+
.all();
|
|
70
|
+
return { items: results.map(toAuthorRecord), total: countResults[0]?.count ?? 0 };
|
|
71
|
+
}
|
|
72
|
+
export async function updateAuthor(db, id, patch) {
|
|
73
|
+
const row = await fetchAuthorRow(db, id);
|
|
74
|
+
if (patch.slug && patch.slug !== row.slug) {
|
|
75
|
+
const existing = await fetchAuthorRowBySlug(db, patch.slug);
|
|
76
|
+
if (existing) {
|
|
77
|
+
throw new HttpError('validation_error', `slug "${patch.slug}" is already in use`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
await db
|
|
81
|
+
.prepare(`UPDATE authors SET
|
|
82
|
+
slug=?2, name=?3, type=?4, role_title=?5, avatar_url=?6, bio=?7, links=?8, updated_at=datetime('now')
|
|
83
|
+
WHERE id=?1`)
|
|
84
|
+
.bind(id, patch.slug ?? row.slug, patch.name ?? row.name, patch.type ?? row.type, patch.roleTitle !== undefined ? patch.roleTitle : row.role_title, patch.avatarUrl !== undefined ? patch.avatarUrl : row.avatar_url, patch.bio !== undefined ? patch.bio : row.bio, patch.links ? JSON.stringify(patch.links) : row.links)
|
|
85
|
+
.run();
|
|
86
|
+
return toAuthorRecord(await fetchAuthorRow(db, id));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Refuses to delete an author currently credited on any post OR referenced
|
|
90
|
+
* by any pending draft. This avoids silently orphaning post_authors rows,
|
|
91
|
+
* since D1 does not enforce the foreign key. The draft check matters
|
|
92
|
+
* because a draft's author_ids live only in post_drafts JSON until publish:
|
|
93
|
+
* without it, deleting an author that only a draft references succeeds, and
|
|
94
|
+
* publishing that draft then writes a post_authors row pointing at a
|
|
95
|
+
* nonexistent author. The LIKE pattern is safe because
|
|
96
|
+
* ids are UUIDs (no quote or wildcard characters).
|
|
97
|
+
*/
|
|
98
|
+
export async function deleteAuthor(db, id) {
|
|
99
|
+
await fetchAuthorRow(db, id);
|
|
100
|
+
const { results } = await db
|
|
101
|
+
.prepare('SELECT COUNT(*) as count FROM post_authors WHERE author_id = ?1')
|
|
102
|
+
.bind(id)
|
|
103
|
+
.all();
|
|
104
|
+
if ((results[0]?.count ?? 0) > 0) {
|
|
105
|
+
throw new HttpError('validation_error', 'author is credited on one or more posts');
|
|
106
|
+
}
|
|
107
|
+
const { results: draftResults } = await db
|
|
108
|
+
.prepare('SELECT COUNT(*) as count FROM post_drafts WHERE author_ids LIKE ?1')
|
|
109
|
+
.bind(`%"${id}"%`)
|
|
110
|
+
.all();
|
|
111
|
+
if ((draftResults[0]?.count ?? 0) > 0) {
|
|
112
|
+
throw new HttpError('validation_error', 'author is referenced by a pending draft');
|
|
113
|
+
}
|
|
114
|
+
await db.prepare('DELETE FROM authors WHERE id = ?1').bind(id).run();
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/modules/authors/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Cta, PostType, PostRecord, PostStatus, PostVersionRecord } from '../../shared/index.js';
|
|
2
|
+
/** Raw D1 row shapes (snake_case columns), distinct from the camelCase API records in @we8/shared. */
|
|
3
|
+
export interface PostRow {
|
|
4
|
+
id: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
type: PostType;
|
|
7
|
+
title: string;
|
|
8
|
+
excerpt: string | null;
|
|
9
|
+
content: string;
|
|
10
|
+
cover_image_url: string | null;
|
|
11
|
+
category: string | null;
|
|
12
|
+
tags: string;
|
|
13
|
+
status: PostStatus;
|
|
14
|
+
og_title: string | null;
|
|
15
|
+
og_description: string | null;
|
|
16
|
+
og_image_url: string | null;
|
|
17
|
+
answer_summary: string | null;
|
|
18
|
+
question_heading: string | null;
|
|
19
|
+
cta: string | null;
|
|
20
|
+
current_version_id: string | null;
|
|
21
|
+
current_version_number: number | null;
|
|
22
|
+
view_count: number;
|
|
23
|
+
like_count: number;
|
|
24
|
+
word_count: number | null;
|
|
25
|
+
published_at: string | null;
|
|
26
|
+
created_at: string;
|
|
27
|
+
updated_at: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PostDraftRow {
|
|
30
|
+
post_id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
excerpt: string | null;
|
|
33
|
+
content: string;
|
|
34
|
+
cover_image_url: string | null;
|
|
35
|
+
category: string | null;
|
|
36
|
+
tags: string;
|
|
37
|
+
og_title: string | null;
|
|
38
|
+
og_description: string | null;
|
|
39
|
+
og_image_url: string | null;
|
|
40
|
+
answer_summary: string | null;
|
|
41
|
+
question_heading: string | null;
|
|
42
|
+
cta: string | null;
|
|
43
|
+
author_ids: string;
|
|
44
|
+
updated_at: string;
|
|
45
|
+
}
|
|
46
|
+
export interface PostVersionRow {
|
|
47
|
+
id: string;
|
|
48
|
+
post_id: string;
|
|
49
|
+
version: number;
|
|
50
|
+
title: string;
|
|
51
|
+
excerpt: string | null;
|
|
52
|
+
content: string;
|
|
53
|
+
cover_image_url: string | null;
|
|
54
|
+
category: string | null;
|
|
55
|
+
tags: string;
|
|
56
|
+
og_title: string | null;
|
|
57
|
+
og_description: string | null;
|
|
58
|
+
og_image_url: string | null;
|
|
59
|
+
answer_summary: string | null;
|
|
60
|
+
question_heading: string | null;
|
|
61
|
+
cta: string | null;
|
|
62
|
+
author_ids: string;
|
|
63
|
+
word_count: number | null;
|
|
64
|
+
note: string | null;
|
|
65
|
+
published_at: string;
|
|
66
|
+
published_by: string | null;
|
|
67
|
+
}
|
|
68
|
+
/** The content fields every one of posts/post_drafts/post_versions carries in common. */
|
|
69
|
+
export interface ContentFields {
|
|
70
|
+
title: string;
|
|
71
|
+
excerpt: string | null;
|
|
72
|
+
content: string;
|
|
73
|
+
coverImageUrl: string | null;
|
|
74
|
+
category: string | null;
|
|
75
|
+
tags: string[];
|
|
76
|
+
ogTitle: string | null;
|
|
77
|
+
ogDescription: string | null;
|
|
78
|
+
ogImageUrl: string | null;
|
|
79
|
+
answerSummary: string | null;
|
|
80
|
+
questionHeading: string | null;
|
|
81
|
+
cta: Cta | null;
|
|
82
|
+
}
|
|
83
|
+
export declare function toPostRecord(row: PostRow, hasPendingDraft: boolean, authorIds?: string[]): PostRecord;
|
|
84
|
+
export declare function toPostVersionRecord(row: PostVersionRow): PostVersionRecord;
|
|
85
|
+
export declare function fetchPostRow(db: D1Database, id: string): Promise<PostRow>;
|
|
86
|
+
export declare function fetchPostRowBySlug(db: D1Database, slug: string): Promise<PostRow | null>;
|
|
87
|
+
export declare function fetchDraftRow(db: D1Database, postId: string): Promise<PostDraftRow | null>;
|
|
88
|
+
export declare function hasDraft(db: D1Database, postId: string): Promise<boolean>;
|
|
89
|
+
export declare function fetchVersionRow(db: D1Database, postId: string, version: number): Promise<PostVersionRow | null>;
|
|
90
|
+
/** Validates every id exists in authors; throws otherwise (D1 does not enforce the FK). */
|
|
91
|
+
export declare function assertAuthorsExist(db: D1Database, authorIds: string[]): Promise<void>;
|
|
92
|
+
/** Replaces a post's author associations to match authorIds, in order. */
|
|
93
|
+
export declare function syncPostAuthorsStatements(db: D1Database, postId: string, authorIds: string[]): D1PreparedStatement[];
|
|
94
|
+
export declare function postRowToContentFields(row: PostRow): ContentFields;
|
|
95
|
+
export declare function draftRowToContentFields(row: PostDraftRow): ContentFields;
|
|
96
|
+
export declare function versionRowToContentFields(row: PostVersionRow): ContentFields;
|
|
97
|
+
interface ContentPatch {
|
|
98
|
+
title?: string | undefined;
|
|
99
|
+
excerpt?: string | undefined;
|
|
100
|
+
coverImageUrl?: string | undefined;
|
|
101
|
+
category?: string | undefined;
|
|
102
|
+
tags?: string[] | undefined;
|
|
103
|
+
ogTitle?: string | undefined;
|
|
104
|
+
ogDescription?: string | undefined;
|
|
105
|
+
ogImageUrl?: string | undefined;
|
|
106
|
+
answerSummary?: string | undefined;
|
|
107
|
+
questionHeading?: string | undefined;
|
|
108
|
+
cta?: Cta | null | undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Merges a partial content patch over a base. `sanitizedContent` is applied
|
|
112
|
+
* separately since sanitization happens once, before merge. A field that is
|
|
113
|
+
* `undefined` in the patch (key omitted) keeps the base value; an explicit
|
|
114
|
+
* value replaces it, and "" clears to NULL (the wire protocol for "unset" on
|
|
115
|
+
* a field-merge update: JSON cannot carry undefined, so "" is the only way a
|
|
116
|
+
* client can express clearing; normalized here, the single funnel every
|
|
117
|
+
* update path flows through, so rows never store empty strings). `cta` is a
|
|
118
|
+
* whole object, not a string, so it uses `null` directly as its own clear
|
|
119
|
+
* signal instead of the ""-trick: JSON already distinguishes an explicit
|
|
120
|
+
* null from an omitted key, which is all this needs.
|
|
121
|
+
*/
|
|
122
|
+
export declare function mergeContentFields(base: ContentFields, patch: ContentPatch, sanitizedContent: string | undefined): ContentFields;
|
|
123
|
+
export declare function currentAuthorIds(db: D1Database, postId: string): Promise<string[]>;
|
|
124
|
+
/** Appends "-2", "-3", ... until the slug is free. Used by duplicatePost. */
|
|
125
|
+
export declare function generateUniqueSlug(db: D1Database, baseSlug: string): Promise<string>;
|
|
126
|
+
export {};
|
|
127
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/modules/content/db.ts"],"names":[],"mappings":"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"}
|