Build Budget
Kontrola budowy domu: etapy, wydatki na tle budżetu, dokumenty i raporty PDF dla banku.
- Rola
- Wszystko: produkt, API, frontend, infrastruktura, CI
- Stack
- Next.js · NestJS · PostgreSQL · Redis
- Hosting
- Vercel (web) · Railway (API)
Problem
Buduję własny dom, a budowa produkuje mnóstwo informacji: telefony do wykonawców, zdjęcia z placu budowy, rachunki, faktury i pozwolenia. Każdy etap ma swój stos, a w praktyce wszystko było rozrzucone po arkuszach, galerii w telefonie i papierowych segregatorach. Chciałem jednego miejsca, w którym przechowam to wszystko, uporządkowane według etapów i obok budżetu, tak żeby znaleźć wszystko w kilka sekund i udostępnić to każdemu, kto bierze udział w budowie.
Moja rola
Wszystko od początku do końca: zakres produktu, API w NestJS, frontend w Next.js, infrastruktura i CI. Zbudowałem go jako kompletny produkt SaaS, a nie demo CRUD-a: z porządnym logowaniem, płatnościami, zadaniami w tle, przechowywaniem plików i aktualizacjami na żywo.
Co zbudowałem
Budżet dla każdego etapu
Budowa dzieli się na etapy, a każdy etap ma własną stronę z wydatkami, dokumentami, dziennikiem i zadaniami. Wydatki są liczone na tle planowanego budżetu, z wykresem podsumowania i ostrzeżeniem przy każdym etapie, który go przekracza.
Dokumenty i dziennik budowy
Rachunki, faktury i pozwolenia trafiają z przeglądarki prosto do S3 przez presigned URL, więc API nie przepuszcza przez siebie ich zawartości. Każdy etap ma dziennik ze zdjęciami z budowy.
Raporty PDF dla banku
Raporty generują się w tle (Redis, BullMQ, PDFKit), więc ciężki raport nigdy nie blokuje zapytania. Użytkownik pobiera go, gdy jest gotowy.
Współpraca na żywo
Projekt można udostępnić z rolą właściciela albo edytora. Socket.io wysyła zmiany etapów i wydatków do wszystkich, którzy mają projekt otwarty.
Subskrypcje
Stripe Checkout z darmowym planem ograniczonym do jednego projektu. Plan zmienia się wyłącznie przez webhooki weryfikowane podpisem.
Przypomnienia i porównanie kosztów
Codzienne zadanie wysyła e-mailem przypomnienia o zadaniach z terminem w ciągu trzech dni. Nocne zadanie liczy anonimowy koszt m² według regionu i etapu, trzyma go w Redisie i pokazuje na publicznej stronie.
Inżynieria
- Monorepo: aplikacja Next.js (App Router), API w NestJS i wspólna paczka, dzięki której zasady haseł są zdefiniowane raz dla obu stron.
- Tokeny JWT (access i refresh) w ciasteczkach httpOnly ustawianych przez route handlery Next.js. Uwierzytelniony proxy po cichu odświeża sesję, a przed pierwszym logowaniem trzeba potwierdzić e-mail.
- Testy jednostkowe z progiem pokrycia 80% dla plików z logiką oraz testy end-to-end, które uruchamiają prawdziwą aplikację na osobnej bazie. CI odpala jedne i drugie oraz buduje obie aplikacje przy każdym pushu, z Postgresem i Redisem jako kontenerami usług.
- Wieloetapowy obraz Dockera na node:20-slim, a nie Alpine, bo silnik zapytań Prismy potrzebuje glibc i OpenSSL. Obraz zawiera tylko API.
- E-maile idą lokalnie przez Mailpit, a na produkcji przez HTTPS API Resend, bo Railway blokuje wychodzący SMTP.
- Interfejs po polsku i angielsku (next-intl).
Kluczowa decyzja
Naprawa wyścigu przy odświeżaniu tokenu
Każdy panel na stronie projektu pobiera własne dane, więc gdy token dostępu wygasa, kilka zapytań próbuje go odświeżyć w tej samej chwili. Przy rotacji przy każdym odświeżeniu pierwsze zapytanie unieważniało token, którego pozostałe jeszcze używały, i użytkownik był wylogowywany w trakcie pracy. Odświeżanie jest teraz idempotentne: refresh token zmienia się tylko na początku nowej sesji (logowanie, potwierdzenie e-maila), więc równoległe odświeżenia kończą się sukcesem na tym samym tokenie.