| 1 | -- v9 → v1 post import
|
|---|
| 2 | -- Run with: sqlite3 storage/database.sqlite < import.sql
|
|---|
| 3 |
|
|---|
| 4 | BEGIN TRANSACTION;
|
|---|
| 5 |
|
|---|
| 6 | -- audio-player: "Audio Player"
|
|---|
| 7 | INSERT INTO posts (
|
|---|
| 8 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 9 | status, cover_image_url, pinned, type, tags,
|
|---|
| 10 | published_at, created_at, updated_at
|
|---|
| 11 | ) VALUES (
|
|---|
| 12 | '10eca96b-6ca8-44a0-9b0e-88722465e4c9',
|
|---|
| 13 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 14 | 'audio-player',
|
|---|
| 15 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 16 | 'Audio Player',
|
|---|
| 17 | '<p>Ik wilde mijn eigen platform samenstellen, mijn eigen app, mijn eigen omgeving, voor mijn posts, mijn muziek, mijn content.<br>En dat is gelukt =)<br><br><br>Mine, mine<span style="font-size: 1.0625rem;">, mine</span><span style="font-size: 1.0625rem;">, mine</span><span style="font-size: 1.0625rem;">, mine.<br><br></span></p><p><span style="font-size: 1.0625rem;"><br></span></p>',
|
|---|
| 18 | 'speciaal voor mij een goede custom audio player website',
|
|---|
| 19 | 'published',
|
|---|
| 20 | '/images/0e98262c47efe4bb.jpg',
|
|---|
| 21 | 0,
|
|---|
| 22 | 'audio',
|
|---|
| 23 | '["muziek","audio","player","website","prutcms","feature","custom","soundfabrics","build","zip"]',
|
|---|
| 24 | '2026-04-24T10:55:44+02:00',
|
|---|
| 25 | '2026-04-24T10:55:44+02:00',
|
|---|
| 26 | '2026-04-24T10:55:44+02:00'
|
|---|
| 27 | );
|
|---|
| 28 |
|
|---|
| 29 | -- cookie-monster: "Cookie Monster?"
|
|---|
| 30 | INSERT INTO posts (
|
|---|
| 31 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 32 | status, cover_image_url, pinned, type, tags,
|
|---|
| 33 | published_at, created_at, updated_at
|
|---|
| 34 | ) VALUES (
|
|---|
| 35 | '565cd5ed-7753-4210-bbd7-3c6842d84e88',
|
|---|
| 36 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 37 | 'cookie-monster',
|
|---|
| 38 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 39 | 'Cookie Monster?',
|
|---|
| 40 | '<p>Kort antwoord: <span class="accent">PrutCMS zet geen tracking-cookies, spoort je niet, stuurt niks naar derden, en heeft geen consent-banner nodig</span>. Nul externe calls by default — geen enkele byte gaat naar een domein dat jij niet zelf host. Dit artikel legt uit wat er wél gebeurt en waarom dat binnen AVG/GDPR blijft.</p>
|
|---|
| 41 |
|
|---|
| 42 | <h2>Wat PrutCMS NIET doet</h2>
|
|---|
| 43 | <p>Geen Google Analytics. Geen Facebook Pixel. Geen Hotjar, Mixpanel, Segment, of enige andere analytics-dienst. Geen <a href="https://www.cookiepro.com">Consent Management Platform</a>. Geen externe fonts van fonts.googleapis.com. Geen CDN voor jQuery/Bootstrap/whatever. Geen Gravatar-calls. Geen OpenGraph-scrapes naar andere sites tenzij jij een URL embedded. Geen error-reporting naar Sentry of iets dergelijks. Geen newsletter-service tracking pixels. Geen "share"-buttons die naar Facebook/Twitter phonen.</p>
|
|---|
| 44 |
|
|---|
| 45 | <p>Check zelf: open DevTools → Network tab → refresh een pagina. Je ziet alleen requests naar het domein waar je nu bent. Geen enkele third-party domain. Dat is geen accident — dat is het ontwerp.</p>
|
|---|
| 46 |
|
|---|
| 47 | <h2>Wat PrutCMS WEL doet</h2>
|
|---|
| 48 |
|
|---|
| 49 | <h3>Session cookies</h3>
|
|---|
| 50 | <p>Als je inlogt, zet PrutCMS één cookie: <code>PHPSESSID</code>. Dit is een random ID dat jouw browser koppelt aan een server-side sessie. Alleen voor login-status, CSRF-protection, en shopping-cart-achtige state. Geen tracking-info in deze cookie, geen cross-site bruikbaarheid, <code>HttpOnly</code> en <code>SameSite=Lax</code> by default. Duur: tot je uitlogt of de sessie expired.</p>
|
|---|
| 51 |
|
|---|
| 52 | <p>Voor anonieme bezoekers wordt er <strong>geen cookie gezet</strong>. Je kan volledig de hele site browsen zonder ooit een cookie te krijgen. Pas bij daadwerkelijk inloggen komt die PHPSESSID.</p>
|
|---|
| 53 |
|
|---|
| 54 | <h3>Localstorage (alleen jouw browser)</h3>
|
|---|
| 55 | <p>Voor gebruikers-comfort bewaart PrutCMS in je lokale browser-storage:</p>
|
|---|
| 56 |
|
|---|
| 57 | <ul>
|
|---|
| 58 | <li><code>pcms-theme</code> — light/dark mode voorkeur</li>
|
|---|
| 59 | <li><code>pcms-audio-state</code> — welke track je aan het luisteren bent + positie (zodat het doorspeelt bij pagina-wissel)</li>
|
|---|
| 60 | <li><code>pcms-audio-volume</code> — jouw volume-setting</li>
|
|---|
| 61 | </ul>
|
|---|
| 62 |
|
|---|
| 63 | <p>Dit is géén cookie. Het leeft uitsluitend in jouw browser, wordt nooit naar de server gestuurd, en is niet leesbaar voor andere sites. Clear je browser-data en het is weg. AVG/GDPR: localStorage voor puur functionele voorkeuren zonder tracking-doel valt onder "strikt noodzakelijk" en vereist geen consent-banner.</p>
|
|---|
| 64 |
|
|---|
| 65 | <h3>Service worker (PWA)</h3>
|
|---|
| 66 | <p>Voor de installable-app-functionaliteit registreert PrutCMS een service worker (<code>/sw.js</code>). Deze cached je site offline, maar doet geen calls naar derden, bewaart geen analytics, en tracked geen usage. Je kan ''m zien via DevTools → Application → Service Workers, en uitschakelen met <code>/sw-reset</code>.</p>
|
|---|
| 67 |
|
|---|
| 68 | <h3>Signed audio URLs (v9)</h3>
|
|---|
| 69 | <p>Audio-bestanden krijgen een HMAC-signed tijdelijke URL (10 minuten geldig). Hotlinkers en scrapers kunnen je audio niet stelen — directe URL''s naar <code>/audio/hash.mp3</code> geven 403. De signing-logica draait volledig op jouw server met een eigen secret. Geen externe auth-service, geen DRM-licentie-server, geen telemetrie over wie wat afspeelt.</p>
|
|---|
| 70 |
|
|---|
| 71 | <p>De player vernieuwt URLs proactief elke 8 minuten via jouw eigen domein (<code>?refresh_audio=1</code>). Geen externe token-server, geen third-party refresh-dienst. Wat over de lijn gaat is alleen een lijst van jouw eigen audio-files met nieuwe signatures.</p>
|
|---|
| 72 |
|
|---|
| 73 | <h3>MediaSession API (lockscreen controls)</h3>
|
|---|
| 74 | <p>Deze API toont de huidige track + cover op je telefoon-lockscreen, in de notification-bar, en laat bluetooth-headphone-knoppen werken. Dit is <strong>100% browser-lokaal</strong>: metadata wordt door <em>jouw</em> browser aan <em>jouw</em> OS doorgegeven, niet naar een server gestuurd. Geen Google Cast, geen Spotify Connect, geen external media registry — pure native browser API.</p>
|
|---|
| 75 |
|
|---|
| 76 | <h3>Wake Lock API (scherm-actief tijdens play)</h3>
|
|---|
| 77 | <p>Tijdens audio-playback stelt de speler het scherm-blanking uit zodat je telefoon niet in deep sleep gaat halverwege een track. Dit gaat via de <strong>browser Wake Lock API</strong> — een lokale OS-permissie die jij per site kunt geven of weigeren. Geen tracking, geen batterij-drain-profilering, geen "wie luistert wanneer"-logging. De lock wordt automatisch losgelaten zodra je pauzeert of de tab sluit.</p>
|
|---|
| 78 |
|
|---|
| 79 | <h3>Server logs</h3>
|
|---|
| 80 | <p>Apache/nginx logt standaard wat requests, incluis IP-adres. PrutCMS doet zelf géén IP-logging in applicatie-data. Voor rate-limiting (login-pogingen etc) wordt het IP-adres <strong>gehashed</strong> opgeslagen in de database of in een klein rate-limit-bestand — niet rauw. Audit logs bevatten actions (wie logde in, wie verwijderde een post) maar gebruiken ook gehashed IPs.</p>
|
|---|
| 81 |
|
|---|
| 82 | <p>Wat je hoster met server-logs doet valt buiten PrutCMS. Goede hosters (TransIP, Hetzner, OVH) volgen AVG/GDPR en bewaren logs maximaal een paar weken. Check hun privacy-verklaring.</p>
|
|---|
| 83 |
|
|---|
| 84 | <h3>Email (optioneel)</h3>
|
|---|
| 85 | <p>Voor password-reset en email-verify stuurt PrutCMS emails via je server''s PHP <code>mail()</code> of via SMTP naar een mailserver die jij configureert. Geen Mailchimp, geen SendGrid, geen externe mailing-dienst. De email-adressen staan in je users-database of users.json, nooit bij een derde.</p>
|
|---|
| 86 |
|
|---|
| 87 | <h2>CRDT-sync is ook privacy-neutraal</h2>
|
|---|
| 88 | <p>Als je PrutCMS instances met elkaar laat syncen (voor multi-device of federation), gebeurt dit via je eigen domein. Geen central sync-server, geen "PrutCMS cloud", geen Anthropic/Google/AWS in de keten. Jij hosted alles, jij controleert het.</p>
|
|---|
| 89 |
|
|---|
| 90 | <p>Elke tabel/bestand heeft een <code>hlc_ts</code> (Hybrid Logical Clock timestamp) voor conflict-free merging. Dit is technische metadata, geen identificatie. Als je twee kopieën wil mergen: beide draaien de sync-endpoint, pushen hun recent-changed items naar elkaar, HLC beslist welke "wint". Alles P2P tussen jouw servers.</p>
|
|---|
| 91 |
|
|---|
| 92 | <h2>Externe embeds (als je ze gebruikt)</h2>
|
|---|
| 93 | <p>Als je in een post een YouTube-video, SoundCloud-track, of Spotify-embed invoegt, wordt die vanuit hun servers geladen. Op <em>die</em> embed-pagina gelden hun cookies/tracking. PrutCMS gebruikt <code>youtube-nocookie.com</code> voor YouTube embeds by default — dat is YouTube''s privacy-enhanced mode die pas tracking-cookies zet na interactie.</p>
|
|---|
| 94 |
|
|---|
| 95 | <p>Als je embeds vermijdt, heb je nul externe calls. Als je ze gebruikt, bepaal jij het per post, en de gebruiker ziet zelf waar ze vandaan komen (zichtbaar in DevTools).</p>
|
|---|
| 96 |
|
|---|
| 97 | <h2>AVG/GDPR: geen consent-banner nodig</h2>
|
|---|
| 98 | <p>De Europese AVG vereist een consent-banner voor niet-essentiële cookies en tracking. Aangezien PrutCMS <strong>geen niet-essentiële cookies zet en niet trackt</strong>, is er niets om consent voor te vragen. De enige cookie (PHPSESSID na login) valt onder "strikt noodzakelijk voor de aangevraagde dienst" — vrijgesteld van consent-plicht.</p>
|
|---|
| 99 |
|
|---|
| 100 | <p>Als je zelf externe embeds, Google Analytics, of nieuwsbrief-tracking toevoegt: dan zul je weer een consent-banner moeten bouwen. Maar dat is jouw keuze, niet iets wat PrutCMS oplegt.</p>
|
|---|
| 101 |
|
|---|
| 102 | <h2>MySQL-optie (v9.1+): wat verandert er?</h2>
|
|---|
| 103 | <p>Met MySQL als backend werken dezelfde principes. Rate-limit en audit-log verhuizen van JSON-files naar DB-tabellen, maar de inhoud blijft gelijk — gehashed IPs, geen rauwe identificatie. Je backup wordt <code>mysqldump</code> in plaats van <code>tar /posts</code>, maar de data blijft op jouw server.</p>
|
|---|
| 104 |
|
|---|
| 105 | <h2>Bewijs: audit het zelf</h2>
|
|---|
| 106 | <p>Open <code>grep -r "google\|facebook\|fbq\|gtag\|analytics\|hotjar\|segment" /path/to/prutcms</code>. Resultaat: geen matches in de core. De enige "google" die je zou kunnen vinden is in docs of comments — geen enkele runtime-call.</p>
|
|---|
| 107 |
|
|---|
| 108 | <p>Voor forensische zekerheid: draai PrutCMS achter een firewall die outbound connections logt (<code>tcpdump</code> of je router''s logs). Surf door je eigen site. Het enige outbound verkeer zou naar update-checks of embed-URLs moeten zijn — en die kan je uitschakelen.</p>
|
|---|
| 109 |
|
|---|
| 110 | <p>Dat is PrutCMS''s belofte, en het is controleerbaar. Geen marketing, gewoon geen externe calls.</p>
|
|---|
| 111 | ',
|
|---|
| 112 | 'PrutCMS zet geen tracking-cookies, spoort je niet, stuurt niks naar derden, en heeft geen consent-banner nodig. Zero external calls by default. Dit artikel legt per onderdeel uit wat er wel en niet gebeurt met je data.',
|
|---|
| 113 | 'published',
|
|---|
| 114 | '/images/4cc2237d68c1b5f4.jpg',
|
|---|
| 115 | 1,
|
|---|
| 116 | 'overig',
|
|---|
| 117 | '["privacy","avg","gdpr","cookies","security","no-tracking"]',
|
|---|
| 118 | '2026-04-24T10:29:59.000Z',
|
|---|
| 119 | '2026-04-24T10:29:59.000Z',
|
|---|
| 120 | '2026-04-24T10:29:59.000Z'
|
|---|
| 121 | );
|
|---|
| 122 |
|
|---|
| 123 | -- covers: "AI Covers"
|
|---|
| 124 | INSERT INTO posts (
|
|---|
| 125 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 126 | status, cover_image_url, pinned, type, tags,
|
|---|
| 127 | published_at, created_at, updated_at
|
|---|
| 128 | ) VALUES (
|
|---|
| 129 | '542da3fd-5e28-4e6c-9999-00efe8ce992e',
|
|---|
| 130 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 131 | 'covers',
|
|---|
| 132 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 133 | 'AI Covers',
|
|---|
| 134 | '<div data-pcms-playlist="ai-covers" contenteditable="false" class="post-playlist-ref">📃 Playlist <code>ai-covers</code> — wordt automatisch geladen bij weergave</div><p><br></p>',
|
|---|
| 135 | '',
|
|---|
| 136 | 'published',
|
|---|
| 137 | '/images/b40355d8b9b5cf87.jpg',
|
|---|
| 138 | 1,
|
|---|
| 139 | 'overig',
|
|---|
| 140 | '[]',
|
|---|
| 141 | '2026-04-25T09:46:53.000Z',
|
|---|
| 142 | '2026-04-25T09:46:53.000Z',
|
|---|
| 143 | '2026-04-25T09:46:53.000Z'
|
|---|
| 144 | );
|
|---|
| 145 |
|
|---|
| 146 | -- officialapp: "Als je durft"
|
|---|
| 147 | INSERT INTO posts (
|
|---|
| 148 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 149 | status, cover_image_url, pinned, type, tags,
|
|---|
| 150 | published_at, created_at, updated_at
|
|---|
| 151 | ) VALUES (
|
|---|
| 152 | '386c48b8-53a0-4baf-8829-002e452cd2d0',
|
|---|
| 153 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 154 | 'officialapp',
|
|---|
| 155 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 156 | 'Als je durft',
|
|---|
| 157 | '<p>Ik bied een officiele soundfabrics app aan alleen voor Android gebruikers.<br><br>Klik op de onderstaande link beneden om het te installeren:<br>> <a href="https://roboburr.com/apk-build/soundfabrics.apk">soundfabrics.apk</a><br><br>Voor nieuwe gebruikers, dit is een app dat niet te vinden is in de Play Store, dus moet onbekende bronnen aangezet worden om het te kunnen installeren.<br>Als het goed is, doet Play Protect ook meteen een veiligheidscan, waar het geen problemen mee zal gaan vinden.<br><br>Groet Robin</p>',
|
|---|
| 158 | 'Ik bied een officiele soundfabrics app aan alleen voor Android gebruikers.',
|
|---|
| 159 | 'published',
|
|---|
| 160 | '/images/57944b37abd65d4f.jpg',
|
|---|
| 161 | 1,
|
|---|
| 162 | 'link',
|
|---|
| 163 | '["app","android","apk","file","soundfabrics","roboburr","website","link","music","ai"]',
|
|---|
| 164 | '2026-04-24T18:06:34.000Z',
|
|---|
| 165 | '2026-04-24T18:06:34.000Z',
|
|---|
| 166 | '2026-04-24T18:06:34.000Z'
|
|---|
| 167 | );
|
|---|
| 168 |
|
|---|
| 169 | -- post: "Dit is een post"
|
|---|
| 170 | INSERT INTO posts (
|
|---|
| 171 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 172 | status, cover_image_url, pinned, type, tags,
|
|---|
| 173 | published_at, created_at, updated_at
|
|---|
| 174 | ) VALUES (
|
|---|
| 175 | '785545c2-c097-4d63-a166-db9d5f554070',
|
|---|
| 176 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 177 | 'post',
|
|---|
| 178 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 179 | 'Dit is een post',
|
|---|
| 180 | '<p>Begin hier met schrijven… want dat doe je voor een post.</p><figure class="post-image post-image--small post-image--left" contenteditable="false"><img src="/images/a07ad0f1982e449b.jpg" alt=""></figure><p><br></p>',
|
|---|
| 181 | 'alles testen!',
|
|---|
| 182 | 'published',
|
|---|
| 183 | '/images/40a637c723fe2ad4.jpg',
|
|---|
| 184 | 0,
|
|---|
| 185 | 'tekst',
|
|---|
| 186 | '["post","random","schrijven","lol"]',
|
|---|
| 187 | '2026-04-24T09:02:08+02:00',
|
|---|
| 188 | '2026-04-24T09:02:08+02:00',
|
|---|
| 189 | '2026-04-24T09:02:08+02:00'
|
|---|
| 190 | );
|
|---|
| 191 |
|
|---|
| 192 | -- themesong-yougubrands: "Theme Song van YOUGU Brands"
|
|---|
| 193 | INSERT INTO posts (
|
|---|
| 194 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 195 | status, cover_image_url, pinned, type, tags,
|
|---|
| 196 | published_at, created_at, updated_at
|
|---|
| 197 | ) VALUES (
|
|---|
| 198 | '4665451b-43f7-45de-8800-67893573c25f',
|
|---|
| 199 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 200 | 'themesong-yougubrands',
|
|---|
| 201 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 202 | 'Theme Song van YOUGU Brands',
|
|---|
| 203 | '<h2><span class="accent"><div class="post-audio-track" data-pcms-track-url="/audio/stream/b41e81f92618db3b.mp3?t=bad29408522df2a2baa73c3e43436c7d1b2758bd44a4712b2883131d2c188af8&exp=1777069366" data-pcms-track="{"url":"/audio/stream/b41e81f92618db3b.mp3?t=bad29408522df2a2baa73c3e43436c7d1b2758bd44a4712b2883131d2c188af8&exp=1777069366","title":"Theme Song","artist":"YOUGU Brands","cover":"/images/d61ffcdb2a30c479.jpg"}" contenteditable="false"><span class="pat-meta"><span class="pat-title">Theme Song</span><span class="pat-artist">YOUGU Brands</span></span><span class="pat-duration">4:54</span></div><p><br></p>yougubrands.com — het huis achter de merken</span></h2>
|
|---|
| 204 |
|
|---|
| 205 | <p>Yougubrands.com is de digitale paraplu van de <span class="accent">YOUGU</span>-holding — de plek waar een zorgvuldig samengesteld portfolio van premium wellness- en lifestyle-merken zich als één familie presenteert. De kernboodschap wordt kort en bilingual (EN/CN) gebracht: <em>organisch leven verheffen via hoogwaardige producten en duurzame innovatie</em>. Waar natuurlijke wijsheid en moderne lifestyle-behoeften elkaar ontmoeten, zoals de tagline het formuleert. Het is een missiestatement dat bewust brede schouders heeft — want onder die paraplu hangt een verrassend eclectisch gezelschap.</p>
|
|---|
| 206 |
|
|---|
| 207 | <p>Vier merken, vier werelden. <strong><span class="accent"><b>Daily Joy</b></span></strong> zit in de hoek van probiotica en dagelijks welzijn.<span class="accent"><b> Luxovious</b></span><strong></strong> mikt op de liefhebber van whisky en premium drank. <strong></strong><span class="accent"><b>Piko Bello </b></span>maakt producten voor kinderen. En<span class="accent"><b> OE</b></span><strong></strong> vervolmaakt het kwartet. Op papier een bijna vreemd mengsel — kinderverzorging naast single malt — maar de rode draad is de houding: natuur als startpunt, kwaliteit als norm, en een blik die duidelijk óók op de Chinese markt is gericht. Vandaar die consequente tweetaligheid in elke hoek van de site, van menu tot footer.</p>
|
|---|
| 208 |
|
|---|
| 209 | <p>De site zelf is eerder een rustige etalage dan een schreeuwerige marketingbrochure. Zachtgroen, crème, het YOUGU-logo als terugkerend anker, een foundation-sectie voor de impact-kant van het verhaal, een blog voor context en verdieping, en — heerlijk eigenwijs detail — een <em>theme song player</em> bovenin de header. Het voelt als een merkenhuis dat niet bang is om even stil te staan bij wat het is en wat het doet, zonder de bezoeker meteen met pop-ups te bestoken. Overzichtelijk, hedendaags, en voor wie de afzonderlijke merken al kent: eindelijk een plek waar ze samenkomen.</p>
|
|---|
| 210 |
|
|---|
| 211 | <p>🌱 <a href="https://yougubrands.com/about">yougubrands.com</a></p>',
|
|---|
| 212 | 'yougubrands heeft nu een eigen theme song sinds de site bijna af is!',
|
|---|
| 213 | 'published',
|
|---|
| 214 | '/images/43feb24aee7f899a.png',
|
|---|
| 215 | 0,
|
|---|
| 216 | 'tekst',
|
|---|
| 217 | '["yougubrands","yougu","bioyougu","website","finished","htmx","themesong","theme","song"]',
|
|---|
| 218 | '2026-04-24T09:11:18+02:00',
|
|---|
| 219 | '2026-04-24T09:11:18+02:00',
|
|---|
| 220 | '2026-04-24T09:11:18+02:00'
|
|---|
| 221 | );
|
|---|
| 222 |
|
|---|
| 223 | -- waiting-on-you: "Back to 1987!"
|
|---|
| 224 | INSERT INTO posts (
|
|---|
| 225 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 226 | status, cover_image_url, pinned, type, tags,
|
|---|
| 227 | published_at, created_at, updated_at
|
|---|
| 228 | ) VALUES (
|
|---|
| 229 | '1d4fb567-453c-4695-8563-5e35b55ccdd5',
|
|---|
| 230 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 231 | 'waiting-on-you',
|
|---|
| 232 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 233 | 'Back to 1987!',
|
|---|
| 234 | '<h3><span class="accent">Rick Astley — Waiting On You (80s AI Cover)</span></h3>
|
|---|
| 235 |
|
|---|
| 236 | <p>Rick Astley bracht op 6 februari 2026 <em>Waiting On You</em> uit als verjaardagscadeau aan zichzelf —<span class="accent"> hij werd 60</span>. Het nummer schreef, produceerde en speelde hij vrijwel helemaal solo in zijn eigen studio The Spud Farm. Emotionele strijkers, zijn onmiskenbare bariton, en een tekst die terugblikt op de eerste vonk van liefde en hoe die een leven lang kan blijven nasmeulen. Astley zei er zelf over dat spelen op een gitaar ouder dan hijzelf hem anders doet schrijven — <em>"a decade long ago, lyrically and style wise"</em>. Het klinkt modern, maar met een ouder hart.</p><p><br></p>
|
|---|
| 237 |
|
|---|
| 238 | <p>En precies dáár zit de grap van deze <span class="accent">80s AI-cover</span>. Waar het origineel bewust ingetogen en volwassen klinkt — piano, strijkers, de stem van een man die terugkijkt — sleurt deze versie het nummer terug naar<span class="accent"> exact de sound waar de Rickroll uit geboren i</span>s: die glimmende Stock Aitken Waterman-productie van <em><span class="accent">Never Gonna Give You Up</span></em>. Gated reverb drums, synth-stabs, een baslijn die stuitert. Het refrein krijgt ineens diezelfde onontkoombare earworm-energie als de hook <span class="accent">waarmee Astley in ''87 de wereld veroverde</span>.</p><p><br></p>
|
|---|
| 239 |
|
|---|
| 240 | <p>Wat het grappig maakt, is dat het verdacht goed wérkt.<span class="accent"> <span class="accent"></span></span><span class="accent"><span class="accent"></span></span><span class="accent"></span>De tekst —<span class="accent"> </span><span class="accent"><span class="accent"></span></span><span class="accent"></span>steegjes, sigarettenrook, een wasserette-jukebox, een meisje met een cheeky grin — leest al als een 80s-videoclip. <span class="accent">Rick schreef in 2026 eigenlijk een nummer dat hij óók in 1987 had kunnen maken</span>; deze cover pakt dat gevoel bij de kladden en zet het terug in het juiste decennium. Het resultaat voelt haast als een verloren B-kant van <em>Whenever You Need Somebody</em> die pas nu is opgegraven. Je verwacht half dat er halverwege ineens díe bekende riff losbarst — en technisch gezien is dat deze keer niet eens een <span class="accent">Rickroll</span>, maar gewoon <span class="accent">dezelfde man, dezelfde DNA, veertig jaar later.</span></p><p><span class="accent"><br></span></p>
|
|---|
| 241 |
|
|---|
| 242 | <p>🎧 Luister op deze website:<br><span style="font-size: 1.0625rem;"><div class="post-audio-track" data-pcms-track-url="/audio/stream/d773625168a0cbf3.mp3?t=f02624525b10ba9eaf3ebbecca5a2dd74a3c1b9becfc264f5196e81a748b4307&exp=1777062695" data-pcms-track="{"url":"/audio/stream/d773625168a0cbf3.mp3?t=f02624525b10ba9eaf3ebbecca5a2dd74a3c1b9becfc264f5196e81a748b4307&exp=1777062695","title":"Waiting On You (AI 80s)","artist":"Rick Astley","cover":"/images/2b9db76a45f4d274.jpg"}" contenteditable="false"><span class="pat-meta"><span class="pat-title">Waiting On You (AI 80s)</span><span class="pat-artist">Rick Astley</span></span><span class="pat-duration">3:23</span></div><p><br></p>Of op onderstaande streamingsdiensten, zoals:</span></p><p></p><p></p><p><a class="post-audio-external" data-platform="spotify" href="https://open.spotify.com/track/5KBRB9Ckyi2k1b6DqSAue1?si=3e475d757da948ec" target="_blank" rel="noopener noreferrer" contenteditable="false"><span class="pae-icon" aria-hidden="true">♫</span><span class="pae-meta"><span class="pae-title">Rick Astley - Waiting On You (AI 80s)</span><span class="pae-platform">Spotify</span></span><span class="pae-go" aria-hidden="true">↗</span></a><br><a class="post-audio-external" data-platform="soundcloud" href="https://soundcloud.com/roboburr/waitingonyou" target="_blank" rel="noopener noreferrer" contenteditable="false"><span class="pae-icon" aria-hidden="true">☁</span><span class="pae-meta"><span class="pae-title">Rick Astley - Waiting On You (AI 80s)</span><span class="pae-platform">SoundCloud</span></span><span class="pae-go" aria-hidden="true">↗</span></a><span style="font-size: 1.0625rem;"><br>Ook beschikbaar op andere diensten!</span></p><p></p><p><br></p><br><p></p><br><p></p>',
|
|---|
| 243 | 'rick roll',
|
|---|
| 244 | 'published',
|
|---|
| 245 | '/images/a07ad0f1982e449b.jpg',
|
|---|
| 246 | 0,
|
|---|
| 247 | 'audio',
|
|---|
| 248 | '[]',
|
|---|
| 249 | '2026-04-24T08:32:45+02:00',
|
|---|
| 250 | '2026-04-24T08:32:45+02:00',
|
|---|
| 251 | '2026-04-24T08:32:45+02:00'
|
|---|
| 252 | );
|
|---|
| 253 |
|
|---|
| 254 | -- welcome: "Welkom bij PrutCMS v9"
|
|---|
| 255 | INSERT INTO posts (
|
|---|
| 256 | id, site_id, slug, author_id, title, content, excerpt,
|
|---|
| 257 | status, cover_image_url, pinned, type, tags,
|
|---|
| 258 | published_at, created_at, updated_at
|
|---|
| 259 | ) VALUES (
|
|---|
| 260 | 'c714c515-8448-43ba-a563-e561f7f83569',
|
|---|
| 261 | (SELECT id FROM sites LIMIT 1),
|
|---|
| 262 | 'welcome',
|
|---|
| 263 | (SELECT id FROM users ORDER BY created_at ASC LIMIT 1),
|
|---|
| 264 | 'Welkom bij PrutCMS v9',
|
|---|
| 265 | '<p>Dit is <strong>PrutCMS v9</strong> — een CMS dat wil wat de grote jongens doen, maar dan zonder 400 plugins, zonder tracking, en zonder je data kwijtraken aan een cloud-provider. Het draait op goedkope shared hosting én schaalt mee als het groeit. De filosofie is simpel: je moet altijd bij je data kunnen, hoe complex het ook lijkt.</p>
|
|---|
| 266 |
|
|---|
| 267 | <p>Eén installatie draait zoveel sites als je wil, elk met eigen posts, styling en admins. Handig voor meerdere projecten onder één dak, of voor klanten met eigen ruimte op <code>jouwbedrijf.com/sites/klantnaam</code>. Elke sub-site krijgt eigen PWA manifest, eigen installable app, eigen RSS.</p>
|
|---|
| 268 |
|
|---|
| 269 | <h2>💾 Files of MySQL — jouw keuze</h2>
|
|---|
| 270 | <p>v9 werkt <strong>zonder database</strong>: elke post is een bestandje in <code>/posts</code>, elke user een regel in <code>users.json</code>, elke site een map. Portable tot het extreme — <code>zip -r backup.zip .</code> is je volledige backup. Restore = unzip.</p>
|
|---|
| 271 |
|
|---|
| 272 | <p>Voor grotere sites is er nu <strong>MySQL-support</strong>. Run <code>php migrate.php init && php migrate.php import</code>, zet <code>"storage": "mysql"</code> in config, en je hebt full-text search, real-time queries, foreign keys, transactions. Alles blijft backwards-compatible: switchen tussen <code>files</code> en <code>mysql</code> kost één config-regel. Zie <code>MYSQL-SETUP.md</code> voor de complete handleiding.</p>
|
|---|
| 273 |
|
|---|
| 274 | <h2>🎧 Audio player met lockscreen</h2>
|
|---|
| 275 | <p>Upload MP3, OGG, M4A of WAV via Media → Audio, en onderaan de site verschijnt een persistent player. Tik erop voor de <strong>full-screen expand-view</strong>: grote cover, next/prev, volledige wachtrij met klikbare track-covers, swipe-down om te sluiten. De player leeft buiten pagina''s — door de site klikken onderbreekt niet.</p>
|
|---|
| 276 |
|
|---|
| 277 | <p>Via MediaSession API verschijnt de huidige track op het <strong>Android en iPhone lockscreen</strong>, inclusief cover, artist, en bediening. Bluetooth-headphone-knoppen werken. Wake Lock voorkomt dat je scherm afsluit tijdens playback. Proactieve URL-refresh elke 8 minuten houdt lange luistersessies alive.</p>
|
|---|
| 278 |
|
|---|
| 279 | <p>Album-blocks voeg je toe via de 💿 knop in de editor: selecteer tracks, kies volgorde, en er verschijnt een klikbare album-cover met compacte tracklist. Klik op de cover — hele album laadt in de player. Track-switching gebeurt in de expand-view, niet in de post-HTML, wat de post schoon en klein houdt.</p>
|
|---|
| 280 |
|
|---|
| 281 | <p><strong>Signed URLs</strong>: elke audio-file krijgt een HMAC-signed URL met 10-minuten TTL. Directe links naar <code>/audio/hash.mp3</code> geven 403. Stopt hotlinkers, scrapers, en DevTools-kopieer-acties.</p>
|
|---|
| 282 |
|
|---|
| 283 | <h2>💬 Forum + Prutter + DMs</h2>
|
|---|
| 284 | <p>Ingebouwd forum met categorieën, threads, replies, pinning, locking, en moderatie-rollen. <strong>Prutter</strong> is de korte-post timeline: 280-tekens, likes, reposts, replies. <strong>Direct messages</strong> tussen users, end-to-end toegang-gecontroleerd. Alles gewoon onderdeel van PrutCMS — geen externe service, geen federation-gedoe voor een simpele community.</p>
|
|---|
| 285 |
|
|---|
| 286 | <h2>📲 Installable PWA + APK</h2>
|
|---|
| 287 | <p>Op Android en desktop Chrome/Edge verschijnt een <strong>Installeren</strong>-knop. PrutCMS draait dan in eigen venster, zonder browser-balk, met offline-cache via service worker. Elke sub-site is apart installable.</p>
|
|---|
| 288 |
|
|---|
| 289 | <p>Voor een echte Android-app: de <code>apk-build/</code> kit maakt een TWA (Trusted Web Activity) met Bubblewrap. Docker-based builder vereist alleen Docker — geen JDK 17, geen Android SDK, geen Node. Build, version-bump, en assetlinks-verify zijn losse scripts. Eén commando, getekende APK en AAB als output.</p>
|
|---|
| 290 |
|
|---|
| 291 | <h2>🔐 Auth, 2FA, en rechten</h2>
|
|---|
| 292 | <p>Eigen auth-systeem: username + password met bcrypt, optioneel TOTP (authenticator app) met backup-codes. Rate-limiting op login (file-based of MySQL-table). Rollen: <code>user</code>, <code>moderator</code>, <code>super_admin</code>, plus demo-mode voor read-only showcase-accounts. Invite-only registratie via tokens met TTL.</p>
|
|---|
| 293 |
|
|---|
| 294 | <h2>🎨 Thema en aanpasbaarheid</h2>
|
|---|
| 295 | <p>Dark/light mode auto-switched via prefers-color-scheme, met localStorage override. Per-site palet via <code>--accent</code> CSS variabele. Lettertypen zelf-gehost (geen Google Fonts). Mobile-first tot en met iPhone SE (320px).</p>
|
|---|
| 296 |
|
|---|
| 297 | <h2>🔒 Security</h2>
|
|---|
| 298 | <p>CSRF tokens op alle POST endpoints. HMAC-signed audio URLs. XSS-safe HTML sanitizer. Path-traversal protection op alle file-operaties. Rate-limiting op login, signup, password-reset. Security headers: CSP, HSTS (2 jaar + preload), frame-ancestors, base-uri, form-action, object-src, Cross-Origin-Opener/Resource-Policy. Permissions-Policy blokkeert 6 APIs standaard. <code>.htaccess</code> heeft Gzip en Brotli compressie, plus long-lived cache voor statics.</p>
|
|---|
| 299 |
|
|---|
| 300 | <h2>⚡ Snelle start</h2>
|
|---|
| 301 | <p>1. Upload bestanden naar webroot. 2. Open de site — installer begeleidt je door super-admin aanmaken en wat basis-config. 3. Klaar. Voor MySQL-upgrade: check <code>MYSQL-SETUP.md</code>. Voor APK-build: check <code>apk-build/README.md</code>.</p>
|
|---|
| 302 |
|
|---|
| 303 | <p>Veel plezier ermee. Als je iets stuk vindt: open een issue, of gewoon <code>grep -r "TODO" /path/to/prutcms</code> — misschien heb ik het al genoteerd.</p>
|
|---|
| 304 | ',
|
|---|
| 305 | 'Self-hosted CMS met multi-site, file-based of MySQL backend, signed audio URLs met lockscreen-controls, CRDT-sync, forum, prutter, en een installable PWA. Geen tracking, geen cookies, geen telemetrie. Dutch humor included.',
|
|---|
| 306 | 'published',
|
|---|
| 307 | '',
|
|---|
| 308 | 1,
|
|---|
| 309 | 'tekst',
|
|---|
| 310 | '["welkom","prutcms","v9","cms","privacy"]',
|
|---|
| 311 | '2026-04-24T10:00:00.000Z',
|
|---|
| 312 | '2026-04-24T10:00:00.000Z',
|
|---|
| 313 | '2026-04-24T10:00:00.000Z'
|
|---|
| 314 | );
|
|---|
| 315 |
|
|---|
| 316 | COMMIT;
|
|---|
| 317 |
|
|---|
| 318 | -- Done. Verify with: sqlite3 storage/database.sqlite "SELECT id, slug, title, pinned FROM posts;" |
|---|