Jakub Kubík
Napíšte mi

Všetky zápisky

zápisok · weby · ~7 min čítania

Ako vznikol web OZ Juliána: redizajn bez WordPressu a s vlastnou správou aktualít

Prečo web malého združenia beží bez WordPressu a databázy, ako funguje správa aktualít s bezpečným prihlásením z telefónu a ako sa overoval redizajn.

Publikované
téma
Weby pre malé organizácie
stav
hotové

V administrácii hostingu je tlačidlo „nastaviť DNS“. Kto naň klikne, ticho prepíše záznamy, podľa ktorých prehliadač hľadá server domény: zrazu mieria priamo na server hostingu, mimo CDN (siete serverov, ktorá web doručuje a chráni). Web beží ďalej, takže si zmenu nikto nevšimne. Na ozjuliana.sk sa presne toto stalo a zmenu neodhalil človek, ale skript, ktorý po každom nasadení živý web iba číta. Takých poistiek má web malého združenia viac, než by sa čakalo, a tento zápisok je o tom, prečo tam sú.

Občianske združenie Juliána celoročne zbiera oblečenie, obuv, hračky a potreby do domácnosti a rozváža ich ľuďom v núdzi. Web ozjuliana.sk preň dobrovoľnícky robím a spravujem od apríla 2026. Zadanie znie jednoducho: povedať, čo združenie robí a ako sa dá pomôcť, a umožniť človeku bez technického zázemia pridať z telefónu správu o zbierke aj s fotkami. Čísla v skratke sú v prípadovej štúdii; tu je dlhší príbeh.

Prečo nie WordPress a prečo súbory JSON bez databázy

Web beží na lacnom zdieľanom webhostingu za CDN. Na serveri je iba PHP: žiadne SSH (vzdialený príkazový riadok), žiadny Git, nasadzuje sa cez FTPS (prenos súborov šifrovaným spojením). WordPress by tam bežal, ale priniesol by so sebou pravidelné aktualizácie jadra a pluginov, databázu a nekonečné pokusy o prihlásenie na všeobecne známej adrese. Združenie pritom pridá článok s fotkami raz za pár týždňov. Pre takú potrebu sa neoplatí udržiavať celý redakčný systém.

Výsledkom je vlastné PHP bez frameworku a bez Composera. Verejné stránky sú šablóny, ktoré fungujú aj bez JavaScriptu. Tri malé skripty len pridávajú animácie, lištu so súhlasom, úvodnú clonu s logom a prehliadač fotiek.

Databáza nie je ani pre články. Každý článok je jeden súbor JSON (textový formát pre štruktúrované údaje), fotky ležia vedľa v priečinku a pri nahratí ich zmenší knižnica GD. Aj SQLite by sa dala použiť, lenže pri desiatkach článkov nič nezrýchli a súbory majú dve praktické výhody: celý obsah webu sa zálohuje skopírovaním dvoch priečinkov a v núdzi sa dá článok opraviť cez FTP v obyčajnom textovom editore.

Zápis ide vždy do dočasného súboru, ktorý sa až potom premenuje na cieľový, takže prerušené uloženie nenechá napoly zapísaný článok.

{
    "slug": "zbierka-satstva-2026",
    "title": "Zbierka šatstva 2026",
    "status": "published",
    "published_at": "2026-09-18T12:00:00+02:00",
    "text": "Ďakujeme všetkým, ktorí prispeli.\n\nZbierka pokračuje každý piatok.",
    "cover": { "big": "uploads/…-big.jpg", "thumb": "uploads/…-thumb.jpg", "alt": "Triedenie darovaného oblečenia" },
    "gallery": []
}

Článok je vymyslený, skutočné súbory majú navyše čas vytvorenia a poslednej úpravy. Obsah zo servera každý večer stiahne záloha, ktorá cez FTPS iba číta a drží 30 denných snímok. Pri chybe príde e‑mail.

Dvojfaktorové prihlásenie v čistom PHP

Administrácia článkov je jediné miesto, kde sa dá obsah webu zmeniť, preto má povinné dvojfaktorové prihlásenie: heslo a šesťmiestny kód z aplikácie Google Authenticator. Bez druhého faktora účet ani nevznikne.

TOTP, teda jednorazový kód odvodený od času (norma RFC 6238), je prekvapivo malý algoritmus: HMAC (kryptografický odtlačok) z tajného kľúča a poradového čísla 30-sekundového okna, z výsledku sa vyberie šesť číslic. Na hostingu bez Composera je jednoduchšie napísať ho v čistom PHP zo štandardných funkcií, než ručne nahrávať knižnicu so závislosťami. Implementácia sa overuje proti testovacím hodnotám priamo z normy a testovacia sada bez závislostí beží pred každým nasadením.

Okolo toho je bežná výbava: heslá uložené ako odtlačok bcrypt, ochranný token proti podvrhnutému odoslaniu (CSRF) v každom formulári, session s obmedzenou platnosťou, počítadlo neúspešných pokusov a prvý účet, ktorý sa nedá založiť bez prístupu k serveru. Konkrétne nastavenia tu zámerne nie sú; útočníkovi by pomohli viac než čitateľovi.

Administrácia pre telefón a človeka bez technického zázemia

Obsah sa spravuje takmer výlučne z telefónu. Administrácia je preto navrhnutá od šírky 320 px: väčšie tlačidlá a zaškrtávacie polia, dotykové ciele aspoň 44 px a vstupné polia s písmom aspoň 16 px, aby iPhone pri ťuknutí stránku nepribližoval. Funguje bez JavaScriptu, takže ju nerozbije ani starší prehliadač.

Formulár má len to, čo treba: nadpis, dátum, text, úvodnú fotku, galériu, popisy fotiek a prepínač medzi konceptom a zverejnením. Text je obyčajný text, prázdny riadok znamená nový odsek. Žiadny editor s tlačidlami, ktorý by do článku vkladal skryté HTML. Fotky z telefónu sa otočia podľa údajov EXIF, ktoré do nich zapíše fotoaparát, zmenšia a uložia bez metadát. Pri audite sa overilo všetkých 84 fotiek na živom webe a ani jedna nemá GPS súradnice.

Jedno rozhodnutie vzišlo priamo z toho, kto web obsluhuje. Prehliadač, v ktorom sa už niekto úspešne prihlásil, má vlastné počítadlo pokusov. Cudzie pokusy o prihlásenie tak môžu zablokovať neznáme zariadenia, nie však telefón, z ktorého sa obsah naozaj pridáva.

K administrácii patrí aj krátky návod v slovenčine. Najužitočnejšia rada v ňom nie je technická: prvá veta článku má povedať kedy, kde, čo a koľko, lebo z nej vzniká úryvok na kartách aj popis pre vyhľadávače.

Lišta so súhlasom podľa zákona 452/2021

Zákon o elektronických komunikáciách v § 109 ods. 8 vyžaduje súhlas s ukladaním údajov v zariadení návštevníka, pokiaľ nie sú nevyhnutné na fungovanie služby. Web je postavený tak, aby bez súhlasu nepotreboval nič také: verejné stránky nenastavujú žiadnu cookie ani nespúšťajú session a písma sa načítavajú z vlastného servera, nie z Google Fonts.

Google Analytics sa načíta až po kliknutí na „Prijať“. „Prijať“ a „Odmietnuť“ sú na prvej vrstve rovnocenné tlačidlá, voľba sa dá kedykoľvek zmeniť odkazom v pätičke a odvolanie zmaže analytické cookies. Každá voľba sa zapíše na server ako doklad o súhlase: náhodný identifikátor, čas, verzia a voľba, bez IP adresy. Výnimkou je vložená mapa Google v kontakte, ktorá sa načíta hneď; stránka o ochrane osobných údajov to otvorene uvádza.

Redizajn, ktorý sa dá zmerať

V septembri prišiel redizajn s pracovným názvom „Pokojná elegancia“. Predchádzal mu audit starej verzie a výsledky boli triezve: 29 rôznych veľkostí písma, hranice vstupných polí s kontrastom 1,24 : 1, štyri nálezy nástroja axe na automatickú kontrolu prístupnosti a navigácia, ktorá na niektorých šírkach telefónu o pár pixelov pretekala.

Práca mala dve fázy. Prvá bola refaktoring s jediným pravidlom: žiadna viditeľná zmena. Spoločná šablóna, jeden modul kontaktného formulára, farby a veľkosti ako premenné.

Po každom commite sa nová verzia porovnala so starou: HTML výstupy 41 scenárov bajt po bajte na PHP 8.4 aj 8.5 a screenshoty pixel po pixeli. Na konci fázy boli výstupy zhodné, okrem jednej vopred schválenej opravy, a 450 screenshotov (15 stránok × 10 šírok × 3 režimy: bežný, bez JavaScriptu a so zníženým pohybom) malo rozdiel 0 pixelov. Celá sada bežala bez administrátorských práv: PHP vo WebAssembly a Chrome bez okna, ovládaný cez Playwright.

Až potom nasledoval samotný redizajn. Každá zamýšľaná odchýlka sa zapísala vopred a porovnanie ju normalizovalo; všetko ostatné muselo ostať zhodné. Takto sa nedá omylom zmeniť stránka, ktorá sa meniť nemala.

Výsledok: typografická škála s 12 krokmi (audit pred redizajnom napočítal 29 hodnôt), hranice polí s kontrastom nad 3 : 1, nula porušení podľa axe (pravidlá prístupnosti WCAG 2, úrovne A a AA) v 30 behoch na 10 stránkach a žiadne vodorovné pretečenie v rozsahu 320–1440 px, v najrizikovejších pásmach kontrolované po jednom pixeli. Sekcia o poslaní združenia sa na mobile skrátila z približne 1 000 na 450 px a logo v hlavičke telefónu narástlo z 20 × 20 na 36 × 32 px, takže je konečne čitateľné.

Čo sa pokazilo a ako sa to opravilo

Audit pred redizajnom našiel v pôvodnej administrácii chyby, ktoré bežné klikanie neodhalí. Apostrof v nadpise rozbil potvrdzovacie okno pri mazaní, takže článok sa zmazal bez opýtania. Neplatný znak UTF-8 v mene účtu viedol k zápisu prázdneho súboru s účtami. Na PHP 8.5 navyše zastaraná funkcia na prácu s obrázkami vypísala po uložení článku varovanie a presmerovanie sa nevykonalo. Všetky tri chyby boli opravené a overené ešte pred spustením.

Hneď po spustení prišiel bezpečnostný audit živého webu v dvoch kolách, druhé z nich s dvoma nezávislými útočnými revíziami kódu. Priniesol 38 zistení, ale cestu do administrácie bez prihlásenia nenašlo ani jedno kolo. Najpoučnejšie zistenie: zo 16 súbežných pokusov so zlým heslom sa nezablokoval ani jeden, lebo pokus sa započítal až po kontrole hesla. Dnes sa pokus započíta pod zámkom ešte pred ňou. Opravy kódu išli v piatich pull requestoch v ten istý deň.

Posledná chyba, tá s tlačidlom z úvodu, nebola v kóde. Doména po jednom kliknutí mierila priamo na server hostingu a web fungoval ďalej, len bez CDN a jej ochrany. Prezradil to skript, ktorý po každom nasadení kontroluje živý web iba čítaním: neprešla mu kontrola certifikátu. Záznamy sa vrátili hneď nasledujúce ráno. Bez toho skriptu mohol web takto bežať celé mesiace a nikto by si to nevšimol.

Čo si z toho beriem

  • Malé združenie nepotrebuje redakčný systém, potrebuje jeden dobrý formulár. Pri desiatkach článkov mu stačia aj súbory bez databázy, ak je zápis atomický a záloha automatická.
  • Návrh administrácie začína otázkou, kto ju obsluhuje a na akom zariadení. Od toho sa odvíja aj bezpečnosť, napríklad dôveryhodný prehliadač s vlastným počítadlom.
  • Refaktoring bez merania stojí na dojme. Bajtová a pixelová parita mení vetu „myslím, že sa nič nezmenilo“ na dôkaz.
  • Audit sa oplatí dvakrát: na starom kóde pred zmenami a na živom webe po spustení. Každý z nich našiel niečo iné.
  • Kontrola nasadenia, ktorá živý web iba číta, chytí aj chyby mimo kódu.

Tlačidlo „nastaviť DNS“ je v administrácii hostingu dodnes. Skript, ktorý na to pri najbližšom nasadení príde, tiež.