No description
Find a file
2026-10-11 22:48:44 +03:30
backend feat: add backend service, web panel, docker configuration, and documentation 2026-10-11 22:48:44 +03:30
docs feat: add backend service, web panel, docker configuration, and documentation 2026-10-11 22:48:44 +03:30
panel feat: add backend service, web panel, docker configuration, and documentation 2026-10-11 22:48:44 +03:30
.env.example feat: add backend service, web panel, docker configuration, and documentation 2026-10-11 22:48:44 +03:30
.gitignore feat: add backend service, web panel, docker configuration, and documentation 2026-10-11 22:48:44 +03:30
docker-compose.yml feat: add backend service, web panel, docker configuration, and documentation 2026-10-11 22:48:44 +03:30
README.md first commit 2026-10-11 22:42:28 +03:30

Weather Aggregator Dashboard

Collects current weather for multiple cities from multiple providers and shows a comparison dashboard. Compares data across providers, monitors response times, and surfaces failures/stale data.

  • Backend: Go (backend/), REST API, SQLite (pure-Go modernc.org/sqlite, no CGO)
  • Panel: React + TypeScript, Bun + Vite (panel/), dark theme
  • Providers: 11 built-in total — Open-Meteo, MET Norway, 7Timer!, wttr.in, IRIMO (Iran) work with no key; OpenWeatherMap, WeatherAPI.com, Tomorrow.io, Visual Crossing, Weatherbit, Weatherstack need free API keys. Custom generic JSON providers can be added from the panel with no code.
  • Fresh installs seed 3 demo cities (Amsterdam, London, Sydney), so the first cycle already shows live data
cp .env.example .env   # optional; set OPENWEATHER_API_KEY / ports
docker compose up --build -d
docker compose ps      # backend should become "healthy"
  • Panel: http://localhost:5173 (nginx serves the built panel and proxies /api to the backend, same-origin)
  • Backend API: http://localhost:8080/api/health
  • Data persists in the weather-data volume (DATABASE_PATH=/data/weather.db in the container).
  • Override host ports: BACKEND_PORT=18080 PANEL_PORT=15173 docker compose up --build -d.
docker compose logs -f backend   # watch collection cycles
docker compose down              # stop (keeps data)
docker compose down -v           # stop and delete all stored data

Only the backend talks to weather-provider APIs; the browser talks to the panel container, which proxies /api/* to backend:8080.

Quick start without Docker

1. Backend

cd backend
cp .env.example .env   # edit keys / ports as needed
go mod download
go run ./cmd/server
# or: go build -o server ./cmd/server && ./server

Env vars (see backend/.env.example):

Var Default Purpose
HTTP_ADDR 127.0.0.1:8080 Bind address (keep localhost for MVP)
DATABASE_PATH ./data/weather.db SQLite file
REFRESH_INTERVAL_SECONDS 300 Full collection cycle period
GLOBAL_REQUEST_DELAY_MS 1000 Delay between outgoing provider requests
REQUEST_TIMEOUT_SECONDS 10 Per-request timeout
HISTORY_RETENTION_DAYS 7 Prune readings/attempts/cycles older than this
OPENWEATHER_API_KEY — Required only for the OpenWeatherMap provider

Backend runs a startup cycle (if cities + providers are enabled), then schedules every REFRESH_INTERVAL_SECONDS. Manual refresh: POST /api/refresh (409 if a cycle is already running).

2. Panel

cd panel
bun install
bun run dev      # http://127.0.0.1:5173, proxies /api -> 127.0.0.1:8080
# production:
bun run build    # dist/

The panel never calls weather APIs directly; it only talks to the Go backend and polls /api/dashboard every 15s. Loading the dashboard never triggers collection.

Usage

  1. Open the panel — Amsterdam, London and Sydney are pre-seeded and the startup cycle already fetched weather from the 5 keyless providers (IRIMO records skipped for non-Iran cities).
  2. Add cities: type a name → Search → Select, or enter name + latitude/longitude directly with Add with coordinates.
  3. Enable providers: Provider settings. Open-Meteo, MET Norway, 7Timer! and wttr.in work immediately; the other six show needs key with the exact env var until you set it and restart the backend. See docs/providers.md.
  4. Wait for the automatic cycle or press Refresh now.
  5. Compare per-city × per-provider cards (temp, feels-like, condition, humidity, wind, fetch vs observation time, status, duration).
  6. Tune Scheduler settings (interval, delay, timeout, retention) and inspect Recent activity (cycles, failures, timeouts).

REST API

Method Endpoint Purpose
GET /api/health Health check
GET /api/dashboard Cycle + locations + providers + latest readings
GET /api/locations List locations
GET /api/locations/search?q= Geocode candidates
POST /api/locations Resolve + add ({name} or full {name,latitude,longitude,...}; 300 + candidates if ambiguous)
PATCH /api/locations/{id} {name?, is_enabled?}
DELETE /api/locations/{id} Remove
GET /api/locations/{id}/readings?limit= Recent readings
GET /api/weather/latest?location_id=&city=&country_code=&provider=&enabled_only= Latest weather per city (for other backends, see docs/service-api.md)
POST /api/weather/fetch {"location_id"|"city"+country_code?, "provider?"} → fresh fetch now
GET /api/providers List providers + config status (kind, redacted generic config)
POST /api/providers Add provider ({id, name, kind?, config?})
PATCH /api/providers/{id} {name?, is_enabled?, request_delay_ms?, clear_delay?, config?}
DELETE /api/providers/{id} Remove provider + its history
GET/PATCH /api/settings Scheduler settings (validated)
POST /api/refresh Manual cycle (202 started, 409 already running)
GET /api/cycles?limit= Recent cycles
GET /api/cycles/{id} Cycle + attempts
GET /api/activity?limit= Recent attempts

Architecture

Browser (React+TS, Bun/Vite) ──REST──▶ Go backend ──▶ provider APIs
                                     ├─ REST API (stdlib ServeMux)
                                     ├─ Location service (Open-Meteo geocoding)
                                     ├─ Scheduler (ticker, mutex, no overlap)
                                     ├─ Provider adapters (common interface)
                                     └─ SQLite (locations, providers, settings,
                                        readings, attempts, cycles)

Each cycle iterates enabled locations × enabled providers, sleeps the provider (or global) delay between requests, applies a per-request timeout, records every attempt, and keeps the last successful reading on failure. Retry-After/429 is recorded as rate_limited with no retry loop.

Adding a provider

For a JSON API you can add a generic provider from the panel (Provider settings → Add provider) — no code needed. See docs/providers.md.

For a built-in adapter (custom auth, HTML scraping, ...):

  1. Create backend/internal/providers/myprov.go implementing:
    type WeatherProvider interface {
        ID() string
        Name() string
        CurrentWeather(ctx context.Context, location Location) (WeatherReading, *CallLog, error)
    }
    
    Normalize units inside the adapter (°C, m/s, hPa, mm); use nil for missing fields. Return a *CallLog (redacted URL + response snippet) so attempts are inspectable, or a *SkipError when the provider has no coverage for the city.
  2. Register it in providers.NewRegistry() and extend IsConfigured() if it needs a key.
  3. The providers seed table picks up the new id automatically; scheduler and dashboard need no changes.

Testing

cd backend && go test ./...
cd ../panel && bunx tsc --noEmit && bun run build

Covers: N×M fan-out, disabled provider/location exclusion, failure isolation, delay respected, no concurrent cycles, timeout isolation, persistence round-trip, settings validation.

Security notes

  • Keys live only in env/backend; never sent to the browser or logged (error messages are sanitized).
  • Binds to localhost by default; set API_TOKEN and terminate TLS in a reverse proxy before exposing beyond a trusted network.
  • All outbound HTTP has timeouts; inputs and settings are validated.

See docs/providers.md for provider setup, docs/storage.md for where your data lives, docs/api.md for endpoint details, and docs/service-api.md for consuming the API from another backend.