Vlad Ilie
Lumea BookTruck – catalog, stoc, împrumuturi și încărcări din teren pentru o bibliotecă mobilă
Next.js · Pro-bono · ONG

Lumea BookTruck – catalog, stoc, împrumuturi și încărcări din teren pentru o bibliotecă mobilă

lumea.booktruck.ro ↗
acum o lună
•••
9 min de citit

Context

BookTruck este biblioteca mobilă a asociației Cărțile pe Față, căreia îi întrețin pro-bono infrastructura IT de câțiva ani buni. Mașinile duc cărți copiilor din școlile din mediul rural, așa că în spatele fiecărei ture există un stoc de urmărit, împrumuturi de notat, restituiri de recuperat – și foarte multe poze și filmări.

Lumea BookTruck este aplicația care le ține pe toate laolaltă. Partea publică e un catalog în care oricine poate căuta cărțile care circulă cu BookTruck-urile. Panoul de administrare e locul în care administratorii, bibliotecarii și voluntarii își fac treaba de zi cu zi, de la birou sau de pe telefon, în curtea școlii.

Problema

Erau două lucruri împrăștiate. Catalogul, stocul și împrumuturile către școli stăteau parțial în tabele și parțial în memoria oamenilor: nu era clar câte exemplare dintr-o carte sunt în mașină și câte la sediu, nici câte au rămas la o anumită școală. Iar pozele și filmările de la fiecare tură ajungeau, cum spune chiar asociația, „haotic pe grupul de WhatsApp” – comprimate, nesortate și imposibil de găsit după o lună, deși organizația avea deja spațiu din belșug în Google Drive.

Ce am construit

Permisiunile sunt o matrice pe resurse (cărți, stoc, împrumuturi, restituiri, autori, genuri, școli, locații, statistici, echipă, galerie) pentru trei roluri. Voluntarii consultă catalogul și înregistrează împrumuturi și restituiri. Bibliotecarii gestionează tot catalogul, inclusiv schimbările de stare. Administratorii se ocupă în plus de școli, locații și mutări de stoc, de exportul istoricului, de sesiunile de încărcare și de echipă. Fiecare acțiune e verificată pe server, iar meniul ascunde secțiunile pe care rolul tău nu le poate folosi.

Sub capotă

Decizia interesantă ține de încărcări: fișierele nu trec niciodată prin server. Serverul doar deschide în Google Drive o sesiune de resumable upload cu un cont de serviciu și îi dă browserului adresa ei. Browserul trimite apoi fișierul în bucăți de 8 MB, cu maximum trei fișiere în paralel, ca să nu sufoce conexiunea telefonului. Dacă legătura cade sau se blochează, încărcarea nu o ia de la capăt: browserul așteaptă din ce în ce mai mult între încercări, întreabă Drive-ul cât a primit și continuă de acolo. Orice progres resetează numărul de reîncercări permise, așa că un clip lung trimis pe o conexiune instabilă merge mai departe cât timp avansează. Dacă fișierele ar fi trecut prin server, ne loveam de limita de 4,5 MB pe cerere și de timeout-urile din Vercel Functions. Voluntarii nu primesc niciodată acces la Drive, fiecare sesiune are un plafon de spațiu, iar fiecare fișier încheiat se înregistrează (nume, tip, dimensiune, durată) și alimentează statisticile din panou.

Stocul e ținut pe perechi carte–locație, cu două numere: câte exemplare are locația și câte sunt disponibile acum. Un împrumut scade doar disponibilul, iar o restituire în altă locație mută exemplarele dintr-un stoc în altul. Locațiile sunt referite printr-un identificator care nu se schimbă niciodată, așa că împrumuturile și jurnalele vechi rămân corecte și după ce o locație e redenumită sau retrasă. Calculul pentru mutarea unei locații întregi e o funcție pură, testată separat de baza de date.

Autentificarea a trecut de la Supabase Auth la Better Auth, cu magic link fără parolă și fără înregistrare liberă. Am păstrat toate ID-urile de utilizator și cheile străine existente printr-un backfill aditiv, așa că nu a trebuit remapat nimic. Trimiterea linkurilor e limitată în Redis, pe adresă de e-mail și pe IP, pentru că fiecare e-mail costă credite. Rolul de administrator nu poate fi acordat, schimbat sau preluat din aplicație, ci doar din baza de date. Supabase a rămas doar pentru stocarea coperților și a avatarelor, cu scrieri făcute exclusiv pe server, după verificarea permisiunilor.

Redis mai ține în cache statisticile catalogului, reîmprospătate săptămânal, și topul lunii trecute, generat la prima vizită din luna nouă, fără cron. Tot el poartă semnalele de prezență: câte unul la două minute, ca o echipă mică să rămână lejer în limitele unui plan gratuit. Dacă Redis lipsește, funcțiile care depind de el sunt sărite, iar restul aplicației merge mai departe. Modificările de schemă sunt migrări SQL scrise de mână, iar toate datele calendaristice sunt fixate pe ora României, ca serverul și browserul să afișeze mereu aceeași oră. Regulile de bază – permisiuni, mutări de stoc, invitații, jurnale, exportul istoricului și validarea formularelor de împrumut și restituire – sunt acoperite de teste Vitest.

Rezultat

Un singur link pentru fiecare tură, iar fișierele ajung ordonate în Drive, nu pe un grup de chat. Filmările mari se încarcă de pe telefon chiar și pe conexiuni slabe, fără să atingă – sau să sufoce – serverul. Un împrumut sau o restituire la o școală înseamnă un ISBN scanat și o confirmare, iar stocul pe fiecare mașină și locație e la zi. Bibliotecarii și voluntarii lucrează în același panou, fiecare văzând doar ce îi permite rolul, iar catalogul din care împrumută copiii poate fi, în sfârșit, căutat de oricine.

Tehnologii

Next.js 16 (App Router), React 19, TypeScript, Prisma 7 pe Supabase Postgres, Better Auth, Tailwind CSS 4 cu shadcn/ui, TanStack Table, react-hook-form + zod, Recharts, ZXing, Redis, Google Drive API, Brevo, Supabase Storage, Vitest, Vercel.

Tehnologii:

  • Next.js
  • Prisma
  • Better Auth
  • Google Drive API
  • Supabase
  • Redis
  • Vitest
  • Vercel

Partajează pe: