Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Foi de Parcurs — Fleet Trip-Sheet Management

A full-stack web application for managing "Foi de Parcurs" (Romanian vehicle trip sheets / logbooks) — the legally required documents that fleet operators in Romania use to record each vehicle journey, its driver, mileage, fuel, routes, and associated expenses.

The app lets a company manage its drivers, vehicles, and partners, compose trip sheets with live mileage/fuel calculations, scan expense receipts with AI, and export finished documents to PDF.

Why it exists: trip sheets are still commonly filled in by hand on paper. This app digitises the whole workflow, does the arithmetic for you, and produces a clean printable document — turning a tedious compliance chore into a few clicks.


Live demo

Try it instantly — no signup. One click logs you into a shared, pre-populated demo account and drops you on a realistic dashboard (vehicles, drivers, partners, and a few months of trip sheets):

👉 https://foideparcurs.digitalbunny.eu/demo

The demo resets to a clean state on a schedule, so feel free to click around, edit records, and create new trip sheets. (Credentials, if you prefer the login form: test@example.com / foideparcurs.)


Screenshots

Screenshots are intentionally omitted from this public copy. To add your own, run the app against seeded demo data and drop images in docs/img/, then link them here.


Features

  • Authentication — email/password and Google sign-in via Better-Auth, with cookie-based sessions stored in Postgres. Email verification is mandatory and enforced in two places: a client-side route gate redirects unverified users to /verify-email, and every API request re-checks the verified session server-side (403 otherwise).

  • Per-user data isolation — the user id is always read from the verified session cookie, never from the request body. Every API query is scoped to that user, and updates/deletes run an ownership check before mutating.

  • Drivers, vehicles & partners — full CRUD with active/inactive status and Romanian-specific fields (CUI, ONRC, serie șasiu, număr înmatriculare…). Drivers and vehicles additionally support bulk-delete and duplicate.

  • Trip sheets (foi de parcurs) — a multi-section form that computes values live as you fill it in:

    • mileage: kmTraveled = kmEnd − kmStart
    • fuel: fuelConsumed = fuelInitial + fuelAdded − fuelRemaining
    • multiple routes (legs) and multiple vehicles (tractor + trailer) per sheet.

    The form also auto-saves while editing an existing sheet.

  • Expenses — multi-currency entries (RON, EUR, GBP). Partners are flagged as VAT payers (plătitor TVA), and the expense-settlement (decont cheltuieli) PDF splits amounts into RON and EUR columns.

  • AI receipt scanning — upload a photo of a receipt and Google Gemini (gemini-2.5-flash) extracts the structured expense fields automatically (type, date, merchant, document number, quantity, unit price, amount, currency…).

  • PDF generation — trip sheets and expense settlements are rendered to PDF by a Python / ReportLab backend, invoked as a sandboxed subprocess from the Next.js API with a hard 30-second timeout.

  • Duplicate-document detection — uploaded receipts are SHA-256 hashed in the browser and checked against a per-user hash table, so the same document can't be attached twice.


Tech stack

Layer Technology
Framework Next.js 15 (App Router) · React 18 · TypeScript (strict)
UI Tailwind CSS · shadcn/ui (new-york) · Radix UI · TanStack Table · lucide-react · sonner
Validation Zod — server-side request validation and AI-output validation
Database PostgreSQL via Prisma ORM (typed schema + migrations)
Auth & storage Better-Auth (cookie sessions in Postgres) + self-hosted filesystem storage
AI Google Gemini gemini-2.5-flash (@google/generative-ai)
PDF backend Python 3 + ReportLab
Logging Pino structured logging
Testing Vitest (unit/integration) · Playwright (E2E)
Tooling/CI ESLint · GitHub Actions

Forms are built with plain React state and hand-rolled validation — there is no React Hook Form. Zod is used on the server (API routes and the AI client), not in the client-side forms.


Architecture highlights

  • Off Firebase entirely. The app originally used Firebase (Firestore, Auth, Storage). The data layer was moved to a relational PostgreSQL schema for joins, transactions, and referential integrity; authentication was moved to Better-Auth (sessions in Postgres) and file storage to a self-hosted filesystem volume served through authenticated API routes. No Firebase SDKs remain. User identities preserved their original Firebase UIDs across the migration, so all foreign keys and stored-file paths kept resolving.
  • Clear request flow: React client → Next.js API route (session cookie) → authenticateAndAuthorize middleware (validates the Better-Auth session, requires a verified email, enforces ownership) → Prisma → PostgreSQL.
  • Server-driven identity: the user id always comes from the verified session — never from the request body — so a client cannot act on another user's data or reassign ownership.
  • Document generation is delegated to a sandboxed Python subprocess (data passed as JSON over stdin) with a hard 30-second timeout, keeping PDF rendering out of the Node event loop.

Data model

Fifteen Prisma models back the app: User, CompanySettings, Driver, Vehicle, FoiParcurs (trip sheet), FoiParcursVehicle (vehicle ↔ trip-sheet junction for tractor+trailer), Route, FuelExpense, ConsumptionCalculation, Expense, FileHash (dedup), Partner, plus the Better-Auth tables Session, Account, and Verification. User.id preserves the original Firebase UID; multi-word columns map to snake_case via Prisma @map.


Getting started

Prerequisites

  • Node.js 20 (matches CI)
  • PostgreSQL 14+ (local or remote)
  • Python 3 with ReportLab (pip install -r requirements.txt) for PDF export
  • An SMTP server (for email verification) and, optionally, a Google OAuth client (for Google sign-in) and a Google Gemini API key

Setup

# 1. Install dependencies
npm install

# 2. Configure environment
cp .env.example .env.local
#    → set BETTER_AUTH_SECRET/URL, DATABASE_URL, STORAGE_DIR, SMTP_*,
#      and (optional) GOOGLE_CLIENT_ID/SECRET and GEMINI_API_KEY

# 3. Set up the database
npx prisma generate
npx prisma migrate deploy

# 4. Run the dev server (http://localhost:3100)
npm run dev

Every configuration value is documented in .env.example. BETTER_AUTH_SECRET, BETTER_AUTH_URL, and DATABASE_URL are required; STORAGE_DIR defaults to ./storage. SMTP is required in production for email verification (without it, verification links are only logged to the server console). The Gemini key is optional — only the receipt-scanning endpoint needs it, and it returns 503 when the key is unset. Migrating from a prior Firebase deployment? See FIREBASE_MIGRATION.md.


Testing

npm run test            # unit/integration tests (Vitest)
npm run test:coverage   # coverage report (needs @vitest/coverage-v8)
npm run test:e2e        # end-to-end tests (Playwright, all projects)
npm run test:e2e:chromium   # E2E, Chromium only (used in CI)
npm run type-check      # TypeScript, no emit
npm run lint            # ESLint (zero warnings)
npm run test:all        # lint + type-check + unit + E2E (chromium)

Unit tests run fully offline (no external services). Playwright covers Chromium, Firefox, WebKit, and mobile (Pixel 5 / iPhone 12) projects. See TESTING.md for the complete testing guide and LOGGING.md for the logging setup.


Project structure

src/
├── app/                # Next.js App Router
│   ├── api/            # API routes (auth-guarded CRUD + PDF/AI endpoints)
│   └── dashboard/      # Protected pages (foi-parcurs, soferi, vehicule, partners, …)
├── auth/               # Auth context provider (Better-Auth session)
├── components/         # UI components (shadcn/ui + feature components)
├── hooks/              # Data-fetching hooks
├── lib/                # Better-Auth config + middleware, prisma client, storage, password hashing, env, gemini, logger
├── backend/            # Python (ReportLab) PDF generators + DejaVu fonts
└── types/              # Shared TypeScript types
prisma/                 # Prisma schema + migrations (9 migrations)
e2e/                    # Playwright specs
scripts/                # Firebase→Better-Auth migration + test-seed scripts

Security notes

Security was a first-class concern in this project:

  • AuthZ on every endpointsrc/lib/auth-middleware.ts validates the Better-Auth session, requires a verified email, and checks resource ownership. The only unauthenticated route is /api/health.
  • Locked-down storagesrc/lib/storage.ts scopes every file to users/{uid}/… (a canonical-path + ownership check, reproducing the old Firebase storage rules in code); a user can only read/write their own files, with a path-traversal guard and an 8 MB upload cap.
  • Input validation — write endpoints validate request bodies with Zod before touching the database and never spread raw client input into queries.
  • No outbound image fetch — the AI receipt endpoint reads the image bytes directly from local storage by its owned storageRef (no URL fetch, so no SSRF surface), with an 8 MB size cap.
  • No raw SQL — all database access goes through Prisma's parameterised query builder.
  • No secrets in the repo — all credentials are read from environment variables; only .env.example is tracked.

License

This repository is published for review and demonstration only. It is not open source — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages