source: Klonkt/src/routes/linkbio.js@ eb852c5

main
Last change on this file since eb852c5 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: 2.3 KB
Line 
1/**
2 * Link-in-bio + click stats (premium feature #6).
3 *
4 * GET /links -> Linktree-style page with the site's profile_links
5 * GET /links/go/:i -> counts the click (per url) and redirects to the external URL
6 *
7 * Reuses the existing sites.profile_links (JSON [{platform,url}]) + the
8 * PLATFORMS icons/labels. Clicks are stored in link_clicks (see /admin/stats).
9 * Open-redirect safe: /links/go/:i ONLY redirects to a url present in the
10 * site's own profile_links. Hub: via /user/:slug/links.
11 */
12
13import express from 'express';
14import db from '../config/database.js';
15import { renderPage } from '../middleware/render.js';
16import { premiumUnlocked } from '../services/PatreonService.js';
17import { PLATFORMS } from '../services/PlatformIcons.js';
18
19const router = express.Router();
20
21function parseLinks(site) {
22 if (!site || !site.profile_links) return [];
23 try { return JSON.parse(site.profile_links) || []; } catch { return []; }
24}
25
26router.get('/links', (req, res, next) => {
27 if (!premiumUnlocked()) return next();
28 const site = res.locals.site;
29 if (!site) return next();
30 const links = parseLinks(site).map((l, i) => {
31 const meta = PLATFORMS[l.platform] || {};
32 return { i, url: l.url, platform: l.platform, label: meta.label || l.platform, svg: meta.svg || '', brand: meta.brand || '' };
33 });
34 renderPage(req, res, 'pages/linkbio', {
35 pageTitle: (site.title || '') + ' — links',
36 bodyClass: 'on-linkbio',
37 lbLinks: links,
38 });
39});
40
41router.get('/links/go/:i', (req, res, next) => {
42 if (!premiumUnlocked()) return next();
43 const site = res.locals.site;
44 if (!site) return next();
45 const links = parseLinks(site);
46 const idx = parseInt(req.params.i, 10);
47 const link = (Number.isInteger(idx) && idx >= 0) ? links[idx] : null;
48 if (!link || !link.url) return next();
49 const url = String(link.url);
50 // Only external http(s) or mailto links (no open redirect / javascript:).
51 if (!/^https?:\/\//i.test(url) && !/^mailto:/i.test(url)) return res.status(400).send('Bad link');
52 try {
53 db.prepare(
54 `INSERT INTO link_clicks (site_id, url, clicks, updated_at) VALUES (?, ?, 1, CURRENT_TIMESTAMP)
55 ON CONFLICT(site_id, url) DO UPDATE SET clicks = clicks + 1, updated_at = CURRENT_TIMESTAMP`
56 ).run(site.id, url);
57 } catch { /* counting must never break the redirect */ }
58 res.redirect(302, url);
59});
60
61export default router;
Note: See TracBrowser for help on using the repository browser.