source: Klonkt/src/routes/circle.js@ 8453812

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

Circles: local read page /cirkel/:id (stay on your site) + source in new tab

Feed cards now link locally (htmx) to /cirkel/<id> instead of externally. The
read page shows title, 'via <source>', date, cached text (plain, white-space:pre-wrap)
+ cover, with 'Read more at <source>' -> target=_blank.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

  • Property mode set to 100644
File size: 3.2 KB
Line 
1/**
2 * Cirkel-feed + lokale lees-pagina.
3 * GET /cirkel -> overzicht (zelfde timeline/grid-view als de home)
4 * GET /cirkel/:id -> losse remote-post in eigen chrome (blijf op je site)
5 * Alleen actief als tenancy === 'circle' (anders next() -> postsRoutes/404).
6 * Zie docs/cirkels-v1-spec.md §5c.
7 */
8
9import express from 'express';
10import { renderPage } from '../middleware/render.js';
11import db from '../config/database.js';
12import { getTenancy } from '../services/SettingsService.js';
13
14const router = express.Router();
15
16function safeUrl(u) {
17 return typeof u === 'string' && /^https?:\/\//i.test(u) ? u : null;
18}
19function safeJson(s) {
20 try { return s ? JSON.parse(s) : []; } catch { return []; }
21}
22function mediaImage(media_json) {
23 const media = safeJson(media_json).map((m) => ({ ...m, url: safeUrl(m.url) })).filter((m) => m.url);
24 return media.find((m) => m.type === 'image') || null;
25}
26
27// ── Overzicht ────────────────────────────────────────────────
28router.get('/cirkel', (req, res, next) => {
29 if (getTenancy() !== 'circle') return next();
30
31 const rows = db.prepare(`
32 SELECT p.id, p.published, p.title, p.summary, p.media_json,
33 a.name AS actor_name
34 FROM remote_posts p
35 JOIN remote_actors a ON a.id = p.actor_id
36 ORDER BY COALESCE(p.published, p.fetched_at) DESC
37 LIMIT 100
38 `).all();
39
40 const posts = rows.map((r) => {
41 const image = mediaImage(r.media_json);
42 return {
43 id: r.id,
44 // Lokale lees-pagina -> de kaart blijft op de eigen site (post-card linkt
45 // lokaal + htmx, GEEN external_url).
46 slug: 'cirkel/' + encodeURIComponent(r.id),
47 title: r.title || '(zonder titel)',
48 excerpt: r.summary || '',
49 cover_image_url: image ? image.url : null,
50 published_at: r.published,
51 created_at: r.published,
52 type: 'post',
53 tags: '',
54 pinned: 0,
55 status: 'published',
56 source_name: r.actor_name || 'Onbekend',
57 };
58 });
59
60 renderPage(req, res, 'pages/circle-feed', { pageTitle: 'Cirkel', bodyClass: 'on-cirkel', posts });
61});
62
63// ── Losse remote-post (lokaal lezen) ─────────────────────────
64router.get('/cirkel/:id', (req, res, next) => {
65 if (getTenancy() !== 'circle') return next();
66
67 const row = db.prepare(`
68 SELECT p.id, p.published, p.title, p.summary, p.url, p.media_json,
69 a.name AS actor_name, a.url AS actor_url, a.avatar AS actor_avatar
70 FROM remote_posts p
71 JOIN remote_actors a ON a.id = p.actor_id
72 WHERE p.id = ?
73 `).get(req.params.id);
74
75 if (!row) return next();
76
77 const image = mediaImage(row.media_json);
78 const post = {
79 title: row.title || '(zonder titel)',
80 body: row.summary || '', // platte tekst (gesanitized bij ingest)
81 published: row.published,
82 image: image ? image.url : null,
83 sourceName: row.actor_name || 'Onbekend',
84 sourceUrl: safeUrl(row.actor_url),
85 sourceAvatar: safeUrl(row.actor_avatar),
86 originalUrl: safeUrl(row.url),
87 };
88
89 renderPage(req, res, 'pages/circle-post', {
90 pageTitle: post.title,
91 bodyClass: 'on-cirkel-post',
92 post,
93 });
94});
95
96export default router;
Note: See TracBrowser for help on using the repository browser.