source: Klonkt/src/routes/circle.js@ 82043c9

main
Last change on this file since 82043c9 was 834bcc3, checked in by Robin Genis <roboburr@…>, 3 months ago

i18n: translate Dutch code comments to English across src/

Comments in routes/services/views/config/middleware/assets translated to
English for the public repo. A few dev-facing throw/console message strings
were Englished too. No user-facing UI strings or i18n dictionary values changed
(src/services/i18n.js untouched). Logic unchanged.

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

  • Property mode set to 100644
File size: 3.9 KB
Line 
1/**
2 * Circle feed + local reading page.
3 * GET /cirkel -> overview (same timeline/grid view as the home)
4 * GET /cirkel/:id -> individual remote post in own chrome (stay on your site)
5 * Only active when tenancy === 'circle' (otherwise next() -> postsRoutes/404).
6 * See 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// ── Overview ─────────────────────────────────────────────────
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, p.tags,
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 // Local reading page -> the card stays on the own site (post-card links
45 // locally + htmx, NO 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: r.tags || '',
54 pinned: 0,
55 status: 'published',
56 source_name: r.actor_name || 'Onbekend',
57 };
58 });
59
60 // Sites in the circle — active links only (outdated/error ones are excluded, along
61 // with their posts). Used for the graphic header with avatars.
62 const sites = db.prepare(`
63 SELECT a.name, a.url, a.avatar
64 FROM remote_actors a
65 JOIN circle_links l ON l.remote_actor_id = a.id
66 WHERE l.status = 'active'
67 ORDER BY a.name
68 `).all()
69 .map((s) => ({
70 name: s.name || 'Onbekend',
71 url: safeUrl(s.url),
72 avatar: safeUrl(s.avatar),
73 }));
74
75 renderPage(req, res, 'pages/circle-feed', { pageTitle: 'Cirkel', bodyClass: 'on-cirkel', posts, sites });
76});
77
78// ── Individual remote post (local reading) ────────────────────
79router.get('/cirkel/:id', (req, res, next) => {
80 if (getTenancy() !== 'circle') return next();
81
82 const row = db.prepare(`
83 SELECT p.id, p.published, p.title, p.summary, p.url, p.media_json, p.tags,
84 a.name AS actor_name, a.url AS actor_url, a.avatar AS actor_avatar
85 FROM remote_posts p
86 JOIN remote_actors a ON a.id = p.actor_id
87 WHERE p.id = ?
88 `).get(req.params.id);
89
90 if (!row) return next();
91
92 const image = mediaImage(row.media_json);
93 const post = {
94 title: row.title || '(zonder titel)',
95 body: row.summary || '', // platte tekst (gesanitized bij ingest)
96 published: row.published,
97 image: image ? image.url : null,
98 sourceName: row.actor_name || 'Onbekend',
99 sourceUrl: safeUrl(row.actor_url),
100 sourceAvatar: safeUrl(row.actor_avatar),
101 originalUrl: safeUrl(row.url),
102 tags: (row.tags || '').split(',').map((t) => t.trim()).filter(Boolean),
103 sourceTagBase: safeUrl(row.actor_url) ? safeUrl(row.actor_url).replace(/\/+$/, '') : null,
104 };
105
106 renderPage(req, res, 'pages/circle-post', {
107 pageTitle: post.title,
108 bodyClass: 'on-cirkel-post',
109 post,
110 });
111});
112
113export default router;
Note: See TracBrowser for help on using the repository browser.