source: Klonkt/src/routes/artists.js@ 8afbdd6

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

feat: viewer role + artists directory + Klonkt Hub Beta rebrand

Viewer role (replaces the separate view-mode/readonly toggle):

  • 'kijker' is now a real role (VALID_ROLES) selectable in Admin. May view EVERYTHING including Admin, but cannot modify anything.
  • isViewer(user) (= role kijker or legacy readonly flag) is the source; requireGod/requireSiteManager(BySlug) let a viewer through (viewing), the global guard 403s every write. Existing readonly accounts are migrated on each role change (readonly=0).
  • Write leaks via GET patched: /prutter/new (INSERT) blocks for viewers, /prutter/:id skips markAsRead (UPDATE); WS upgrade rejects viewers (the HTTP guard doesn't cover WebSockets).
  • Clean "Viewer mode" page (viewer-blocked) instead of raw 403 text; styled sticky banner; account page shows read-only UI instead of an upload button that silently 403s. canMutate hides write buttons.

Scalability (>50 artists):

  • Hub home shows max 24 (most active first) + "All N artists ->".
  • New searchable, paginated /artiesten directory (hub only).
  • 'user' + 'artiesten' reserved as slugs.

Rebrand PrutFolio v1 -> Klonkt Hub Beta (footer, PWA manifest, account/
admin texts, default page title, startup, README; internal package +
PWA id 'prutfolio' remain for stability).

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

  • Property mode set to 100644
File size: 2.2 KB
Line 
1/**
2 * Artiesten-directory — alleen in hub-modus.
3 *
4 * GET /artiesten?q=&page= -> doorzoekbare, gepagineerde lijst van ALLE
5 * PrutFolio's. De hub-home toont maar een beperkte selectie; deze pagina
6 * schaalt naar honderden/duizenden artiesten via zoeken + paginering.
7 *
8 * In solo-modus bestaat er maar één site -> next() (valt door naar postsRoutes,
9 * die 'artiesten' als onbekende slug afhandelt).
10 */
11
12import express from 'express';
13import db from '../config/database.js';
14import { renderPage } from '../middleware/render.js';
15import { getTenancy } from '../services/SettingsService.js';
16
17const router = express.Router();
18
19const PER_PAGE = 24;
20
21router.get('/', (req, res, next) => {
22 if (getTenancy() !== 'hub') return next();
23
24 const q = (req.query.q || '').toString().trim().slice(0, 80);
25 let page = parseInt(req.query.page, 10);
26 if (!Number.isFinite(page) || page < 1) page = 1;
27
28 // Zoekterm tegen titel/slug/tagline (case-insensitive via LIKE; SQLite LIKE is
29 // standaard ongevoelig voor ASCII-hoofdletters). De ESCAPE '\' maakt %, _ en \
30 // in de zoekterm letterlijk (anders zouden ze als wildcards werken).
31 const where = q
32 ? "WHERE (s.title LIKE @like ESCAPE '\\' OR s.slug LIKE @like ESCAPE '\\' OR s.tagline LIKE @like ESCAPE '\\')"
33 : '';
34 const like = '%' + q.replace(/[\\%_]/g, (m) => '\\' + m) + '%';
35 const params = q ? { like } : {};
36
37 const total = db.prepare(`SELECT COUNT(*) AS c FROM sites s ${where}`)
38 .get(params).c;
39 const pages = Math.max(1, Math.ceil(total / PER_PAGE));
40 if (page > pages) page = pages;
41 const offset = (page - 1) * PER_PAGE;
42
43 const artists = db.prepare(`
44 SELECT s.slug, s.title, s.tagline, s.profile_photo, s.accent,
45 u.avatar_url AS owner_avatar,
46 (SELECT COUNT(*) FROM posts WHERE site_id = s.id AND status = 'published') AS post_count
47 FROM sites s
48 LEFT JOIN users u ON u.id = s.owner_id
49 ${where}
50 ORDER BY s.title COLLATE NOCASE ASC
51 LIMIT @limit OFFSET @offset
52 `).all({ ...params, limit: PER_PAGE, offset });
53
54 renderPage(req, res, 'pages/artists-directory', {
55 pageTitle: 'Artiesten',
56 bodyClass: 'on-hub',
57 q,
58 artists,
59 total,
60 page,
61 pages,
62 });
63});
64
65export default router;
Note: See TracBrowser for help on using the repository browser.