source: Klonkt/src/services/Scheduler.js@ 71e8fa5

main
Last change on this file since 71e8fa5 was b9dc94c, checked in by roboburr <roboburr@…>, 3 months ago

Release scheduling + fan-only previews (premium feature #3)

  • DB: posts.publish_at (scheduled go-live) + posts.fan_only.
  • Release scheduling: published + future publish_at -> status 'scheduled' (excluded from public status='published' queries). Scheduler.js publishes them when the time is reached (setInterval 60s + on boot) + adds them to posts_fts. No public queries changed -> low risk.
  • Fan-only: posts.fan_only; the single-post view shows anonymous visitors a clean login gate (pages/fan-gate.ejs, link /auth/login?next=) instead of the content; logged-in fans see everything. Listings show the teaser.
  • create/save: read publish_at + fan_only; FTS excludes 'scheduled'.
  • editor (post-edit): premium-gated fields "Fans only" + "Publish on" (datetime-local) + "scheduled for" notice.
  • server.js: startScheduler() after initializeDatabase.

node --check passed.

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 1.5 KB
Line 
1/**
2 * Scheduler — release-planning (premium #3).
3 *
4 * Geplande posts hebben status 'scheduled' + publish_at (toekomst). Een lichte
5 * timer zet ze op 'published' zodra publish_at bereikt is. Zo hoeven de publieke
6 * queries (status='published') NIET aangepast te worden — een geplande post is
7 * gewoon nog niet 'published' en dus nergens publiek zichtbaar tot het moment.
8 */
9
10import db from '../config/database.js';
11import HtmlSanitizerService from './HtmlSanitizerService.js';
12
13export function flipScheduledPosts() {
14 try {
15 const due = db.prepare(`
16 SELECT p.id, p.title, p.content, u.username
17 FROM posts p JOIN users u ON u.id = p.author_id
18 WHERE p.status = 'scheduled' AND p.publish_at IS NOT NULL AND p.publish_at <= CURRENT_TIMESTAMP
19 `).all();
20 if (!due.length) return 0;
21 const upd = db.prepare(
22 "UPDATE posts SET status = 'published', published_at = COALESCE(published_at, publish_at, CURRENT_TIMESTAMP) WHERE id = ?"
23 );
24 const fts = db.prepare('INSERT INTO posts_fts(content, title, author, post_id) VALUES (?, ?, ?, ?)');
25 for (const p of due) {
26 upd.run(p.id);
27 try { fts.run(HtmlSanitizerService.toPlainText(p.content || ''), p.title || '', p.username || '', p.id); } catch { /* FTS niet-fataal */ }
28 }
29 return due.length;
30 } catch { return 0; }
31}
32
33let _timer = null;
34export function startScheduler() {
35 flipScheduledPosts(); // direct bij boot
36 if (_timer) return;
37 _timer = setInterval(flipScheduledPosts, 60 * 1000); // elke minuut
38 if (_timer.unref) _timer.unref();
39}
Note: See TracBrowser for help on using the repository browser.