Rolagzza 2026-10-05: My Recipes: recipe import screen from the repository store assets

Actualizare: 5 octombrie 2026

Portofoliul actual prezintă My Recipes ca lansat pe iOS și Android, cu planificarea meselor, liste de cumpărături și import AI pe server. Notele de mai jos despre MVP-ul local doar pentru Android documentează etapa din mai 2026, nu versiunea actuală.

My Recipes: rețete importate și planificare offline

Note tehnice originale din mai 2026

Rețetele Mele e un organizator de rețete pentru cei care gătesc acasă și își țin preferatele împrăștiate prin zece bloguri culinare, un canal de YouTube și câteva capturi de ecran pe care nu le mai găsesc niciodată. Pui un link, aplicația scoate rețeta propriu-zisă — titlu, ingrediente, pași — și o salvează într-o colecție pe care o cauți offline. Fără retastat. Asta e toată ideea, și e o corvoadă pe care m-am săturat să o fac de mână.

Aplicația e nativă pe Android, cu un server Django în spate. E un MVP: funcționează local și încă nu am deployat-o. Scriu despre ea acum, cât timp pot fi sincer despre ce merge și ce e încă doar pe hârtie, fiindcă partea interesantă sunt deciziile de inginerie, nu un anunț de lansare.

Import dintr-un singur tap: o cascadă, nu un parser

Funcția centrală e importul. Dai aplicației un URL, iar un worker Celery de pe backend se duce și descarcă pagina. Apoi încearcă să extragă rețeta printr-o cascadă, cu strategia cea mai ieftină și mai sigură prima:

  1. JSON-LD. Multe site-uri de rețete publică deja date structurate Recipe într-un bloc <script type="application/ld+json">, fiindcă pe asta se bazează rich results-urile din Google. Dacă există, parsarea e rapidă, gratis și exactă. O citesc cu extruct.
  2. Extracție din HTML. Dacă nu sunt date structurate, trec pe varianta de a scoate conținutul principal din pagină cu trafilatura, care e bun la curățat meniuri, reclame și mizeria din comentarii până rămâne corpul articolului.
  3. Extracție cu AI. Dacă pagina e tot un haos — și multe bloguri culinare sunt 1.200 de cuvinte de amintiri din copilărie înfășurate în jurul a șase ingrediente — textul curățat ajunge la model, care întoarce o rețetă structurată.

Rostul cascadei e reziliența. Fiecare nivel rulează doar când cel de deasupra eșuează, așa că în cazul comun (un blog cu JSON-LD ca lumea) nu costă nimic, iar cazul greu se rezolvă oricum. Importul se încadrează de obicei între 2 și 10 secunde, în funcție de care nivel face treaba. Detaliez pattern-ul ăsta cu strategii multiple în articolul despre cum țin cheile AI departe de telefon; pe scurt, „date structurate, apoi AI ca rezervă" e un tipar la care ajung tot mai des.

O precizare despre funcția-titlu. README-ul promite import din web, YouTube, TikTok și Instagram, iar YouTube intră curat în cascadă. Vreau să fiu corect cu video-ul social: calea documentată și funcțională sunt URL-urile generice și YouTube. TikTok și Instagram sunt pe roadmap și apar în marketing, dar ar fi o exagerare să spun că sunt complet acolo azi. Cascada e gândită să le absoarbă — e un pas de fetch plus un pas de extracție — dar „gândit să" și „livrat" sunt două afirmații diferite.

Nutriție pe care n-a trebuit s-o tastezi

După ce o rețetă e salvată, aplicația poate să-i estimeze valorile nutriționale. Asta e un job asincron separat care trimite ingredientele la Google Gemini și primește înapoi nutriția pe rețetă. E o estimare, nu o analiză de laborator, și o tratez ca atare — Gemini citește „două linguri de ulei de măsline" și face socoteala pe care ar face-o un om, doar mai repede.

E important că rulează ca job de fundal. Estimarea nutrițională nu e pe calea critică a salvării unei rețete; întâi salvezi, valoarea apare după. Decuplarea ține salvarea instantanee și împiedică un model lent sau limitat de rate să blocheze chiar lucrul pe care l-a cerut utilizatorul.

Offline-first, apoi sincronizare

Aplicația e offline-first prin design. Fiecare scriere ajunge imediat într-o bază de date locală Room, așa că salvarea unei rețete, adăugarea ei într-un album sau bifarea unui produs din lista de cumpărături e instantanee și merge fără conexiune. Apoi un job de fundal WorkManager urcă schimbările pe server când apare rețeaua.

Rezolvarea conflictelor e last-write-wins, pe server. Două dispozitive care editează aceeași rețetă e ceva rar pentru colecția unui singur utilizator, iar LWW e alegerea pragmatică — simplă, previzibilă și suficient de bună când rata reală de conflicte e mică. Aș alege altceva pentru date cu adevărat colaborative, dar o cutie personală de rețete nu e așa ceva.

Capcana care merită semnalată — fiindcă m-a prins pe mine și îi prinde pe toți cei care construiesc așa ceva — sunt ștergerile. Dacă „șters" înseamnă „rândul nu mai există local", sincronizarea n-are cum să distingă între un rând șters și unul care n-a fost niciodată sincronizat, iar un last-write-wins naiv îți va învia voios o rețetă pe care ai aruncat-o: dispozitivul A o șterge, dispozitivul B o are încă, copia lui B câștigă la următoarea sincronizare și gata, a revenit. Soluția sunt tombstone-urile: o ștergere e un soft-delete, un rând marcat care supraviețuiește destul cât să se propage, nu o eliminare reală. Sincronizarea duce atunci ștergerea ca dată. Am scris despre asta și restul tiparelor în analiza despre sincronizarea offline-first — problema cu tombstone-urile e partea despre care mi-aș fi dorit să mă fi avertizat cineva mai devreme.

Dincolo de import, partea de colecție are albume, o listă de cumpărături și un planificator de mese — stratul de organizare care transformă un morman de linkuri salvate în ceva din care chiar ai găti.

Cheile rămân pe server

Fiecare apel AI și fiecare secret stau doar pe server. Aplicația Android nu ține niciodată o cheie Gemini sau o credențială YouTube; ea vorbește cu API-ul meu Django, iar API-ul vorbește cu providerii. Autentificarea e email plus JWT cu rotație de refresh token, cu Google Sign-In funcțional și Apple Sign-In doar schițat (întoarce deocamdată un 501).

Partea de admin e cea mai interesantă, și e preferata mea din tot proiectul. Rulează pe django-unfold cu metrici live, cote per utilizator și posibilitatea de a re-enqueue job-urile eșuate. Prompturile AI se pot edita din admin, dar sunt imuabile și versionate: editarea unui prompt creează o versiune nouă în loc să o suprascrie pe cea veche, iar fiecare versiune își loghează costul și latența. Așa pot compara două versiuni de prompt după cât costă cu adevărat și cât de repede au rulat, și pot reveni dacă un prompt „mai bun" se dovedește mai prost sau mai scump. Prompturile sunt configurație, nu cod pe care trebuie să-l redeployez, și au un istoric pe care îl pot judeca.

Stack-ul

Backend: Python 3.13, Django 5.1 și Django REST Framework, cu Celery 5.4 pentru job-urile asincrone de import și nutriție. Datele stau în Postgres 17 în spatele lui PgBouncer, cu Redis ca broker Celery și cache. Extracția folosește httpx pentru fetch și trafilatura/extruct pentru nivelurile JSON-LD și HTML; AI-ul e Gemini prin google-genai. Observabilitatea e Sentry plus Prometheus, și totul rulează pe Docker Compose, țintind api.retetelemele.ro.

Android: Kotlin 2.1 cu Jetpack Compose pentru UI, Hilt pentru injecția de dependențe, Room v3 pentru stocarea offline, Retrofit/OkHttp pentru networking, Coroutines și Flow pentru asincron, și WorkManager pentru sincronizarea de fundal.

Unde e acum și ce urmează

Status sincer: e un MVP care funcționează local și nu e deployat. Zece faze sunt livrate, backendul are 87 de teste care trec, și există un build Android funcțional (versionCode 1, versiunea 0.1.0). Faza de infrastructură de producție e amânată explicit și încă neimplementată — aplicația țintește api.retetelemele.ro, dar asta e o destinație, nu o adresă vie. Nu are încă licență, iar iOS și Web sunt doar planificate.

Ce urmează e lista neglamuroasă: ridicarea infrastructurii de producție, deploy-ul propriu-zis și consolidarea importului de video social, ca TikTok și Instagram să treacă de pe roadmap în realitate. Mi-aș dori și mai multă încredere în cazurile limită de sincronizare — ferestrele de retenție a tombstone-urilor, sincronizările parțiale pe conexiuni instabile — înainte să o consider gata.

E unul dintre mai multe produse pe care le construiesc de unul singur; dacă vrei contextul mai larg, uite cum livrez 11 produse ca fondator solo. Iar dacă te lupți cu importul de rețete sau cu sincronizarea offline, trimit din când în când notițe de build — te poți abona la newsletter și urmări cum Rețetele Mele trece de la MVP local la deployat.

AndroidDjangoCeleryoffline-firstGeminiKotlin
Distribuie articolul