← Späť na blog

Vlastná appka na archiváciu faktúr a dokladov — a moment tesne pred zverejnením, keď takmer unikli reálne dáta

18. júla 2026 · 6 min čítania

self-hostedbezpečnosť

Appka na to, aby sa dôležitý papier (faktúra, zmluva, rodný list) dal nájsť za pár sekúnd — nie preto, že by to bola zábavka, ale preto, že "kde mám tú poistku" je otázka, ktorú si kladie každá domácnosť.

Prečo vlastná appka, nie hotové riešenie

Cieľ: fotka z telefónu, mailová príloha alebo nahraný PDF sa automaticky spracuje — vytiahne sa z neho hlavný identifikátor (firma pri faktúre, meno pri rodnom liste), typ dokumentu, dátum, suma, krátke zhrnutie — a súbor sa uloží tak, aby sa dal neskôr nájsť jednoduchým hľadaním ("uniqa" → všetko od poisťovne, meno dieťaťa → jeho rodný list). Nielen faktúry a zmluvy — čokoľvek dôležité, čo si niekedy v budúcnosti bude treba rýchlo dohľadať.

Existujúce hotové riešenia (napr. paperless-ngx) robia podobnú vec cez OCR a fulltextové hľadanie. Rozdiel bol vo zvolenom prístupe k extrakcii metadát: namiesto len OCR textu appka posiela dokument na skutočnú AI extrakciu (rozpozná typ dokumentu, vytiahne kľúčové polia, urobí zhrnutie) — s viacvrstvovým fallbackom medzi AI providermi, aby jeden výpadok nezastavil celý archív.

Architektúra požičaná, nie vymyslená odznova

Namiesto navrhovania architektúry od nuly appka prevzala overenú konvenciu z iného, už existujúceho projektu v tom istom homelabe: FastAPI backend, React+Vite frontend, session-cookie autentifikácia s CSRF ochranou namiesto JWT. Odchýlky od tejto konvencie boli vedomé a zdôvodnené: SQLite s FTS5 rozšírením namiesto plochých JSON súborov (potrebné pre skutočné fulltextové hľadanie naprieč archívom), a jeden proces servírujúci aj frontend bez samostatného reverzného proxy kontajnera — kvôli požiadavke, aby appka vedela bežať cez Docker aj cez systemd na hocijakom Linuxe, nie len na tomto konkrétnom stroji.

Tichá chyba, ktorá strácala reálnu poštu

Appka mala sledovať schránku a automaticky archivovať mailové prílohy. Test s reálnym mailom (preposlaná výplatná páska) sa v appke vôbec neobjavil.

Vyšetrenie ukázalo dve nezávislé veci naraz. Po prvé, pôvodný mechanizmus sledovania pošty hľadal len neprečítané správy (SEARCH UNSEEN) — daná správa už mala príznak "prečítané" nastavený (pravdepodobne nejakým serverovým skenovaním na pozadí, presná príčina sa nepodarila s istotou určiť), takže appka ju jednoducho nikdy nevidela. Krehký mechanizmus bez ohľadu na presnú príčinu — riešenie bolo sledovať vlastnú pozíciu (UID vodoznak, "spracované po poradové číslo X"), nezávisle od toho, čo s príznakom "prečítané" robí čokoľvek iné na serveri.

Po druhé, a možno prekvapivejšie: appka mala nastavené logovanie, ale nikde v kóde nebola inicializovaná samotná konfigurácia logovacieho systému — čo v praxi znamenalo, že všetky informačné logy v celej appke boli ticho zahadzované defaultnou úrovňou logovania (len varovania a vyššie). Mesiace behu appky bez jedinej viditeľnej informačnej správy v logoch, hoci appka logovanie zjavne používala. Jeden riadok inicializácie to opravil naraz pre celú appku.

(A ako drobná bodka na koniec: skutočný testovací mail napokon prílohu vôbec neobsahoval — mailový klient ju pri preposielaní potichu vynechal. Appka sa teda v tomto jednom konkrétnom prípade správala presne správne, len sa to prekrylo s dvoma skutočnými bugmi objavenými popri tom.)

Duplicity, ktoré sa nikdy neupratali

Pri testovaní sledovaného priečinka (skopírovanie už archivovaného súboru naspäť do vstupného priečinka) appka správne rozpoznala duplicitu — no správanie záviselo od toho, odkiaľ dokument prišiel. Pri nahratí cez appku alebo mail duplicita nevadila, lebo volajúci kód si zdrojový súbor beztak upratal sám. Pri sledovanom priečinku ale nikto súbor neupratal — appka len tíško skončila bez toho, aby sa čo i len pohla so zdrojovým súborom, ktorý by tak navždy zostal ležať vo vstupnom priečinku a pri každom ďalšom prehľadaní priečinka by sa znova a znova vyhodnotil ako duplicita.

Riešenie: duplicitné súbory sa presúvajú do samostatného priečinka namiesto toho, aby zostávali na mieste — vstupný priečinok ostáva čistý, a nič sa už neposiela na AI extrakciu opakovane zbytočne.

Testy, ktoré predtým neexistovali

Súčasťou prípravy na zverejnenie bola aj prvá poriadna sada automatizovaných testov — predtým appka žiadne nemala. Osemnásť testov bežiacich offline za necelé dve sekundy (žiadne skutočné volania na AI providerov, nahradené falošným providerom pre testovacie účely), pokrývajúcich prihlasovací súhlas, správne rozlišovanie dočasného výpadku providera od trvalej chyby extrakcie, fulltextové hľadanie, GDPR mazanie súboru z disku, CSRF ochranu, aj prechody stavu pri kontrole dokumentov. Súčasťou bol aj čestný dokument popisujúci, čo tieto testy nepokrývajú — frontend, skutočné AI providery, procesy bežiace na pozadí — namiesto tvárenia sa, že je pokryté všetko.

Moment tesne pred zverejnením, ktorý stojí za to si zapamätať

Pri príprave appky na verejné GitHub repo vznikla séria demo dokumentov (fiktívne firmy, fiktívne mená) a k nim screenshoty pre README — appka totiž predtým bežala len s reálnymi rodinnými dokumentmi.

Pri kontrole pred publikovaním sa ukázalo, že jeden zo screenshotov (stránka Nastavenia) obsahoval viac, než mal: reálnu IP adresu domáceho mailového servera, reálnu mailovú adresu, na ktorú appka prijíma faktúry, a reálne Telegram chat ID pre notifikácie. Nič z toho nebolo heslo ani prístupový token, ale všetko to boli reálne, konkrétne detaily o súkromnej infraštruktúre, ktoré nemali dôvod byť verejne vo fotke v README súbore.

Zachytené a opravené ešte pred publikovaním — nastavenia sa na čas screenshotu dočasne nahradili demo hodnotami, odfotilo sa, a hneď potom sa vrátili späť na reálne (s overením, že notifikácie po vrátení naozaj stále fungujú). Ako doplnkový, nižšie závažný nález sa pri neskoršom prehľadaní git histórie našla ešte jedna reálna mailová adresa — v starom príklade v inštalačnej dokumentácii a v jednej správe commitu. Vyriešené prepísaním histórie (nástroj na prepis histórie repozitára, spustený dvakrát — raz na obsah súborov, raz samostatne na správy commitov, keďže jeden beh sám osebe nezachytí oboje), s plnou zálohou pôvodného repozitára uloženou len lokálne pred akýmkoľvek zásahom.

Poučenie z tohto momentu je jednoduché, ale ľahko sa naň zabúda: príprava na zverejnenie nie je len o kóde a dokumentácii — je aj o každom obrázku, ktorý k tomu pribalíš. Screenshot appky bežiacej na reálnych dátach je presne to miesto, kde citlivý detail najľahšie unikne, lebo sa nekontroluje s rovnakou pozornosťou ako zdrojový kód.

Posledný krok: vyčistiť vývojové dáta a začať naozaj

Po zverejnení repozitára prišiel posledný krok — vyčistiť appku od mesiacov nahromadených testovacích a vývojových dokumentov (zmes reálnych aj testovacích súborov) a začať ju používať naostro. Rozsah čistenia bol vedome úzky: zmazali sa len samotné dokumenty, nie mailové/Telegram nastavenia (tie už boli funkčné a nedávalo zmysel ich nastavovať odznova) ani používateľský účet. Záloha pred zásahom vytvorená ako prvý krok, nie ako dodatočná poistka.

Čo si z toho odniesť

  • Prevzatá architektonická konvencia šetrí čas — odchýlky od nej si zaslúžia vlastné zdôvodnenie, nie tiché improvizovanie tam, kde sa to práve hodí.
  • Filter na "neprečítané" je krehký spôsob, ako sledovať nové dáta, ak čokoľvek iné v systéme môže ten istý príznak zmeniť skôr, než ho appka stihne uvidieť. Vlastný, appke patriaci ukazovateľ pozície je odolnejší.
  • Chýbajúca inicializácia logovania je jeden z najtichších bugov, aké appka môže mať — nevyhodí chybu, len ticho zmizne presne tá informácia, ktorá by pri diagnostike najviac pomohla.
  • Duplicita sa nespráva rovnako naprieč všetkými vstupnými cestami, ak si to niekto výslovne neoverí — čo je bezpečné pri jednej ceste, môže sa hromadiť donekonečna pri inej.
  • Screenshot je súčasť povrchu, ktorý treba pred zverejnením skontrolovať rovnako dôkladne ako kód. Je to presne to miesto, kde reálne dáta najľahšie a najnenápadnejšie unikajú von.

Appka dnes beží naostro, na reálnych rodinných dokumentoch — a vďaka poslednému kolu kontroly, bez toho, aby cestou k zverejneniu unikol čo i len jeden detail navyše.

Podobnú disciplínu pred zverejnením prešiel aj Sindri (katalóg skriptov) — a rovnaký princíp "over to naozaj" naprieč celým vývojom drží aj MidgardOps.