Case study

Smakowity Plan

PWA, która układa rodzinny jadłospis na tydzień, generuje przepisy przez sztuczną inteligencję i składa z nich listę zakupów pogrupowaną po kategoriach.

2025–2026 projekt i wykonanie wersja 2.0.0 dostęp na życzenie

Poproś o dostęp

Kontekst

Mamy dwoje dzieci. Żona codziennie wymyślała, co ugotować — nie dlatego, że nie umie, tylko dlatego, że to praca, której nikt nie widzi i która nigdy się nie kończy. Aplikacja powstała, żeby ją z tego zdjąć: tydzień posiłków, przepisy i lista zakupów w jednym kroku, zamiast codziennego „co dziś na obiad”.

Co zbudowałem

  • Generowanie tygodniowego jadłospisu przez model językowy, z uwzględnieniem liczby domowników i preferencji.
  • Przepis pod każdym posiłkiem: makroskładniki, składniki z gramaturą, kroki przygotowania.
  • Listę zakupów składaną automatycznie z całego tygodnia i grupowaną po kategoriach sklepu.
  • Historię zapisanych jadłospisów z ulubionymi.
  • Panel administracyjny z analityką użycia i audytem.

Sedno

Decyzje techniczne

Dlaczego PIN, a nie hasła?

To aplikacja dla domowników, nie dla internetu — baza użytkowników jest zamknięta, rejestracji nie ma. Przy takim założeniu hasło jest kosztem bez zysku: trzeba je wymyślić, zapamiętać, resetować. PIN wpisuje się jedną ręką, przy garnku.

„Na ten moment wystarczy” jest tu decyzją, nie przeoczeniem — i dlatego reszta jest zbudowana tak, żeby ta decyzja dała się cofnąć: PIN-y żyją w users.json, nie w kodzie, a sesja stoi na tokenie podpisanym HMAC-iem. Wymiana samego mechanizmu logowania nie wymaga ruszania aplikacji.

Dlaczego limit zapytań jest per-endpoint, a nie globalny?

Licznik rozdzielony na kubełki (gemini, auth) rozwiązuje konkretny problem: bez tego pięć generacji jadłospisu wyczerpywałoby limit i blokowało logowanie domownikom. Limit ma chronić klucz API, nie zamykać drzwi rodzinie.

Dlaczego limiter przepuszcza żądanie, gdy nie może zapisać licznika?

Fail-open. Gdy magazyn liczników jest niedostępny, żądanie przechodzi — lepiej wpuścić nadmiarowe zapytanie niż odciąć domownika od aplikacji przez problem z dyskiem. Przy limicie chroniącym budżet, a nie dane, to właściwy kompromis.

Dlaczego porównanie PIN-u nie przerywa pętli po trafieniu?

Pętla przechodzi przez wszystkie PIN-y i używa hash_equals() zamiast ===. Bez tego czas odpowiedzi zdradzałby, ile PIN-ów sprawdzono, zanim trafiono — a przy czterech cyfrach każda wskazówka skraca atak.

Dlaczego klucz do API modelu nigdy nie trafia do przeglądarki?

Zapytania idą przez własny endpoint PHP, który dokłada klucz po stronie serwera. Aplikacja może wskazać model, ale tylko z białej listy walidowanej serwerowo — klient nie decyduje, do czego sięga mój klucz.

Dlaczego plik z kluczem ma strażnika, skoro blokuje go .htaccess?

Obrona w głąb. config.php przerywa wykonanie, jeśli nie został dołączony przez aplikację — działa nawet gdyby reguła Apache'a zniknęła przy migracji hostingu albo ktoś nadpisał .htaccess. Dwie niezależne warstwy, bo pierwsza kiedyś padnie.

Jak to wygląda

Stack

  • Vite
  • PWA / Workbox
  • React
  • PHP 8
  • Gemini 2.5 / 3.5 Flash
  • react-pdf
  • Apache / OVH

Aplikacja jest zamknięta PIN-em — chroni klucz API i dane domowników. Chętnie założę dostęp, jeśli chcesz zobaczyć ją w działaniu.

Poproś o dostęp

Na górę strony