Skip to content

Repository files navigation

Status: Live Next.js 16 Supabase Mapbox WHO Data

🌍 PulseMap

Global disease surveillance, visualized like a weather radar.

Track outbreaks. Monitor spread patterns. WHO Disease Outbreak News on a 6-hour Vercel Cron cycle when you run it against your own Supabase project. The public demo serves the cached static snapshot.

β†’ Live Demo


Why PulseMap Exists

Disease outbreaks don't announce themselves neatly. Data is scattered across WHO bulletins, news wires, and government reports. PulseMap consolidates it into a single dark-mode map where outbreak density glows like radar returns β€” green for low concern, red for critical.

This isn't a wrapper around an API. It's a full data pipeline: ingestion from WHO Disease Outbreak News, geocoding, severity estimation, deduplication, and a live-updating frontend that makes the data feel urgent.

What You See

Feature Detail
Heat map layer Outbreak intensity rendered as weighted heatmap β€” green β†’ yellow β†’ red gradient by severity score
Pulsing hotspot markers Log-scaled by case count, color-coded by severity, animated CSS pulse rings
Click-to-detail panels Disease name, case count, severity classification, WHO summary text
Stats bar Aggregate dashboard strip β€” outbreak count, total cases, countries affected, critical alerts, severity distribution, top disease
Live feed Chronological outbreak reports with source badges β€” scrollable, searchable
Search Filter map + feed by disease name or country
Timeline slider Draggable temporal filter with sparkline density visualization β€” scrub through outbreak history, all layers + feed + stats update in real-time
Spread network Animated great-circle arcs connecting outbreak locations of the same disease β€” glowing lines with flowing dash animation, color-coded by severity
Layer toggles Independent visibility controls for heatmap, hotspot, and spread layers
Data source indicator Real-time badge showing whether you're seeing live Supabase data or static fallback
Fly-to navigation Click a feed item β†’ map smoothly flies to the outbreak location

Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚     Vercel Edge Network           β”‚
                    β”‚     (Next.js 16 App Router)       β”‚
                    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                    β”‚  Mapbox  β”‚  React UI β”‚  API Layer β”‚
                    β”‚  GL JS   β”‚  8 comps  β”‚  2 routes  β”‚
                    β”‚  dark-v11β”‚  client   β”‚  server    β”‚
                    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                    β”‚         Supabase (Postgres)       β”‚
                    β”‚    RLS policies Β· Realtime-ready  β”‚
                    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
  Every 6h ───────▢│  WHO Disease Outbreak News API    β”‚
  POST /backfill ─▢│  Geocoding (Mapbox + static)      β”‚
                    β”‚  Deduplication Β· Severity scoring  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data flow: WHO API β†’ fetch-outbreaks.ts pipeline β†’ geocode country/region β†’ estimate severity β†’ upsert to Supabase β†’ GeoJSON served to Mapbox GL heatmap + marker layers.

Tech Stack

Layer Choice Rationale
Framework Next.js 16 (App Router) SSR + server routes + Vercel-native cron
UI React 19 + TypeScript Strict typing across all 8 components
Styling Tailwind CSS 4 Custom dark theme, no default palette
Map Mapbox GL JS Heatmap layers, smooth fly-to, dark basemap
Database Supabase (Postgres) Row-level security, realtime subscriptions
Data Source WHO DON API Structured, authoritative, global coverage
Geocoding Mapbox Geocoding + static lookup Hybrid: API for precision, static for speed
Testing Vitest 64 unit tests across data ingestion, WHO parser, spread arcs, JSON validation
Hosting Vercel Auto-deploy, cron scheduling, edge CDN

Getting Started

Prerequisites

Install

git clone https://github.com/DareDev256/pulsemap.git
cd pulsemap
npm install

Configure

Create .env.local from the example:

cp .env.local.example .env.local

Required variables:

NEXT_PUBLIC_MAPBOX_TOKEN=     # Mapbox GL access token
NEXT_PUBLIC_SUPABASE_URL=     # Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=# Supabase anon/public key
SUPABASE_SERVICE_ROLE_KEY=    # Supabase service role (server-side only)
CRON_SECRET=                  # Auth token for cron + backfill endpoints

Database Setup

Run the migration in your Supabase SQL editor:

# Copy contents of supabase/migration-001.sql into Supabase SQL Editor β†’ Run

Ingest Data

Outbreak records come from WHO Disease Outbreak News. When Supabase is unreachable the UI serves src/lib/seed-data.ts, a static snapshot, and the data-source badge in the header tells you which of the two you are looking at. The public demo is currently on that snapshot.

# Pull current outbreaks
curl http://localhost:3000/api/cron/update-outbreaks \
  -H "Authorization: Bearer $CRON_SECRET"

# Backfill a date range
curl -X POST http://localhost:3000/api/backfill \
  -H "Authorization: Bearer $CRON_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"startDate":"2024-01-01","endDate":"2024-12-31","source":"who","limit":200}'

Backfill API Parameters

Parameter Type Required Description
startDate string Yes Range start (YYYY-MM-DD)
endDate string Yes Range end (YYYY-MM-DD)
source string No who or all (default: who)
limit number No Max reports (default: 200, max: 500)

Run

npm run dev          # Development server
npm run build        # Production build
npm test             # Run test suite (64 tests)
npm run test:watch   # Watch mode

Security

Response headers (all routes):

  • Content Security Policy -- script/connect/style origins allowlisted per-service (Mapbox, Supabase, WHO, Google Fonts). Everything else blocked.
  • X-Frame-Options: DENY + frame-ancestors 'none' -- prevents clickjacking
  • HSTS -- enforces HTTPS for 1 year including subdomains
  • X-Content-Type-Options / Referrer-Policy / Permissions-Policy -- blocks MIME sniffing, controls Referer leakage, disables unused browser APIs

API authentication (/api/cron/*, /api/backfill):

  • CRON_SECRET is mandatory -- requests are rejected with 503 when the env var is missing, not silently allowed through
  • Timing-safe token comparison -- Bearer tokens are compared in constant time to prevent timing attacks
  • Error sanitization -- API errors never leak stack traces or internal paths to clients
  • Input allowlisting -- the source parameter is validated against a strict allowlist
  • Centralized input validation -- backfill endpoint enforces Content-Type, safe JSON parsing, calendar-valid dates (round-trip check rejects 02-30), 365-day max range, future date rejection, and strict numeric limit typing
  • Upstream timeouts -- all external API fetches use AbortController with a 15s deadline
  • Fail-fast env validation -- server-side Supabase client throws immediately if credentials are missing instead of failing silently

Project Structure

src/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ cron/          # Vercel Cron job β€” WHO ingestion every 6h
β”‚   β”‚   └── backfill/      # POST endpoint β€” historical data by date range
β”‚   β”œβ”€β”€ page.tsx           # Main dashboard (client component)
β”‚   └── globals.css        # Dark theme + hotspot marker animations
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ TimelineSlider.tsx   # Temporal filter with sparkline + range input
β”‚   β”œβ”€β”€ PulseMap.tsx        # Mapbox GL map with heatmap + markers
β”‚   β”œβ”€β”€ Feed.tsx            # Scrollable outbreak feed
β”‚   β”œβ”€β”€ Navbar.tsx          # Top bar with search
β”‚   β”œβ”€β”€ LayerControls.tsx   # Heatmap/hotspot toggle panel
β”‚   β”œβ”€β”€ Legend.tsx          # Color severity legend
β”‚   └── OutbreakDetail.tsx  # Click-to-detail side panel
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ api-client.ts       # Dashboard data loader (single entry point)
β”‚   β”œβ”€β”€ fetch-outbreaks.ts  # Supabase query β†’ GeoJSON transform
β”‚   β”œβ”€β”€ spread-arcs.ts      # Great-circle arc generator for disease networks
β”‚   β”œβ”€β”€ pipeline/           # WHO API client + geocoding + dedup
β”‚   β”œβ”€β”€ seed-data.ts        # Static fallback data
β”‚   └── supabase.ts         # Client initialization
└── types/                  # Shared TypeScript interfaces

Roadmap

βœ… Shipped

  • Phase 1 β€” Interactive heatmap + pulsing hotspot markers on dark basemap
  • Phase 1 β€” Weather-station UI: feed panel, detail drawer, severity legend
  • Phase 1 β€” Supabase backend with RLS policies
  • Phase 4 β€” Automated WHO pipeline via Vercel Cron (6h cycle)
  • Phase 4 β€” Geocoding, deduplication, severity estimation
  • Phase 4 β€” Historical backfill API endpoint
  • Phase 2 β€” Disease spread network with animated great-circle arcs

πŸ”œ Next Up

  • Phase 2 β€” News pin layer (geolocated news markers, toggleable)
  • Phase 3 β€” Google OAuth + user preferences (saved views, "near me")
  • Phase 3 β€” Push notifications for outbreaks in your region
  • Phase 4+ β€” CDC API as secondary data source
  • Phase 4+ β€” LLM-powered extraction from general news APIs
  • Phase 5 β€” Community reporting + moderation + trust scoring
  • Phase 5 β€” Real-time Supabase subscriptions
  • Historical timeline slider βœ… Shipped in v0.4.0
  • Mobile-responsive layout + PWA offline access

Data Sources

Source Status Frequency
WHO Disease Outbreak News Active Every 6 hours
CDC Planned β€”
ReliefWeb Planned β€”
Community Reports Planned (Phase 5) Real-time

License

MIT


Built by @DareDev256

About

Next.js map of WHO Disease Outbreak News: ingest, geocode, dedupe, severity-score, then render as a heat map with spread arcs. The public demo serves a cached snapshot.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages