Skip to content

Architecture

Luara Oliveira edited this page Jul 3, 2026 · 7 revisions

Architecture

Overview

Felines is a full-stack web application built on Next.js 14 with Supabase as the complete backend. The deploy runs on Netlify with continuous deployment via GitHub.


System Diagram

Browser / Mobile Web ↓ Next.js 14 (Netlify) App Router · SSR · TypeScript ↓ Supabase (PostgreSQL) Auth · Storage · RLS · RPCs ↓ External APIs OpenWeatherMap · Nominatim (OSM)Map Layer Leaflet.js · OpenStreetMap tiles


Tech Stack

Layer Technology Why
Frontend Next.js 14 + TypeScript App Router, SSR, performance
Styling Tailwind CSS Speed, consistency, no custom CSS
Map Leaflet.js + OpenStreetMap Open source, no API key, free
Database Supabase (PostgreSQL) SQL, native RLS, integrated auth
Auth Supabase Auth Built-in, robust, secure
Storage Supabase Storage Per-user RLS, public read for photos
Deploy Netlify Auto CD via GitHub, free HTTPS
Weather OpenWeatherMap API Free tier sufficient
Geocoding Nominatim (OSM) Free, no API key required
i18n Custom LanguageContext No routing complexity, localStorage

Folder Structure

felines/ ├── app/ # Next.js App Router (19 routes) │ ├── / # Educational home │ ├── map/ # Interactive colony map │ ├── colony/[id]/ # Colony detail (6 tabs) │ ├── colony/new/ # Register a colony │ ├── learn/ # Educational guide index │ ├── learn/[slug]/ # Individual article (18 articles) │ ├── help/ # Emergency help flow │ ├── reports/ # Community reports + resources │ ├── impact/ # Public impact statistics │ ├── stories/ # Community stories wall │ ├── glossary/ # Interactive glossary (36 terms) │ ├── plants/ # Toxic plants guide │ ├── contacts/ # Contact directory │ ├── curso/ # Caretaker course │ ├── profile/ # User profile │ ├── u/[id]/ # Public caretaker profile │ ├── login/ # Login │ └── signup/ # Sign up ├── components/ # Reusable React components ├── hooks/ │ ├── useFelinesAssistant.ts # Cat assistant triggers │ ├── useCaretakerAccess.ts # Caretaker validation │ └── useLanguage.ts # PT/EN context ├── lib/ │ ├── articles.ts # 18 educational articles │ ├── catCuriosities.ts # 25 curiosities for assistant │ ├── storage.ts # Safe upload (anti path traversal) │ ├── geocode.ts # Validated geocoding (anti SSRF) │ ├── validateCoordinates.ts # Geographic range validation │ └── i18n/ │ ├── pt.ts # Portuguese translations │ └── en.ts # English translations ├── supabase/ │ └── migrations/ # 75 SQL migrations ├── public/ │ └── videos/ # Cat assistant animation ├── AUDIT_REPORT.md # Aikido Security audit results ├── SECURITY.md # Security policy └── CONTRIBUTING.md # Contribution guide


Data Flow

Anonymous user viewing a colony

User opens /map → Leaflet renders map centered on user location → Supabase query returns colonies with BLURRED coordinates (latitude_blurred, longitude_blurred — RLS allows public read) → User clicks a pin → /colony/[id] loads colony data (public fields only) → Exact coordinates never returned by any public query

Authenticated caretaker accessing exact location

User is linked as caretaker → Frontend calls RPC get_colony_exact_location(colony_id) → RPC validates: is the caller a caretaker of this colony? → If yes: returns exact lat/lng → If no: returns permission denied → Validation happens server-side — cannot be bypassed by client

File upload flow

User selects photo → Client validates: type (jpeg/png/webp/gif) + size (max 5MB) → buildSafeStoragePath() generates safe path: folder sanitized + UUID filename (no user input in path) → Upload to Supabase Storage → RLS: only authenticated users can upload → Public read: photos are intentionally public


Key Architecture Decisions

Why Supabase over Firebase? PostgreSQL with native Row Level Security that can be audited. SQL is standard and portable. Storage is integrated. Visual table editor makes field research data entry easy.

Why Leaflet.js over Mapbox or Google Maps? Open source, no API key, no cost, no request limits. OpenStreetMap tiles are reliable and community-maintained.

Why custom i18n over next-intl? No URL-based language routing (/pt/ vs /en/). Preference stored in localStorage. Simple LanguageContext with two translation files. Less dependency, same result.

Why progressive blur instead of hiding coordinates entirely? Users need geographic context for the product to make sense. Hiding coordinates completely would break the map experience. Progressive blur balances utility and animal safety at each trust level.

Clone this wiki locally