@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,56 @@
|
|
|
1
|
+
import { ok, resolveContentPath, postListQuerySchema } from '../../shared/index.js';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getSettings } from '../../env.js';
|
|
5
|
+
import { parseOrThrow } from '../../lib/validate.js';
|
|
6
|
+
import { corsMiddleware, enforceOrigin } from '../../middleware/cors.js';
|
|
7
|
+
import { rateLimit } from '../../middleware/rate-limit.js';
|
|
8
|
+
import { getAuthorsByIds } from '../authors/service.js';
|
|
9
|
+
import { buildPostJsonLd } from '../seo/jsonld.js';
|
|
10
|
+
import { getPublishedPostBySlug, listPublishedPosts, recordView, toggleLike } from './service.js';
|
|
11
|
+
const sessionIdBodySchema = z.object({ sessionId: z.string().min(1).max(200) });
|
|
12
|
+
async function parseJsonBody(request) {
|
|
13
|
+
try {
|
|
14
|
+
return await request.json();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export const contentPublicRoutes = new Hono();
|
|
21
|
+
contentPublicRoutes.use('/', corsMiddleware('publicRead'));
|
|
22
|
+
contentPublicRoutes.use('/:slug', corsMiddleware('publicRead'));
|
|
23
|
+
contentPublicRoutes.use('/:slug/view', corsMiddleware('publicWrite'), enforceOrigin, rateLimit('engagement'));
|
|
24
|
+
contentPublicRoutes.use('/:slug/like', corsMiddleware('publicWrite'), enforceOrigin, rateLimit('engagement'));
|
|
25
|
+
contentPublicRoutes.get('/', async (c) => {
|
|
26
|
+
const query = parseOrThrow(postListQuerySchema, c.req.query());
|
|
27
|
+
const { items, total } = await listPublishedPosts(c.env.DB, query);
|
|
28
|
+
return c.json(ok({ items, page: query.page, pageSize: query.pageSize, total }));
|
|
29
|
+
});
|
|
30
|
+
contentPublicRoutes.get('/:slug', async (c) => {
|
|
31
|
+
const post = await getPublishedPostBySlug(c.env.DB, c.req.param('slug'));
|
|
32
|
+
const [settings, authors] = await Promise.all([
|
|
33
|
+
getSettings(c.env.DB),
|
|
34
|
+
getAuthorsByIds(c.env.DB, post.authorIds),
|
|
35
|
+
]);
|
|
36
|
+
const jsonld = buildPostJsonLd(post, authors, c.env.SITE_URL, resolveContentPath(post.type, settings), settings.publisher);
|
|
37
|
+
// `authors` ships resolved (not just post.authorIds): a public consumer has
|
|
38
|
+
// no way to turn ids into names/avatars (the authors public route is
|
|
39
|
+
// by-slug only), yet any frontend rendering a byline needs them, and the
|
|
40
|
+
// route already loads them for the JSON-LD above. Found migrating the
|
|
41
|
+
// frontend migration, where the legacy API returned authors inline.
|
|
42
|
+
return c.json(ok({ post, authors, jsonld }));
|
|
43
|
+
});
|
|
44
|
+
contentPublicRoutes.post('/:slug/view', async (c) => {
|
|
45
|
+
const body = parseOrThrow(sessionIdBodySchema, await parseJsonBody(c.req.raw));
|
|
46
|
+
const post = await getPublishedPostBySlug(c.env.DB, c.req.param('slug'));
|
|
47
|
+
await recordView(c.env.DB, post.postId, body.sessionId);
|
|
48
|
+
return c.json(ok({}));
|
|
49
|
+
});
|
|
50
|
+
contentPublicRoutes.post('/:slug/like', async (c) => {
|
|
51
|
+
const body = parseOrThrow(sessionIdBodySchema, await parseJsonBody(c.req.raw));
|
|
52
|
+
const post = await getPublishedPostBySlug(c.env.DB, c.req.param('slug'));
|
|
53
|
+
const result = await toggleLike(c.env.DB, post.postId, body.sessionId);
|
|
54
|
+
return c.json(ok(result));
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=routes.public.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.public.js","sourceRoot":"","sources":["../../../src/modules/content/routes.public.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { ContentStats, CreatePostInput, PostListQuery, PostRecord, UpdatePostInput } from '../../shared/index.js';
|
|
2
|
+
export declare function createPost(db: D1Database, input: CreatePostInput): Promise<PostRecord>;
|
|
3
|
+
/**
|
|
4
|
+
* Routes an edit to its correct home: directly onto `posts` if the post has
|
|
5
|
+
* never been published, or forked into `post_drafts` (upserted) if it has.
|
|
6
|
+
* Editing a published post upserts into post_drafts only; the posts row is
|
|
7
|
+
* untouched until publish.
|
|
8
|
+
*/
|
|
9
|
+
export declare function updateWorkingCopy(db: D1Database, postId: string, patch: UpdatePostInput): Promise<PostRecord>;
|
|
10
|
+
/**
|
|
11
|
+
* The admin-facing "what should the editor show" resolution: `posts` alone
|
|
12
|
+
* is not enough once a pending draft exists: because an edit to a published
|
|
13
|
+
* post lands in post_drafts and leaves the posts row alone, that row's
|
|
14
|
+
* content, title, and the rest stay the last-PUBLISHED values while a draft
|
|
15
|
+
* is pending, not the being-edited ones. Non-content fields (status,
|
|
16
|
+
* currentVersionId, viewCount, timestamps, ...) always come from `posts`
|
|
17
|
+
* regardless, since post_drafts carries no equivalents for those.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveAdminPostRecord(db: D1Database, postId: string): Promise<PostRecord>;
|
|
20
|
+
export declare function getPostAdmin(db: D1Database, postId: string): Promise<PostRecord>;
|
|
21
|
+
export interface ListPostsAdminQuery {
|
|
22
|
+
page: number;
|
|
23
|
+
pageSize: number;
|
|
24
|
+
status?: string | undefined;
|
|
25
|
+
type?: string | undefined;
|
|
26
|
+
category?: string | undefined;
|
|
27
|
+
}
|
|
28
|
+
export declare function listPostsAdmin(db: D1Database, query: ListPostsAdminQuery): Promise<{
|
|
29
|
+
items: PostRecord[];
|
|
30
|
+
total: number;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Content performance, all-time rather than day-windowed:
|
|
34
|
+
* view_count/like_count are already-cumulative derived columns (see
|
|
35
|
+
* recordView/toggleLike below), so there is no per-day breakdown to slice
|
|
36
|
+
* without joining post_views/post_likes by date, which nothing here needs.
|
|
37
|
+
* Ranked by view_count regardless of current status (an archived post that
|
|
38
|
+
* was once popular still deserves to show up; the Status column, not a
|
|
39
|
+
* filter, communicates that it is no longer live).
|
|
40
|
+
*/
|
|
41
|
+
export declare function getContentStats(db: D1Database, limit?: number): Promise<ContentStats>;
|
|
42
|
+
export declare function hardDeletePost(db: D1Database, postId: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Clones the current working copy into a brand new, never-published draft.
|
|
45
|
+
* Publish history is deliberately not copied: the duplicate is a new piece of
|
|
46
|
+
* content that happens to start from this one's text, not a fork of its past.
|
|
47
|
+
*/
|
|
48
|
+
export declare function duplicatePost(db: D1Database, postId: string): Promise<PostRecord>;
|
|
49
|
+
/**
|
|
50
|
+
* Counters are DERIVED from the tracking tables inside the same batch, never
|
|
51
|
+
* incremented/decremented blind. A blind `like_count - 1` drifts under
|
|
52
|
+
* concurrent requests (two unlikes for the same session both pass the
|
|
53
|
+
* existence check, the second DELETE removes zero rows, both decrements run,
|
|
54
|
+
* and the counter can go negative). COUNT(*) in the same atomic batch is
|
|
55
|
+
* idempotent and cannot drift.
|
|
56
|
+
*/
|
|
57
|
+
export declare function recordView(db: D1Database, postId: string, sessionId: string): Promise<void>;
|
|
58
|
+
export interface ToggleLikeResult {
|
|
59
|
+
liked: boolean;
|
|
60
|
+
likeCount: number;
|
|
61
|
+
}
|
|
62
|
+
export declare function toggleLike(db: D1Database, postId: string, sessionId: string): Promise<ToggleLikeResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Public reads serve from the current version snapshot, not the posts mirror. `pv.*` already carries `post_id` (the FK back
|
|
65
|
+
* to posts); deliberately not also aliasing `posts.id`, which would collide
|
|
66
|
+
* with that same column name in the result row.
|
|
67
|
+
*/
|
|
68
|
+
export declare function getPublishedPostBySlug(db: D1Database, slug: string): Promise<{
|
|
69
|
+
slug: string;
|
|
70
|
+
type: "blog" | "article" | "news" | "research" | "whitepaper" | "case-study";
|
|
71
|
+
viewCount: number;
|
|
72
|
+
likeCount: number;
|
|
73
|
+
id: string;
|
|
74
|
+
title: string;
|
|
75
|
+
excerpt: string | null;
|
|
76
|
+
content: string;
|
|
77
|
+
coverImageUrl: string | null;
|
|
78
|
+
category: string | null;
|
|
79
|
+
tags: string[];
|
|
80
|
+
ogTitle: string | null;
|
|
81
|
+
ogDescription: string | null;
|
|
82
|
+
ogImageUrl: string | null;
|
|
83
|
+
answerSummary: string | null;
|
|
84
|
+
questionHeading: string | null;
|
|
85
|
+
authorIds: string[];
|
|
86
|
+
cta: {
|
|
87
|
+
style: "link" | "gated-download";
|
|
88
|
+
buttonLabel: string;
|
|
89
|
+
url?: string | undefined;
|
|
90
|
+
heading?: string | undefined;
|
|
91
|
+
formKey?: string | undefined;
|
|
92
|
+
assetUrl?: string | undefined;
|
|
93
|
+
} | null;
|
|
94
|
+
note: string | null;
|
|
95
|
+
version: number;
|
|
96
|
+
wordCount: number | null;
|
|
97
|
+
publishedAt: string;
|
|
98
|
+
postId: string;
|
|
99
|
+
publishedBy: string | null;
|
|
100
|
+
}>;
|
|
101
|
+
export declare function listPublishedPosts(db: D1Database, query: PostListQuery): Promise<{
|
|
102
|
+
items: {
|
|
103
|
+
slug: string;
|
|
104
|
+
type: "blog" | "article" | "news" | "research" | "whitepaper" | "case-study";
|
|
105
|
+
viewCount: number;
|
|
106
|
+
likeCount: number;
|
|
107
|
+
id: string;
|
|
108
|
+
title: string;
|
|
109
|
+
excerpt: string | null;
|
|
110
|
+
content: string;
|
|
111
|
+
coverImageUrl: string | null;
|
|
112
|
+
category: string | null;
|
|
113
|
+
tags: string[];
|
|
114
|
+
ogTitle: string | null;
|
|
115
|
+
ogDescription: string | null;
|
|
116
|
+
ogImageUrl: string | null;
|
|
117
|
+
answerSummary: string | null;
|
|
118
|
+
questionHeading: string | null;
|
|
119
|
+
authorIds: string[];
|
|
120
|
+
cta: {
|
|
121
|
+
style: "link" | "gated-download";
|
|
122
|
+
buttonLabel: string;
|
|
123
|
+
url?: string | undefined;
|
|
124
|
+
heading?: string | undefined;
|
|
125
|
+
formKey?: string | undefined;
|
|
126
|
+
assetUrl?: string | undefined;
|
|
127
|
+
} | null;
|
|
128
|
+
note: string | null;
|
|
129
|
+
version: number;
|
|
130
|
+
wordCount: number | null;
|
|
131
|
+
publishedAt: string;
|
|
132
|
+
postId: string;
|
|
133
|
+
publishedBy: string | null;
|
|
134
|
+
}[];
|
|
135
|
+
total: number;
|
|
136
|
+
}>;
|
|
137
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/content/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAEf,aAAa,EACb,UAAU,EAGV,eAAe,EAChB,MAAM,uBAAuB,CAAC;AAsB/B,wBAAsB,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CA0C5F;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,UAAU,CAAC,CAuCrB;AAoGD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAchG;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAEtF;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCjD;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,YAAY,CAAC,CA6CvF;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsBvF;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBjG;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqBxE;AAED,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkD5E"}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { HttpError } from '../../lib/errors.js';
|
|
2
|
+
import { sanitizeHtml } from '../../lib/sanitize.js';
|
|
3
|
+
import { countWords } from '../../lib/word-count.js';
|
|
4
|
+
import { assertAuthorsExist, currentAuthorIds, draftRowToContentFields, fetchDraftRow, fetchPostRow, fetchPostRowBySlug, generateUniqueSlug, hasDraft, mergeContentFields, postRowToContentFields, syncPostAuthorsStatements, toPostRecord, toPostVersionRecord, } from './db.js';
|
|
5
|
+
export async function createPost(db, input) {
|
|
6
|
+
const existing = await fetchPostRowBySlug(db, input.slug);
|
|
7
|
+
if (existing) {
|
|
8
|
+
throw new HttpError('validation_error', `slug "${input.slug}" is already in use`);
|
|
9
|
+
}
|
|
10
|
+
await assertAuthorsExist(db, input.authorIds);
|
|
11
|
+
const sanitizedContent = await sanitizeHtml(input.content);
|
|
12
|
+
const wordCount = countWords(sanitizedContent);
|
|
13
|
+
const id = crypto.randomUUID();
|
|
14
|
+
await db.batch([
|
|
15
|
+
db
|
|
16
|
+
.prepare(`INSERT INTO posts
|
|
17
|
+
(id, slug, type, title, excerpt, content, cover_image_url, category, tags, status, og_title, og_description, og_image_url, answer_summary, question_heading, cta, word_count)
|
|
18
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 'draft', ?10, ?11, ?12, ?13, ?14, ?15, ?16)`)
|
|
19
|
+
.bind(id, input.slug, input.type, input.title,
|
|
20
|
+
// `|| null` not `?? null`: "" means cleared/absent on the wire (see
|
|
21
|
+
// mergeContentFields) and must never be stored as an empty string.
|
|
22
|
+
input.excerpt || null, sanitizedContent, input.coverImageUrl || null, input.category || null, JSON.stringify(input.tags), input.ogTitle || null, input.ogDescription || null, input.ogImageUrl || null, input.answerSummary || null, input.questionHeading || null, input.cta ? JSON.stringify(input.cta) : null, wordCount),
|
|
23
|
+
...syncPostAuthorsStatements(db, id, input.authorIds),
|
|
24
|
+
]);
|
|
25
|
+
return toPostRecord(await fetchPostRow(db, id), false);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Routes an edit to its correct home: directly onto `posts` if the post has
|
|
29
|
+
* never been published, or forked into `post_drafts` (upserted) if it has.
|
|
30
|
+
* Editing a published post upserts into post_drafts only; the posts row is
|
|
31
|
+
* untouched until publish.
|
|
32
|
+
*/
|
|
33
|
+
export async function updateWorkingCopy(db, postId, patch) {
|
|
34
|
+
const row = await fetchPostRow(db, postId);
|
|
35
|
+
// Slugs are immutable once published (post_drafts/post_versions carry no
|
|
36
|
+
// slug column; permalinks must not change under a live URL). Reject
|
|
37
|
+
// explicitly rather than silently dropping the field: a caller who sent a
|
|
38
|
+
// new slug and got a 200 back would reasonably believe it took.
|
|
39
|
+
if (row.current_version_id !== null && patch.slug !== undefined && patch.slug !== row.slug) {
|
|
40
|
+
throw new HttpError('validation_error', 'slug cannot be changed after a post has been published');
|
|
41
|
+
}
|
|
42
|
+
// Type is locked after publish for the same reason as slug: it selects the
|
|
43
|
+
// site section (contentPaths), so changing it moves a live URL.
|
|
44
|
+
if (row.current_version_id !== null && patch.type !== undefined && patch.type !== row.type) {
|
|
45
|
+
throw new HttpError('validation_error', 'type cannot be changed after a post has been published');
|
|
46
|
+
}
|
|
47
|
+
if (patch.authorIds) {
|
|
48
|
+
await assertAuthorsExist(db, patch.authorIds);
|
|
49
|
+
}
|
|
50
|
+
let sanitizedContent;
|
|
51
|
+
if (patch.content !== undefined) {
|
|
52
|
+
sanitizedContent = await sanitizeHtml(patch.content);
|
|
53
|
+
}
|
|
54
|
+
if (row.current_version_id === null) {
|
|
55
|
+
await updateNeverPublished(db, row, patch, sanitizedContent);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
await upsertDraft(db, postId, patch, sanitizedContent);
|
|
59
|
+
}
|
|
60
|
+
return resolveAdminPostRecord(db, postId);
|
|
61
|
+
}
|
|
62
|
+
async function updateNeverPublished(db, row, patch, sanitizedContent) {
|
|
63
|
+
if (patch.slug && patch.slug !== row.slug) {
|
|
64
|
+
const existing = await fetchPostRowBySlug(db, patch.slug);
|
|
65
|
+
if (existing) {
|
|
66
|
+
throw new HttpError('validation_error', `slug "${patch.slug}" is already in use`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const merged = mergeContentFields(postRowToContentFields(row), patch, sanitizedContent);
|
|
70
|
+
const wordCount = sanitizedContent !== undefined ? countWords(sanitizedContent) : row.word_count;
|
|
71
|
+
const statements = [
|
|
72
|
+
db
|
|
73
|
+
.prepare(`UPDATE posts SET
|
|
74
|
+
slug=?2, type=?3, title=?4, excerpt=?5, content=?6, cover_image_url=?7, category=?8, tags=?9,
|
|
75
|
+
og_title=?10, og_description=?11, og_image_url=?12, answer_summary=?13, question_heading=?14,
|
|
76
|
+
cta=?15, word_count=?16, updated_at=datetime('now')
|
|
77
|
+
WHERE id=?1`)
|
|
78
|
+
.bind(row.id, patch.slug ?? row.slug, patch.type ?? row.type, merged.title, merged.excerpt, merged.content, merged.coverImageUrl, merged.category, JSON.stringify(merged.tags), merged.ogTitle, merged.ogDescription, merged.ogImageUrl, merged.answerSummary, merged.questionHeading, merged.cta ? JSON.stringify(merged.cta) : null, wordCount),
|
|
79
|
+
];
|
|
80
|
+
if (patch.authorIds) {
|
|
81
|
+
statements.push(...syncPostAuthorsStatements(db, row.id, patch.authorIds));
|
|
82
|
+
}
|
|
83
|
+
await db.batch(statements);
|
|
84
|
+
}
|
|
85
|
+
async function upsertDraft(db, postId, patch, sanitizedContent) {
|
|
86
|
+
const existingDraft = await fetchDraftRow(db, postId);
|
|
87
|
+
const base = existingDraft
|
|
88
|
+
? draftRowToContentFields(existingDraft)
|
|
89
|
+
: postRowToContentFields(await fetchPostRow(db, postId));
|
|
90
|
+
const baseAuthorIds = existingDraft
|
|
91
|
+
? JSON.parse(existingDraft.author_ids)
|
|
92
|
+
: await currentAuthorIds(db, postId);
|
|
93
|
+
const merged = mergeContentFields(base, patch, sanitizedContent);
|
|
94
|
+
const authorIds = patch.authorIds ?? baseAuthorIds;
|
|
95
|
+
await db
|
|
96
|
+
.prepare(`INSERT INTO post_drafts
|
|
97
|
+
(post_id, title, excerpt, content, cover_image_url, category, tags, og_title, og_description, og_image_url, answer_summary, question_heading, cta, author_ids, updated_at)
|
|
98
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, datetime('now'))
|
|
99
|
+
ON CONFLICT(post_id) DO UPDATE SET
|
|
100
|
+
title=excluded.title, excerpt=excluded.excerpt, content=excluded.content,
|
|
101
|
+
cover_image_url=excluded.cover_image_url, category=excluded.category, tags=excluded.tags,
|
|
102
|
+
og_title=excluded.og_title, og_description=excluded.og_description, og_image_url=excluded.og_image_url,
|
|
103
|
+
answer_summary=excluded.answer_summary, question_heading=excluded.question_heading,
|
|
104
|
+
cta=excluded.cta, author_ids=excluded.author_ids, updated_at=datetime('now')`)
|
|
105
|
+
.bind(postId, merged.title, merged.excerpt, merged.content, merged.coverImageUrl, merged.category, JSON.stringify(merged.tags), merged.ogTitle, merged.ogDescription, merged.ogImageUrl, merged.answerSummary, merged.questionHeading, merged.cta ? JSON.stringify(merged.cta) : null, JSON.stringify(authorIds))
|
|
106
|
+
.run();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The admin-facing "what should the editor show" resolution: `posts` alone
|
|
110
|
+
* is not enough once a pending draft exists: because an edit to a published
|
|
111
|
+
* post lands in post_drafts and leaves the posts row alone, that row's
|
|
112
|
+
* content, title, and the rest stay the last-PUBLISHED values while a draft
|
|
113
|
+
* is pending, not the being-edited ones. Non-content fields (status,
|
|
114
|
+
* currentVersionId, viewCount, timestamps, ...) always come from `posts`
|
|
115
|
+
* regardless, since post_drafts carries no equivalents for those.
|
|
116
|
+
*/
|
|
117
|
+
export async function resolveAdminPostRecord(db, postId) {
|
|
118
|
+
const row = await fetchPostRow(db, postId);
|
|
119
|
+
const draft = await fetchDraftRow(db, postId);
|
|
120
|
+
if (!draft) {
|
|
121
|
+
return toPostRecord(row, false, await currentAuthorIds(db, postId));
|
|
122
|
+
}
|
|
123
|
+
const contentFields = draftRowToContentFields(draft);
|
|
124
|
+
return {
|
|
125
|
+
...toPostRecord(row, true, JSON.parse(draft.author_ids)),
|
|
126
|
+
...contentFields,
|
|
127
|
+
wordCount: countWords(contentFields.content),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export async function getPostAdmin(db, postId) {
|
|
131
|
+
return resolveAdminPostRecord(db, postId);
|
|
132
|
+
}
|
|
133
|
+
export async function listPostsAdmin(db, query) {
|
|
134
|
+
const offset = (query.page - 1) * query.pageSize;
|
|
135
|
+
const conditions = [];
|
|
136
|
+
const bindings = [];
|
|
137
|
+
if (query.status) {
|
|
138
|
+
conditions.push(`status = ?${bindings.length + 1}`);
|
|
139
|
+
bindings.push(query.status);
|
|
140
|
+
}
|
|
141
|
+
if (query.type) {
|
|
142
|
+
conditions.push(`type = ?${bindings.length + 1}`);
|
|
143
|
+
bindings.push(query.type);
|
|
144
|
+
}
|
|
145
|
+
if (query.category) {
|
|
146
|
+
conditions.push(`category = ?${bindings.length + 1}`);
|
|
147
|
+
bindings.push(query.category);
|
|
148
|
+
}
|
|
149
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
150
|
+
const { results } = await db
|
|
151
|
+
.prepare(`SELECT * FROM posts ${whereClause} ORDER BY created_at DESC LIMIT ?${bindings.length + 1} OFFSET ?${bindings.length + 2}`)
|
|
152
|
+
.bind(...bindings, query.pageSize, offset)
|
|
153
|
+
.all();
|
|
154
|
+
const { results: countResults } = await db
|
|
155
|
+
.prepare(`SELECT COUNT(*) as count FROM posts ${whereClause}`)
|
|
156
|
+
.bind(...bindings)
|
|
157
|
+
.all();
|
|
158
|
+
const items = await Promise.all(results.map(async (row) => toPostRecord(row, await hasDraft(db, row.id))));
|
|
159
|
+
return { items, total: countResults[0]?.count ?? 0 };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Content performance, all-time rather than day-windowed:
|
|
163
|
+
* view_count/like_count are already-cumulative derived columns (see
|
|
164
|
+
* recordView/toggleLike below), so there is no per-day breakdown to slice
|
|
165
|
+
* without joining post_views/post_likes by date, which nothing here needs.
|
|
166
|
+
* Ranked by view_count regardless of current status (an archived post that
|
|
167
|
+
* was once popular still deserves to show up; the Status column, not a
|
|
168
|
+
* filter, communicates that it is no longer live).
|
|
169
|
+
*/
|
|
170
|
+
export async function getContentStats(db, limit = 10) {
|
|
171
|
+
const totals = await db
|
|
172
|
+
.prepare(`SELECT
|
|
173
|
+
COALESCE(SUM(view_count), 0) as totalViews,
|
|
174
|
+
COALESCE(SUM(like_count), 0) as totalLikes,
|
|
175
|
+
SUM(CASE WHEN status = 'published' THEN 1 ELSE 0 END) as publishedCount
|
|
176
|
+
FROM posts`)
|
|
177
|
+
.first();
|
|
178
|
+
const { results } = await db
|
|
179
|
+
.prepare(`SELECT id, slug, title, type, status, view_count, like_count, published_at
|
|
180
|
+
FROM posts
|
|
181
|
+
ORDER BY view_count DESC, like_count DESC, created_at DESC
|
|
182
|
+
LIMIT ?1`)
|
|
183
|
+
.bind(limit)
|
|
184
|
+
.all();
|
|
185
|
+
return {
|
|
186
|
+
totalViews: totals?.totalViews ?? 0,
|
|
187
|
+
totalLikes: totals?.totalLikes ?? 0,
|
|
188
|
+
publishedCount: totals?.publishedCount ?? 0,
|
|
189
|
+
topPosts: results.map((row) => ({
|
|
190
|
+
id: row.id,
|
|
191
|
+
slug: row.slug,
|
|
192
|
+
title: row.title,
|
|
193
|
+
type: row.type,
|
|
194
|
+
status: row.status,
|
|
195
|
+
viewCount: row.view_count,
|
|
196
|
+
likeCount: row.like_count,
|
|
197
|
+
publishedAt: row.published_at,
|
|
198
|
+
})),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export async function hardDeletePost(db, postId) {
|
|
202
|
+
await fetchPostRow(db, postId); // 404s if missing
|
|
203
|
+
await db.batch([
|
|
204
|
+
db.prepare('DELETE FROM post_drafts WHERE post_id = ?1').bind(postId),
|
|
205
|
+
db.prepare('DELETE FROM post_versions WHERE post_id = ?1').bind(postId),
|
|
206
|
+
db.prepare('DELETE FROM post_authors WHERE post_id = ?1').bind(postId),
|
|
207
|
+
db.prepare('DELETE FROM post_views WHERE post_id = ?1').bind(postId),
|
|
208
|
+
db.prepare('DELETE FROM post_likes WHERE post_id = ?1').bind(postId),
|
|
209
|
+
db.prepare('DELETE FROM posts WHERE id = ?1').bind(postId),
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Clones the current working copy into a brand new, never-published draft.
|
|
214
|
+
* Publish history is deliberately not copied: the duplicate is a new piece of
|
|
215
|
+
* content that happens to start from this one's text, not a fork of its past.
|
|
216
|
+
*/
|
|
217
|
+
export async function duplicatePost(db, postId) {
|
|
218
|
+
const row = await fetchPostRow(db, postId);
|
|
219
|
+
const authorIds = await currentAuthorIds(db, postId);
|
|
220
|
+
const newSlug = await generateUniqueSlug(db, `${row.slug}-copy`);
|
|
221
|
+
return createPost(db, {
|
|
222
|
+
slug: newSlug,
|
|
223
|
+
type: row.type,
|
|
224
|
+
title: `${row.title} (copy)`,
|
|
225
|
+
excerpt: row.excerpt ?? undefined,
|
|
226
|
+
content: row.content, // already sanitized; createPost re-sanitizes, which is idempotent
|
|
227
|
+
coverImageUrl: row.cover_image_url ?? undefined,
|
|
228
|
+
category: row.category ?? undefined,
|
|
229
|
+
tags: JSON.parse(row.tags),
|
|
230
|
+
ogTitle: row.og_title ?? undefined,
|
|
231
|
+
ogDescription: row.og_description ?? undefined,
|
|
232
|
+
ogImageUrl: row.og_image_url ?? undefined,
|
|
233
|
+
answerSummary: row.answer_summary ?? undefined,
|
|
234
|
+
questionHeading: row.question_heading ?? undefined,
|
|
235
|
+
cta: row.cta ? JSON.parse(row.cta) : undefined,
|
|
236
|
+
authorIds,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Counters are DERIVED from the tracking tables inside the same batch, never
|
|
241
|
+
* incremented/decremented blind. A blind `like_count - 1` drifts under
|
|
242
|
+
* concurrent requests (two unlikes for the same session both pass the
|
|
243
|
+
* existence check, the second DELETE removes zero rows, both decrements run,
|
|
244
|
+
* and the counter can go negative). COUNT(*) in the same atomic batch is
|
|
245
|
+
* idempotent and cannot drift.
|
|
246
|
+
*/
|
|
247
|
+
export async function recordView(db, postId, sessionId) {
|
|
248
|
+
await db.batch([
|
|
249
|
+
// The scalar subquery captures whatever version is current AT INSERT
|
|
250
|
+
// TIME, a permanent snapshot: post_versions rows are
|
|
251
|
+
// immutable and insert-only, so this stamp never needs revisiting once
|
|
252
|
+
// written. INSERT OR IGNORE means a session's first view of a post wins
|
|
253
|
+
// the stamp; a later re-view under a newer version does not overwrite it.
|
|
254
|
+
db
|
|
255
|
+
.prepare(`INSERT OR IGNORE INTO post_views (post_id, session_id, version_number)
|
|
256
|
+
VALUES (?1, ?2, (SELECT current_version_number FROM posts WHERE id = ?1))`)
|
|
257
|
+
.bind(postId, sessionId),
|
|
258
|
+
db
|
|
259
|
+
.prepare('UPDATE posts SET view_count = (SELECT COUNT(*) FROM post_views WHERE post_id = ?1) WHERE id = ?1')
|
|
260
|
+
.bind(postId),
|
|
261
|
+
]);
|
|
262
|
+
}
|
|
263
|
+
export async function toggleLike(db, postId, sessionId) {
|
|
264
|
+
const existing = await db
|
|
265
|
+
.prepare('SELECT 1 FROM post_likes WHERE post_id = ?1 AND session_id = ?2')
|
|
266
|
+
.bind(postId, sessionId)
|
|
267
|
+
.first();
|
|
268
|
+
const mutation = existing
|
|
269
|
+
? db
|
|
270
|
+
.prepare('DELETE FROM post_likes WHERE post_id = ?1 AND session_id = ?2')
|
|
271
|
+
.bind(postId, sessionId)
|
|
272
|
+
: db
|
|
273
|
+
// Same at-insert-time version stamp as recordView; see its comment.
|
|
274
|
+
.prepare(`INSERT OR IGNORE INTO post_likes (post_id, session_id, version_number)
|
|
275
|
+
VALUES (?1, ?2, (SELECT current_version_number FROM posts WHERE id = ?1))`)
|
|
276
|
+
.bind(postId, sessionId);
|
|
277
|
+
await db.batch([
|
|
278
|
+
mutation,
|
|
279
|
+
db
|
|
280
|
+
.prepare('UPDATE posts SET like_count = (SELECT COUNT(*) FROM post_likes WHERE post_id = ?1) WHERE id = ?1')
|
|
281
|
+
.bind(postId),
|
|
282
|
+
]);
|
|
283
|
+
const row = await fetchPostRow(db, postId);
|
|
284
|
+
return { liked: !existing, likeCount: row.like_count };
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Public reads serve from the current version snapshot, not the posts mirror. `pv.*` already carries `post_id` (the FK back
|
|
288
|
+
* to posts); deliberately not also aliasing `posts.id`, which would collide
|
|
289
|
+
* with that same column name in the result row.
|
|
290
|
+
*/
|
|
291
|
+
export async function getPublishedPostBySlug(db, slug) {
|
|
292
|
+
const row = await db
|
|
293
|
+
.prepare(`SELECT posts.slug, posts.type, posts.view_count, posts.like_count, pv.*
|
|
294
|
+
FROM posts JOIN post_versions pv ON pv.id = posts.current_version_id
|
|
295
|
+
WHERE posts.slug = ?1 AND posts.status = 'published'`)
|
|
296
|
+
.bind(slug)
|
|
297
|
+
.first();
|
|
298
|
+
if (!row)
|
|
299
|
+
throw new HttpError('not_found', 'post not found');
|
|
300
|
+
return {
|
|
301
|
+
...toPostVersionRecord(row),
|
|
302
|
+
slug: row.slug,
|
|
303
|
+
type: row.type,
|
|
304
|
+
viewCount: row.view_count,
|
|
305
|
+
likeCount: row.like_count,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
export async function listPublishedPosts(db, query) {
|
|
309
|
+
const offset = (query.page - 1) * query.pageSize;
|
|
310
|
+
const conditions = ["posts.status = 'published'"];
|
|
311
|
+
const bindings = [];
|
|
312
|
+
if (query.category) {
|
|
313
|
+
conditions.push(`pv.category = ?${bindings.length + 1}`);
|
|
314
|
+
bindings.push(query.category);
|
|
315
|
+
}
|
|
316
|
+
if (query.tag) {
|
|
317
|
+
conditions.push(`pv.tags LIKE ?${bindings.length + 1}`);
|
|
318
|
+
bindings.push(`%"${query.tag}"%`);
|
|
319
|
+
}
|
|
320
|
+
if (query.type) {
|
|
321
|
+
conditions.push(`posts.type = ?${bindings.length + 1}`);
|
|
322
|
+
bindings.push(query.type);
|
|
323
|
+
}
|
|
324
|
+
const where = conditions.join(' AND ');
|
|
325
|
+
const { results } = await db
|
|
326
|
+
.prepare(`SELECT posts.slug, posts.type, posts.view_count, posts.like_count, pv.*
|
|
327
|
+
FROM posts JOIN post_versions pv ON pv.id = posts.current_version_id
|
|
328
|
+
WHERE ${where}
|
|
329
|
+
ORDER BY pv.published_at DESC
|
|
330
|
+
LIMIT ?${bindings.length + 1} OFFSET ?${bindings.length + 2}`)
|
|
331
|
+
.bind(...bindings, query.pageSize, offset)
|
|
332
|
+
.all();
|
|
333
|
+
const { results: countResults } = await db
|
|
334
|
+
.prepare(`SELECT COUNT(*) as count FROM posts JOIN post_versions pv ON pv.id = posts.current_version_id WHERE ${where}`)
|
|
335
|
+
.bind(...bindings)
|
|
336
|
+
.all();
|
|
337
|
+
return {
|
|
338
|
+
items: results.map((row) => ({
|
|
339
|
+
...toPostVersionRecord(row),
|
|
340
|
+
slug: row.slug,
|
|
341
|
+
type: row.type,
|
|
342
|
+
viewCount: row.view_count,
|
|
343
|
+
likeCount: row.like_count,
|
|
344
|
+
})),
|
|
345
|
+
total: countResults[0]?.count ?? 0,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/modules/content/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FormRecord, FormSettings, FormType, SubmissionRecord, SubmissionStatus } from '../../shared/index.js';
|
|
2
|
+
export interface FormRow {
|
|
3
|
+
id: string;
|
|
4
|
+
form_key: string;
|
|
5
|
+
name: string;
|
|
6
|
+
type: FormType;
|
|
7
|
+
settings: string;
|
|
8
|
+
created_at: string;
|
|
9
|
+
updated_at: string | null;
|
|
10
|
+
archived_at: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** A form row plus the submission aggregates the list/get queries LEFT JOIN in. */
|
|
13
|
+
export interface FormListRow extends FormRow {
|
|
14
|
+
submission_count: number;
|
|
15
|
+
last_submission_at: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface SubmissionRow {
|
|
18
|
+
id: string;
|
|
19
|
+
form_id: string;
|
|
20
|
+
submitted_at: string;
|
|
21
|
+
payload: string;
|
|
22
|
+
email: string | null;
|
|
23
|
+
name: string | null;
|
|
24
|
+
status: SubmissionStatus;
|
|
25
|
+
notes: string | null;
|
|
26
|
+
ip_hash: string | null;
|
|
27
|
+
user_agent: string | null;
|
|
28
|
+
source_page: string | null;
|
|
29
|
+
referrer: string | null;
|
|
30
|
+
}
|
|
31
|
+
/** A submission row joined with its form's name, for the inbox display. */
|
|
32
|
+
export interface SubmissionRowWithForm extends SubmissionRow {
|
|
33
|
+
form_name: string | null;
|
|
34
|
+
}
|
|
35
|
+
export declare function parseFormSettings(settingsJson: string): FormSettings;
|
|
36
|
+
export declare function toFormRecord(row: FormListRow): FormRecord;
|
|
37
|
+
export declare function toSubmissionRecord(row: SubmissionRowWithForm): SubmissionRecord;
|
|
38
|
+
export declare function fetchFormByKey(db: D1Database, formKey: string): Promise<FormRow>;
|
|
39
|
+
export declare function fetchFormById(db: D1Database, id: string): Promise<FormRow>;
|
|
40
|
+
/** Single submission joined with its form name; 404s if the id is unknown. */
|
|
41
|
+
export declare function fetchSubmissionRow(db: D1Database, id: string): Promise<SubmissionRowWithForm>;
|
|
42
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/modules/forms/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAI/B,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,2EAA2E;AAC3E,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CAEpE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,CAazD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,qBAAqB,GAAG,gBAAgB,CAc/E;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOtF;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIhF;AAED,8EAA8E;AAC9E,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,qBAAqB,CAAC,CAWhC"}
|