Changeset 0c2228a in Klonkt


Ignore:
Timestamp:
06/14/2026 05:10:16 PM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
7963a6e
Parents:
05a6ca0
Message:

hub: home button actually visible + delete for every user (cascade)

  • topnav: the hub-home brand used .site-title, which is display:none everywhere (the profile header carries the name). Now its own visible class .nav-hub-home -> the hub name is truly shown top-left on /user/ pages, linking to the hub home.
  • admin/users: delete button for EVERY user (except yourself), not only empty accounts. Deletion cascades (sites + posts + playlists + audio + members + comments) atomically in a transaction; god-only, not the last god, not yourself. Confirmation warns when content will be removed.

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

Location:
src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-users.js

    r05a6ca0 r0c2228a  
    9898  }
    9999
    100   const owned = db.prepare(`
    101     SELECT
    102       (SELECT COUNT(*) FROM posts WHERE author_id = ?) AS posts,
    103       (SELECT COUNT(*) FROM sites WHERE owner_id  = ?) AS sites
    104   `).get(userId, userId);
     100  // Cascade-verwijderen: de sites van deze user (+ posts/playlists/audio/leden/
     101  // comments daaronder), z'n eigen content elders, en daarna de user zelf.
     102  // Atomisch in een transactie — faalt er een FK, dan rolt alles terug.
     103  const del = db.transaction(() => {
     104    const sites = db.prepare('SELECT id FROM sites WHERE owner_id = ?').all(userId).map((s) => s.id);
     105    for (const sid of sites) {
     106      db.prepare('DELETE FROM comments WHERE post_id IN (SELECT id FROM posts WHERE site_id = ?)').run(sid);
     107      db.prepare('DELETE FROM posts WHERE site_id = ?').run(sid);
     108      db.prepare('DELETE FROM playlists WHERE site_id = ?').run(sid);
     109      db.prepare('DELETE FROM audio_tracks WHERE site_id = ?').run(sid);
     110      db.prepare('DELETE FROM site_members WHERE site_id = ?').run(sid);
     111      db.prepare('DELETE FROM sites WHERE id = ?').run(sid);
     112    }
     113    // Eigen content op andere sites + losse koppelingen.
     114    db.prepare('DELETE FROM comments WHERE post_id IN (SELECT id FROM posts WHERE author_id = ?)').run(userId);
     115    db.prepare('DELETE FROM posts WHERE author_id = ?').run(userId);
     116    db.prepare('DELETE FROM comments WHERE author_id = ?').run(userId);
     117    db.prepare('DELETE FROM site_members WHERE user_id = ?').run(userId);
     118    db.prepare('DELETE FROM users WHERE id = ?').run(userId);
     119  });
    105120
    106   if (owned.posts > 0 || owned.sites > 0) {
    107     return res.redirect('/admin/users?error=' + encodeURIComponent(
    108       `Cannot delete: user owns ${owned.sites} site(s) and ${owned.posts} post(s). Reassign or delete those first.`
    109     ));
     121  try {
     122    del();
     123  } catch (e) {
     124    console.error('[admin/users delete]', e.message);
     125    return res.redirect('/admin/users?error=' + encodeURIComponent('Verwijderen mislukt (mogelijk gekoppelde data).'));
    110126  }
    111127
    112   // Clean up dangling references
    113   db.prepare('DELETE FROM site_members WHERE user_id = ?').run(userId);
    114   db.prepare('DELETE FROM comments WHERE author_id = ?').run(userId);
    115   db.prepare('DELETE FROM users WHERE id = ?').run(userId);
    116 
    117   res.redirect('/admin/users?success=' + encodeURIComponent('User deleted: ' + target.username));
     128  res.redirect('/admin/users?success=' + encodeURIComponent('Gebruiker verwijderd: ' + target.username));
    118129});
    119130
  • src/views/pages/admin-users.ejs

    r05a6ca0 r0c2228a  
    7474            </form>
    7575
    76             <% if (u.id !== user.id && (u.post_count + u.site_count === 0)) { %>
     76            <% if (u.id !== user.id) { %>
     77              <% var _warn = (u.post_count + u.site_count > 0)
     78                   ? ('Dit verwijdert ook hun site + ' + u.post_count + ' post(s). ')
     79                   : ''; %>
    7780              <form method="post" action="/admin/users/<%= u.id %>/delete" class="ax-delete-form"
    78                     onsubmit="return confirm('Verwijder gebruiker <%= u.username %>?')">
     81                    onsubmit="return confirm('Gebruiker <%= u.username %> verwijderen? <%= _warn %>Dit kan niet ongedaan worden.')">
    7982                <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="Verwijderen" title="Verwijderen">🗑</button>
    8083              </form>
  • src/views/partials/topnav.ejs

    r05a6ca0 r0c2228a  
    2828      <% } else if (typeof tenancy !== 'undefined' && tenancy === 'hub') { %>
    2929        <%# Hub: de brand is de hub-naam en gaat naar de hub-home (volledige nav, geen
    30             HTMX, zodat de headerloze overview correct rendert). %>
    31         <a class="site-title" href="/" aria-label="Naar de hub-home"><%= (typeof hubTitle !== 'undefined' && hubTitle) ? hubTitle : 'Home' %></a>
     30            HTMX, zodat de headerloze overview correct rendert). Eigen class want
     31            .site-title is overal display:none. %>
     32        <a class="nav-hub-home" href="/" aria-label="Naar de hub-home"><%= (typeof hubTitle !== 'undefined' && hubTitle) ? hubTitle : 'Home' %></a>
    3233      <% } else if (site) { %>
    3334        <a class="site-title" href="<%= _siteUrlBase %>/"
     
    232233}
    233234
     235/* Hub-home brand is WÉL zichtbaar — de thuisknop op artiest-/hub-pagina's. */
     236.masthead-brand .nav-hub-home {
     237  display: inline-block; vertical-align: middle;
     238  color: var(--ink); text-decoration: none;
     239  font-family: var(--font-display, serif); font-weight: 700; font-size: 1.05rem;
     240  transition: color 120ms;
     241}
     242.masthead-brand .nav-hub-home:hover { color: var(--accent); }
     243
    234244/* On mobile, hide the entire masthead except for admin pages. The brand
    235245   appears in the profile-header on content pages, and the bottom-tab
Note: See TracChangeset for help on using the changeset viewer.