Changeset 8cb1dc7 in Klonkt
- Timestamp:
- 06/14/2026 04:17:14 PM (3 months ago)
- Branches:
- main
- Children:
- 640b39c
- Parents:
- ccbd6a6
- Location:
- src
- Files:
-
- 1 added
- 8 edited
-
middleware/auth.js (modified) (2 diffs)
-
middleware/render.js (modified) (2 diffs)
-
routes/admin-comments.js (modified) (3 diffs)
-
routes/admin-sites.js (modified) (4 diffs)
-
routes/admin.js (modified) (1 diff)
-
views/pages/admin-comments.ejs (modified) (2 diffs)
-
views/pages/hub-home.ejs (modified) (1 diff)
-
views/pages/my-site.ejs (added)
-
views/partials/topnav.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/middleware/auth.js
rccbd6a6 r8cb1dc7 2 2 * Auth middleware 3 3 */ 4 5 import db from '../config/database.js'; 4 6 5 7 /** … … 42 44 next(); 43 45 } 46 47 // Mag de ingelogde user de HUIDIGE site (res.locals.site) beheren? god altijd; 48 // anders alleen de owner van die site. Gebruikt voor site-gescopete beheerroutes 49 // die een artiest via /user/<eigen-slug>/admin/... bereikt (res.locals.site is dan 50 // z'n eigen site; een vreemde slug levert een andere site -> 403). 51 export function requireSiteManager(req, res, next) { 52 if (!req.session?.user) return loginRedirect(req, res); 53 const u = req.session.user; 54 if (u.role === 'god') return next(); 55 const site = res.locals.site; 56 if (site && site.owner_id === u.id) return next(); 57 return res.status(403).send('Geen toegang tot deze site.'); 58 } 59 60 // Idem, maar de site wordt bepaald door de :slug-parameter (bv. site-edit). 61 export function requireSiteManagerBySlug(req, res, next) { 62 if (!req.session?.user) return loginRedirect(req, res); 63 const u = req.session.user; 64 if (u.role === 'god') return next(); 65 const site = db.prepare('SELECT owner_id FROM sites WHERE slug = ?').get(req.params.slug); 66 if (site && site.owner_id === u.id) return next(); 67 return res.status(403).send('Geen toegang tot deze site.'); 68 } -
src/middleware/render.js
rccbd6a6 r8cb1dc7 13 13 import { fileURLToPath } from 'url'; 14 14 import ejs from 'ejs'; 15 import db from '../config/database.js'; 15 16 import PermissionsService from '../services/PermissionsService.js'; 16 17 import { PLATFORMS as PLATFORMS_CATALOG } from '../services/PlatformIcons.js'; … … 36 37 const isPartial = req.headers['hx-request'] === 'true' || req.query.partial === '1'; 37 38 39 // Bezit deze (niet-god) user een eigen site? Bepaalt of 'ie een "Beheer"- 40 // ingang ziet (artiest-zelfbeheer). god ziet beheer sowieso (op rol). 41 const _u = req.session?.user || null; 42 const userOwnsSite = !!(_u && _u.role !== 'god' && 43 db.prepare('SELECT 1 FROM sites WHERE owner_id = ? LIMIT 1').get(_u.id)); 44 38 45 // Common locals 39 46 const locals = { 40 user: req.session?.user || null, 47 user: _u, 48 userOwnsSite, 41 49 site: data.site || res.locals.site || null, 42 50 audioTracks: data.audioTracks || res.locals.audioTracks || [], -
src/routes/admin-comments.js
rccbd6a6 r8cb1dc7 15 15 import db from '../config/database.js'; 16 16 import { renderPage } from '../middleware/render.js'; 17 import { require God} from '../middleware/auth.js';17 import { requireSiteManager } from '../middleware/auth.js'; 18 18 19 19 const router = express.Router(); 20 20 21 router.get('/', require God, (req, res) => {21 router.get('/', requireSiteManager, (req, res) => { 22 22 const site = res.locals.site; 23 23 if (!site) return res.status(404).send('No site'); … … 61 61 const site = res.locals.site; 62 62 if (!site) return res.status(404).send('No site'); 63 const base = res.locals.siteUrlBase || ''; // /user/<slug> in hub-artiestcontext, anders '' 63 64 64 65 const row = db.prepare(` … … 67 68 `).get(req.params.id, site.id); 68 69 69 if (!row) return res.redirect( '/admin/comments?error=Not+found');70 if (!row) return res.redirect(base + '/admin/comments?error=Not+found'); 70 71 71 72 db.prepare( 72 73 'UPDATE comments SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?' 73 74 ).run(status, req.params.id); 74 res.redirect( '/admin/comments?success=' + encodeURIComponent('Comment ' + status));75 res.redirect(base + '/admin/comments?success=' + encodeURIComponent('Comment ' + status)); 75 76 } 76 77 77 router.post('/:id/approve', require God, (req, res) => setStatus(req, res, 'approved'));78 router.post('/:id/reject', require God, (req, res) => setStatus(req, res, 'rejected'));78 router.post('/:id/approve', requireSiteManager, (req, res) => setStatus(req, res, 'approved')); 79 router.post('/:id/reject', requireSiteManager, (req, res) => setStatus(req, res, 'rejected')); 79 80 80 81 export default router; -
src/routes/admin-sites.js
rccbd6a6 r8cb1dc7 22 22 import db from '../config/database.js'; 23 23 import { renderPage } from '../middleware/render.js'; 24 import { requireGod } from '../middleware/auth.js';24 import { requireGod, requireAuth, requireSiteManagerBySlug } from '../middleware/auth.js'; 25 25 import ThemeService from '../services/ThemeService.js'; 26 26 import { listPlatforms, PLATFORMS } from '../services/PlatformIcons.js'; … … 82 82 // holds the URL string in `profile_photo` — this endpoint just stores the 83 83 // file and hands back a URL that the form can paste into the input field. 84 router.post('/upload-photo', require God, (req, res) => {84 router.post('/upload-photo', requireAuth, (req, res) => { 85 85 photoUpload.single('photo')(req, res, (err) => { 86 86 if (err) return res.status(400).json({ ok: false, error: err.message }); … … 218 218 219 219 // ==================== EDIT (form) ==================== 220 router.get('/:slug/edit', require God, (req, res) => {220 router.get('/:slug/edit', requireSiteManagerBySlug, (req, res) => { 221 221 const site = db.prepare('SELECT * FROM sites WHERE slug = ?').get(req.params.slug); 222 222 if (!site) return res.redirect('/admin/sites?error=Not+found'); … … 242 242 243 243 // ==================== SAVE ==================== 244 router.post('/:slug/save', require God, (req, res) => {244 router.post('/:slug/save', requireSiteManagerBySlug, (req, res) => { 245 245 const site = db.prepare('SELECT id FROM sites WHERE slug = ?').get(req.params.slug); 246 246 if (!site) return res.redirect('/admin/sites?error=Not+found'); -
src/routes/admin.js
rccbd6a6 r8cb1dc7 8 8 import db from '../config/database.js'; 9 9 import { renderPage } from '../middleware/render.js'; 10 import { require God} from '../middleware/auth.js';10 import { requireAuth } from '../middleware/auth.js'; 11 11 import { getTenancy } from '../services/SettingsService.js'; 12 12 13 13 const router = express.Router(); 14 14 15 router.get('/', requireGod, (req, res) => { 15 router.get('/', requireAuth, (req, res) => { 16 const user = req.session.user; 17 18 // Niet-god: een artiest die een eigen site bezit krijgt een "Mijn PrutFolio"- 19 // dashboard, gescopet op z'n eigen site. Bezit 'ie geen site -> geen beheer. 20 if (user.role !== 'god') { 21 const mySite = db.prepare( 22 'SELECT * FROM sites WHERE owner_id = ? ORDER BY created_at ASC LIMIT 1' 23 ).get(user.id); 24 if (!mySite) return res.status(403).send('Geen beheer beschikbaar voor dit account.'); 25 26 const mine = { 27 posts: db.prepare("SELECT COUNT(*) AS c FROM posts WHERE site_id = ?").get(mySite.id).c, 28 published: db.prepare("SELECT COUNT(*) AS c FROM posts WHERE site_id = ? AND status = 'published'").get(mySite.id).c, 29 }; 30 return renderPage(req, res, 'pages/my-site', { 31 pageTitle: 'Mijn PrutFolio', 32 bodyClass: 'on-admin', 33 mySite, 34 mine, 35 }); 36 } 37 16 38 const tenancy = getTenancy(); 17 39 -
src/views/pages/admin-comments.ejs
rccbd6a6 r8cb1dc7 26 26 <strong><%= c.author_username %></strong> 27 27 <% if (c.parent_comment_id) { %><span class="muted">(reply)</span><% } %> 28 on <a href=" /<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a>28 on <a href="<%= siteUrlBase %>/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a> 29 29 <span class="muted">· <%= formatDateTime(c.created_at) %></span> 30 30 </div> 31 31 <blockquote class="moderation-content"><%= c.content %></blockquote> 32 32 <div class="moderation-actions"> 33 <form method="post" action=" /admin/comments/<%= c.id %>/approve" style="display:inline">33 <form method="post" action="<%= siteUrlBase %>/admin/comments/<%= c.id %>/approve" style="display:inline"> 34 34 <button type="submit" class="btn btn-primary">Approve</button> 35 35 </form> 36 <form method="post" action=" /admin/comments/<%= c.id %>/reject" style="display:inline">36 <form method="post" action="<%= siteUrlBase %>/admin/comments/<%= c.id %>/reject" style="display:inline"> 37 37 <button type="submit" class="btn btn-danger">Reject</button> 38 38 </form> … … 55 55 <tr class="status-<%= c.status %>"> 56 56 <td><%= c.author_username %></td> 57 <td><a href=" /<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a></td>57 <td><a href="<%= siteUrlBase %>/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a></td> 58 58 <td> 59 59 <span class="status-pill status-pill--<%= c.status %>"><%= c.status %></span> -
src/views/pages/hub-home.ejs
rccbd6a6 r8cb1dc7 5 5 <div class="hub-nav-inner container"> 6 6 <% if (user) { %> 7 <% if (user.role === 'god' || user.role === 'admin' ) { %>7 <% if (user.role === 'god' || user.role === 'admin' || (typeof userOwnsSite !== 'undefined' && userOwnsSite)) { %> 8 8 <a class="hub-nav-link" href="/admin">Beheer</a> 9 9 <% } %> -
src/views/partials/topnav.ejs
rccbd6a6 r8cb1dc7 109 109 <span>Account</span> 110 110 </a> 111 <% if (user.role === 'god' || user.role === 'admin' ) { %>111 <% if (user.role === 'god' || user.role === 'admin' || (typeof userOwnsSite !== 'undefined' && userOwnsSite)) { %> 112 112 <a href="/admin" role="menuitem" class="udi"> 113 113 <svg class="udi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3h7v7H3z"/><path d="M14 3h7v7h-7z"/><path d="M14 14h7v7h-7z"/><path d="M3 14h7v7H3z"/></svg> … … 116 116 <% } %> 117 117 <% if (_canPost) { %> 118 <a href=" /posts/new" role="menuitem" class="udi"119 hx-get=" /posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"120 hx-push-url=" /posts/new" hx-indicator="#pcms-loading">118 <a href="<%= _siteUrlBase %>/posts/new" role="menuitem" class="udi" 119 hx-get="<%= _siteUrlBase %>/posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 120 hx-push-url="<%= _siteUrlBase %>/posts/new" hx-indicator="#pcms-loading"> 121 121 <svg class="udi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg> 122 122 <span>Nieuwe post</span>
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)