Changeset b9dc94c in Klonkt
- Timestamp:
- 06/19/2026 04:28:02 AM (3 months ago)
- Branches:
- main
- Children:
- 8d32dcf
- Parents:
- 6be57b4
- Location:
- src
- Files:
-
- 2 added
- 4 edited
-
config/database.js (modified) (1 diff)
-
routes/posts.js (modified) (7 diffs)
-
server.js (modified) (2 diffs)
-
services/Scheduler.js (added)
-
views/pages/fan-gate.ejs (added)
-
views/pages/post-edit.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
r6be57b4 rb9dc94c 82 82 // Per-post noindex + type 83 83 ensureColumn('posts', 'noindex', 'INTEGER DEFAULT 0'); 84 ensureColumn('posts', 'publish_at', 'DATETIME'); // release-planning (premium #3): geplande go-live 85 ensureColumn('posts', 'fan_only', 'INTEGER DEFAULT 0'); // fan-only preview (premium #3) 84 86 ensureColumn('posts', 'type', "TEXT DEFAULT 'post'"); // post | foto | video | audio 85 87 -
src/routes/posts.js
r6be57b4 rb9dc94c 154 154 155 155 const { title, slug, content, excerpt, status, pinned, cover_image_url, tags, noindex, type } = req.body; 156 const fanOnly = req.body.fan_only ? 1 : 0; 156 157 157 158 // Content arrives as user-authored HTML from the WYSIWYG editor — sanitize … … 177 178 const postId = uuid(); 178 179 const now = new Date().toISOString(); 179 const finalStatus = status || 'draft'; 180 const publishedAt = finalStatus === 'published' ? now : null; 180 let finalStatus = status || 'draft'; 181 let publishedAt = finalStatus === 'published' ? now : null; 182 // Release-planning: gepubliceerd + een toekomstige publish_at -> 'scheduled' 183 // (de Scheduler zet 'm live op het moment zelf). Verleden/leeg -> meteen live. 184 let publishAt = null; 185 const pa = Date.parse(req.body.publish_at || ''); 186 if (finalStatus === 'published' && Number.isFinite(pa) && pa > Date.now()) { 187 finalStatus = 'scheduled'; 188 publishAt = new Date(pa).toISOString(); 189 publishedAt = null; 190 } 181 191 182 192 db.prepare(` 183 193 INSERT INTO posts ( 184 194 id, site_id, slug, author_id, title, content, excerpt, 185 status, cover_image_url, pinned, tags, type, noindex, 195 status, cover_image_url, pinned, tags, type, noindex, fan_only, publish_at, 186 196 created_at, updated_at, published_at 187 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )197 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 188 198 `).run( 189 199 postId, site.id, finalSlug, req.session.user.id, … … 191 201 finalStatus, cover_image_url || null, parsePinnedRank(pinned), 192 202 JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)), 193 finalType, noindex ? 1 : 0, 203 finalType, noindex ? 1 : 0, fanOnly, publishAt, 194 204 now, now, publishedAt 195 205 ); … … 255 265 256 266 const { title, content, excerpt, status, pinned, cover_image_url, tags, noindex, type } = req.body; 267 const fanOnly = req.body.fan_only ? 1 : 0; 257 268 const newSlug = req.body.slug; 258 269 const action = req.body.action || 'save'; … … 281 292 } 282 293 294 // Release-planning: gepubliceerd + toekomstige publish_at -> 'scheduled'. 295 let publishAt = null; 296 const pa = Date.parse(req.body.publish_at || ''); 297 if (finalStatus === 'published' && Number.isFinite(pa) && pa > Date.now()) { 298 finalStatus = 'scheduled'; 299 publishAt = new Date(pa).toISOString(); 300 publishedAt = null; 301 } 302 283 303 db.prepare(` 284 304 UPDATE posts SET 285 305 title = ?, content = ?, excerpt = ?, status = ?, 286 306 cover_image_url = ?, pinned = ?, tags = ?, 287 type = ?, noindex = ?, 307 type = ?, noindex = ?, fan_only = ?, publish_at = ?, 288 308 slug = ?, published_at = ?, updated_at = ? 289 309 WHERE id = ? … … 292 312 cover_image_url || null, parsePinnedRank(pinned), 293 313 JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)), 294 finalType, noindex ? 1 : 0, 314 finalType, noindex ? 1 : 0, fanOnly, publishAt, 295 315 finalSlug, publishedAt, now, post.id 296 316 ); … … 393 413 const canEdit = req.session?.user && PermissionsService.canEditPost(req.session.user, post, site); 394 414 if (!canEdit) return res.status(403).send('Not published'); 415 } 416 417 // Fan-only preview (premium #3): volledige inhoud alleen voor ingelogde fans. 418 // Anonieme bezoekers krijgen een nette login-gate i.p.v. de inhoud (de titel/ 419 // teaser mag elders wel als lokkertje verschijnen). 420 if (post.fan_only && !(req.session && req.session.user)) { 421 return renderPage(req, res, 'pages/fan-gate', { 422 pageTitle: post.title || 'Alleen voor fans', 423 bodyClass: 'on-special', 424 fgTitle: post.title || '', 425 fgNext: (res.locals.siteUrlBase || '') + '/' + post.slug, 426 }); 395 427 } 396 428 -
src/server.js
r6be57b4 rb9dc94c 16 16 import http from 'http'; 17 17 import db, { initializeDatabase } from './config/database.js'; 18 import { startScheduler } from './services/Scheduler.js'; 18 19 import { SqliteSessionStore } from './services/SqliteSessionStore.js'; 19 20 import { ensurePrimarySite } from './services/ensurePrimarySite.js'; … … 140 141 // → crash-loop op de allereerste boot). 141 142 initializeDatabase(); 143 startScheduler(); // release-planning: zet geplande posts live zodra publish_at bereikt is 142 144 143 145 // Vangnet: garandeer dat er altijd een primaire site is (solo/hub/circle). -
src/views/pages/post-edit.ejs
r6be57b4 rb9dc94c 193 193 <span>🚫 noindex (verberg voor zoekmachines)</span> 194 194 </label> 195 <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %> 196 <label class="pe-checkbox"> 197 <input type="checkbox" name="fan_only" value="1" <%= post.fan_only ? 'checked' : '' %>> 198 <span>🔒 Alleen voor ingelogde fans</span> 199 </label> 200 <div style="margin-top:8px"> 201 <label style="display:block;font-size:12.5px;opacity:.8;margin-bottom:4px">📅 Publiceren op (laat leeg = direct)</label> 202 <input type="datetime-local" name="publish_at" value="<%= post.publish_at ? String(post.publish_at).replace(' ','T').slice(0,16) : '' %>" style="padding:8px 10px;border-radius:8px;border:1px solid var(--rule,rgba(128,128,128,.4));background:transparent;color:inherit"> 203 <% if (post.status === 'scheduled' && post.publish_at) { %><div style="font-size:12px;opacity:.7;margin-top:4px">⏳ Ingepland voor <%= post.publish_at %></div><% } %> 204 </div> 205 <% } %> 195 206 </div> 196 207 </section>
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)