docs: add structured documentation covering architecture, contexts, web, infra, and dev setup
This commit is contained in:
parent
1006fdaefc
commit
0e322a2403
13 changed files with 943 additions and 0 deletions
41
docs/README.md
Normal file
41
docs/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# aprs.me Documentation
|
||||||
|
|
||||||
|
APRS packet visualization and tracking platform built with Phoenix LiveView + PostGIS.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- [Architecture Overview](architecture/overview.md) — System components, data flow, and design decisions
|
||||||
|
- [Ingestion Pipeline](architecture/ingestion-pipeline.md) — APRS-IS → GenStage → Postgres → PubSub broadcast
|
||||||
|
|
||||||
|
## Domain Contexts
|
||||||
|
|
||||||
|
- [Accounts](contexts/accounts.md) — User registration, authentication, session management
|
||||||
|
- [Packets](contexts/packets.md) — Packet storage, querying, replay, weather, and spatial search
|
||||||
|
- [Devices](contexts/devices.md) — APRS device identification and registry
|
||||||
|
|
||||||
|
## Web Layer
|
||||||
|
|
||||||
|
- [LiveView Map](web/liveview-map.md) — MapLive architecture, real-time streaming, components
|
||||||
|
- [LiveView Pages](web/liveview-pages.md) — Packet streams, weather, status, info, auth pages
|
||||||
|
- [REST API](web/api-rest.md) — API v1 endpoints (callsign lookup, weather nearby)
|
||||||
|
- [Mobile WebSocket](web/channels.md) — MobileChannel protocol for iOS/Android apps
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
- [Deployment](infrastructure/deployment.md) — Kubernetes (ArgoCD), Fly.io, Docker, Nix dev shell
|
||||||
|
- [Observability](infrastructure/observability.md) — PromEx metrics, error tracking, telemetry
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- [Setup Guide](development/setup.md) — Local dev environment, required services, tooling
|
||||||
|
|
||||||
|
## Features & Plans
|
||||||
|
|
||||||
|
- [Tracked Callsign Behavior](tracked-callsign-behavior.md)
|
||||||
|
- [Trail Line Visualization](features/trail-line-visualization.md)
|
||||||
|
- [Plans](plans/) — Design documents and implementation plans
|
||||||
|
- [Improvement Todos](improvement-todos.md)
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- [Mobile API Reference](mobile-api.md) — WebSocket message format for mobile clients
|
||||||
66
docs/architecture/ingestion-pipeline.md
Normal file
66
docs/architecture/ingestion-pipeline.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Ingestion Pipeline
|
||||||
|
|
||||||
|
The packet ingestion pipeline moves raw APRS data from the APRS-IS network into Postgres and broadcasts to connected clients.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Aprsme.Is (TCP Connection)
|
||||||
|
|
||||||
|
- **File:** `lib/aprsme/is/is.ex`
|
||||||
|
- **Type:** GenServer
|
||||||
|
- **Role:** Maintains TCP connection to APRS-IS, authenticates with callsign + passcode, receives raw APRS frames
|
||||||
|
- **Parsing:** Uses `aprs.parse/1` (external Gleam library) to decode APRS frames
|
||||||
|
- **Filtering:** Configurable APRS-IS filter (by callsign, area, distance) via `Aprsme.Is.LoginParams`
|
||||||
|
- **Backpressure:** Responds to `{:backpressure, :activate | :deactivate}` signals from `PacketProducer` — sets TCP socket to `:passive` mode when buffer is full
|
||||||
|
|
||||||
|
### PacketProducer (GenStage)
|
||||||
|
|
||||||
|
- **File:** `lib/aprsme/packet_producer.ex`
|
||||||
|
- **Type:** GenStage producer
|
||||||
|
- **Buffer:** Erlang `:queue` with water-mark backpressure
|
||||||
|
- **High water** (>80% capacity): signals `Aprsme.Is` to activate backpressure
|
||||||
|
- **Low water** (<30% capacity): signals `Aprsme.Is` to deactivate backpressure
|
||||||
|
- **Demand-driven:** Only emits packets when consumers are ready
|
||||||
|
|
||||||
|
### PacketConsumer (GenStage Consumer)
|
||||||
|
|
||||||
|
- **File:** `lib/aprsme/packet_consumer.ex`
|
||||||
|
- **Type:** GenStage consumer (pooled via `PacketConsumerPool`)
|
||||||
|
- **Processing pipeline per packet:**
|
||||||
|
1. Sanitize — `PacketSanitizer.sanitize_packet/1` (overflow prevention)
|
||||||
|
2. Normalize — `EncodingUtils` conversions (UTF-8, latin1, floats, decimals)
|
||||||
|
3. Extract position — lat/lon → PostGIS geometry
|
||||||
|
4. Extract device — `DeviceParser.extract_device_id/1`
|
||||||
|
5. Filter fields — `PacketFieldWhitelist` trims to schema columns
|
||||||
|
6. Insert — `Repo.insert_all` with `on_conflict: :nothing` (idempotent)
|
||||||
|
7. Broadcast — async dispatch to PubSub systems
|
||||||
|
|
||||||
|
### Broadcasting After Insert
|
||||||
|
|
||||||
|
After successful insert, each packet is broadcast to three PubSub layers:
|
||||||
|
|
||||||
|
1. **StreamingPacketsPubSub** — ETS-based, clients subscribe with bounds
|
||||||
|
2. **SpatialPubSub** — Grid-indexed (1° cells), viewport-filtered delivery
|
||||||
|
3. **Phoenix.PubSub** — Legacy per-topic broadcasts:
|
||||||
|
- `"postgres:aprsme_packets"` — global packet stream
|
||||||
|
- `"packets:#{callsign}"` — per-callsign updates
|
||||||
|
- `"weather:#{callsign}"` — per-weather-station updates
|
||||||
|
|
||||||
|
Broadcast execution is async via `BroadcastTaskSupervisor`.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Packets that fail parsing or validation are stored in `badpackets` table via `Packets.store_bad_packet/2` with error type/message, viewable at `/badpackets` page.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- APRS-IS server, callsign, passcode set via application config
|
||||||
|
- Consumer pool size configurable per environment
|
||||||
|
- Retention period configurable via `CleanupScheduler`
|
||||||
|
|
||||||
|
## Performance Notes
|
||||||
|
|
||||||
|
- `PreparedQueries` module caches frequently-used query plans
|
||||||
|
- `InsertOptimizer` batch-tunes insert operations
|
||||||
|
- `PacketConsumer` uses `insert_all` (not individual inserts) for throughput
|
||||||
|
- Daily table partitioning via `PartitionManager` keeps index sizes manageable
|
||||||
92
docs/architecture/overview.md
Normal file
92
docs/architecture/overview.md
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Architecture Overview
|
||||||
|
|
||||||
|
## System Purpose
|
||||||
|
|
||||||
|
Real-time visualization of APRS (Automatic Packet Reporting System) packets on an interactive map. Tracks amateur radio stations, weather data, and station telemetry via APRS-IS network feed.
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|---|---|
|
||||||
|
| Language | Elixir 1.17+ |
|
||||||
|
| Web Framework | Phoenix 1.8 + LiveView 1.2 |
|
||||||
|
| Database | PostgreSQL + PostGIS |
|
||||||
|
| APRS Parsing | `aprs` (Gleam BEAM library) |
|
||||||
|
| Map Client | Leaflet (lazy-loaded) |
|
||||||
|
| Charts | Chart.js (lazy-loaded) |
|
||||||
|
| Metrics | PromEx + Telemetry |
|
||||||
|
| Deployment | Kubernetes (ArgoCD), Fly.io, Docker |
|
||||||
|
|
||||||
|
## High-Level Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
APRS-IS Network (TCP)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Aprsme.Is (GenServer — TCP connection manager)
|
||||||
|
│ raw APRS frames parsed via aprs.parse/1
|
||||||
|
▼
|
||||||
|
PacketProducer (GenStage — water-mark backpressure)
|
||||||
|
│ buffers incoming packets, signals backpressure upstream
|
||||||
|
▼
|
||||||
|
PacketConsumer pool (GenStage consumers)
|
||||||
|
│ sanitize → normalize → insert into Postgres
|
||||||
|
│ broadcast to PubSub
|
||||||
|
├──► Postgres packets table (daily-partitioned)
|
||||||
|
└──► Broadcasting
|
||||||
|
├── SpatialPubSub (viewport-filtered, grid-indexed)
|
||||||
|
├── StreamingPacketsPubSub (ETS-based, bounds-filtered)
|
||||||
|
└── Phoenix.PubSub (per-callsign topics)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
LiveView clients (MapLive, PacketsLive, InfoLive)
|
||||||
|
Mobile WebSocket clients (MobileChannel)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supervision Tree
|
||||||
|
|
||||||
|
```
|
||||||
|
Aprsme.Application
|
||||||
|
├── Aprsme.Repo
|
||||||
|
├── Phoenix.PubSub
|
||||||
|
├── Aprsme.Cache / DeviceCache / WeatherCache / RegexCache
|
||||||
|
├── Aprsme.RateLimiter
|
||||||
|
├── Aprsme.PacketPipelineSupervisor
|
||||||
|
│ ├── PacketProducer
|
||||||
|
│ └── PacketConsumerPool
|
||||||
|
│ └── PacketConsumer (×N)
|
||||||
|
├── Aprsme.BroadcastTaskSupervisor
|
||||||
|
├── Aprsme.Is.IsSupervisor
|
||||||
|
│ └── Aprsme.Is
|
||||||
|
├── Aprsme.SpatialPubSub
|
||||||
|
├── Aprsme.StreamingPacketsPubSub
|
||||||
|
├── Aprsme.CleanupScheduler
|
||||||
|
├── Aprsme.PartitionManager
|
||||||
|
├── Aprsme.PostgresNotifier
|
||||||
|
├── Aprsme.ConnectionMonitor
|
||||||
|
├── Aprsme.SignalHandler
|
||||||
|
├── Aprsme.ShutdownHandler
|
||||||
|
├── Aprsme.PromEx
|
||||||
|
├── Cluster modules (optional, via libcluster)
|
||||||
|
└── AprsmeWeb.Endpoint (Bandit)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### Database Partitioning
|
||||||
|
The `packets` table is **daily-partitioned** via `PartitionManager`. High ingestion volume makes partitioning essential for query performance and retention cleanup.
|
||||||
|
|
||||||
|
### Spatial Filtering
|
||||||
|
Rather than broadcasting every packet to every client, `SpatialPubSub` maintains a 1° grid spatial index. Clients register their map viewport bounds; only packets intersecting those bounds are delivered.
|
||||||
|
|
||||||
|
### Backpressure
|
||||||
|
`PacketProducer` uses water-mark backpressure (>80% buffer → pause TCP; <30% → resume) to prevent memory exhaustion under load spikes.
|
||||||
|
|
||||||
|
### Symbol System
|
||||||
|
APRS symbols are rendered client-side from sprite sheets. `AprsSymbol` provides server-side symbol table lookups for info pages.
|
||||||
|
|
||||||
|
### Clustering
|
||||||
|
Optional multi-node deployment via `libcluster`. Uses leader election to designate the node that maintains the APRS-IS connection. Packets are relayed to other nodes via `PacketDistributor`.
|
||||||
|
|
||||||
|
### Lazy Asset Loading
|
||||||
|
The map bundle (Leaflet + plugins) and chart bundle (Chart.js) are loaded on-demand only for pages that need them, keeping initial page load small.
|
||||||
73
docs/contexts/accounts.md
Normal file
73
docs/contexts/accounts.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Accounts Context
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Module:** `Aprsme.Accounts` (`lib/aprsme/accounts.ex`)
|
||||||
|
|
||||||
|
Handles user registration, authentication, email confirmation, password reset, and session management.
|
||||||
|
|
||||||
|
## Schemas
|
||||||
|
|
||||||
|
### User (`lib/aprsme/accounts/user.ex`)
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | binary_id | Primary key |
|
||||||
|
| `email` | citext | Case-insensitive, unique |
|
||||||
|
| `callsign` | string | Optional amateur radio callsign |
|
||||||
|
| `hashed_password` | string | Bcrypt-hashed |
|
||||||
|
| `confirmed_at` | utc_datetime | Email confirmation timestamp |
|
||||||
|
| `password` | virtual | Transient, used for changesets only |
|
||||||
|
|
||||||
|
### UserToken (`lib/aprsme/accounts/user_token.ex`)
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | binary_id | Primary key |
|
||||||
|
| `token` | binary | Bcrypt-hashed token |
|
||||||
|
| `context` | string | `"session"`, `"confirm"`, `"reset_password"`, `"change_email"` |
|
||||||
|
| `sent_to` | string | Email or identifier |
|
||||||
|
| `user_id` | binary_id | FK → `users.id` |
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
### Registration
|
||||||
|
- `register_user/1` — Create user from registration params
|
||||||
|
- `change_user_registration/2` — Changeset for registration form
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
- `get_user_by_email_and_password/2` — Verify credentials
|
||||||
|
- `generate_user_session_token/1` — Create session token
|
||||||
|
- `get_user_by_session_token/1` — Lookup user from token
|
||||||
|
- `delete_user_session_token/1` — Logout
|
||||||
|
|
||||||
|
### Email Confirmation
|
||||||
|
- `deliver_user_confirmation_instructions/2` — Send confirmation email
|
||||||
|
- `confirm_user/1` — Mark user as confirmed
|
||||||
|
|
||||||
|
### Password Reset
|
||||||
|
- `deliver_user_reset_password_instructions/2` — Send reset email
|
||||||
|
- `get_user_by_reset_password_token/1` — Validate reset token
|
||||||
|
- `reset_user_password/2` — Update password
|
||||||
|
|
||||||
|
### Account Settings
|
||||||
|
- `change_user_email/2`, `update_user_email/2` — Email change flow
|
||||||
|
- `change_user_callsign/2`, `update_user_callsign/3` — Callsign management
|
||||||
|
- `change_user_password/2`, `update_user_password/3` — Password change
|
||||||
|
|
||||||
|
## Email Delivery
|
||||||
|
|
||||||
|
Uses `Aprsme.Accounts.UserNotifier` which delegates to `Aprsme.Mailer` (Swoosh with custom Resend adapter).
|
||||||
|
|
||||||
|
## Web Integration
|
||||||
|
|
||||||
|
- `AprsmeWeb.UserAuth` module provides plug-based and LiveView `on_mount` hooks
|
||||||
|
- Session stored in signed+encrypted cookie (`_aprs_key`)
|
||||||
|
- "Remember me" cookie (60-day expiry, signed)
|
||||||
|
- Token-based session with fixation prevention (renew on login/logout)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- No FK relationship between `users` ↔ `packets`
|
||||||
|
- Authentication is optional; most pages are publicly accessible without login
|
||||||
|
- Authenticated pages: `/users/settings`, `/dashboard` (LiveDashboard), `/errors` (ErrorTracker)
|
||||||
51
docs/contexts/devices.md
Normal file
51
docs/contexts/devices.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Devices Context
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Identifies APRS equipment from packet data, maintaining a local registry of known devices.
|
||||||
|
|
||||||
|
## Schema: `Aprsme.Devices` (`lib/aprsme/devices.ex`)
|
||||||
|
|
||||||
|
Stores known APRS device information in the `devices` table:
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `identifier` | string | Unique device identifier |
|
||||||
|
| `class` | string | Device class (e.g., "tracker", "handheld") |
|
||||||
|
| `model` | string | Model name |
|
||||||
|
| `vendor` | string | Manufacturer name |
|
||||||
|
| `os` | string | Operating system |
|
||||||
|
| `contact` | string | Vendor/author contact |
|
||||||
|
| `features` | {:array, :string} | Feature tags |
|
||||||
|
|
||||||
|
No FK constraint: `device_identifier` on the `packets` table is a free-text string, not a DB-level reference.
|
||||||
|
|
||||||
|
## DeviceIdentification (`lib/aprsme/device_identification.ex`)
|
||||||
|
|
||||||
|
### Static Identification
|
||||||
|
Matches known MIC-E symbol patterns (regex-based) to identify ~20 common device types from packet data.
|
||||||
|
|
||||||
|
### Remote Registry
|
||||||
|
Fetches device database from `aprs-deviceid.aprsfoundation.org`:
|
||||||
|
- `maybe_refresh_devices/0` — Weekly refresh cycle
|
||||||
|
- `fetch_and_upsert_devices/0` — HTTP fetch wrapped in `CircuitBreaker`, upserts results into `devices` table
|
||||||
|
- `lookup_device_by_identifier/1` — Wildcard-matching lookup, delegates to `DeviceCache`
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
- `DeviceCache` GenServer caches device data, refreshed daily
|
||||||
|
- Backed by `Aprsme.Cache` abstraction
|
||||||
|
|
||||||
|
## DeviceParser (`lib/aprsme/device_parser.ex`)
|
||||||
|
|
||||||
|
Extracts device identifiers from incoming packets by probing:
|
||||||
|
1. `data_extended.device_identifier` field
|
||||||
|
2. `destination` field
|
||||||
|
3. `data_extended.symbol_table_id` + `symbol_code` combination
|
||||||
|
|
||||||
|
Called during packet ingestion pipeline in `PacketConsumer`.
|
||||||
|
|
||||||
|
## Resilience
|
||||||
|
|
||||||
|
- `CircuitBreaker` wraps remote HTTP fetch, preventing cascading failures
|
||||||
|
- Cache ensures device lookups work even when remote source is unavailable
|
||||||
|
- Static patterns provide fallback identification without network calls
|
||||||
100
docs/contexts/packets.md
Normal file
100
docs/contexts/packets.md
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# Packets Context
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Module:** `Aprsme.Packets` (`lib/aprsme/packets.ex`)
|
||||||
|
**Behaviour:** `Aprsme.PacketsBehaviour`
|
||||||
|
|
||||||
|
Core domain for APRS packet storage, retrieval, querying, and management.
|
||||||
|
|
||||||
|
## Schema: `Aprsme.Packet` (`lib/aprsme/packet.ex`)
|
||||||
|
|
||||||
|
The `packets` table is **daily-partitioned** on `received_at`. Composite primary key: `{id, received_at}`.
|
||||||
|
|
||||||
|
### Key Field Groups
|
||||||
|
|
||||||
|
| Group | Fields |
|
||||||
|
|---|---|
|
||||||
|
| **Identity** | `id`, `sender` (callsign), `base_callsign`, `ssid`, `destination`, `path` |
|
||||||
|
| **Position** | `lat` (decimal), `lon` (decimal), `location` (PostGIS geometry), `has_position` (boolean), `dao` (map) |
|
||||||
|
| **Symbol** | `symbol_code`, `symbol_table_id` |
|
||||||
|
| **Weather** | `temperature`, `humidity`, `wind_speed`, `wind_direction`, `wind_gust`, `pressure`, `rain_1h`, `rain_24h`, `rain_since_midnight`, `snow`, `has_weather` |
|
||||||
|
| **Equipment** | `manufacturer`, `equipment_type`, `course`, `speed`, `altitude` |
|
||||||
|
| **Messaging** | `addressee`, `message_text`, `message_number` |
|
||||||
|
| **Other** | `data_type`, `region`, `comment`, `raw_packet`, `device_identifier`, `data` (JSONB) |
|
||||||
|
|
||||||
|
### Changeset Pipeline
|
||||||
|
|
||||||
|
The `changeset/2` function auto-computes:
|
||||||
|
- `has_position` and `has_weather` booleans
|
||||||
|
- `location` PostGIS point from lat/lon
|
||||||
|
- Symbol normalization
|
||||||
|
- Course/wind_direction normalization
|
||||||
|
- Display-only fields swept into `data` JSONB column
|
||||||
|
|
||||||
|
## Schema: `Aprsme.BadPacket` (`lib/aprsme/bad_packet.ex`)
|
||||||
|
|
||||||
|
Failed packets stored in `badpackets` table:
|
||||||
|
- `raw_packet` — original APRS frame
|
||||||
|
- `error_message`, `error_type` — parsing/storage error details
|
||||||
|
- `attempted_at` — timestamp
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
### Storage
|
||||||
|
- `store_packet/1` — Sanitize → build attrs → insert; errors → `badpackets`
|
||||||
|
- `store_bad_packet/2` — Record parse/insert failures
|
||||||
|
|
||||||
|
### Querying
|
||||||
|
- `get_recent_packets/1` — Cursor-paginated, latest first
|
||||||
|
- `get_recent_packets_for_map/1` — Lightweight map-optimized query (~22 columns)
|
||||||
|
- `get_latest_packet_for_callsign/1` — Single callsign latest
|
||||||
|
- `get_latest_positions_for_callsigns/1` — Batch latest positions
|
||||||
|
- `get_nearby_stations/3,4` — KNN spatial query via PostGIS `<->` operator
|
||||||
|
- `get_weather_packets/4` — Time-bound weather data
|
||||||
|
- `get_other_ssids/1` — Find SSID variants of a base callsign
|
||||||
|
- `get_total_packet_count/0` — Via PostgreSQL function
|
||||||
|
|
||||||
|
### Replay
|
||||||
|
- `get_packets_for_replay/1` — Historical packets filtered by callsign/bounds/region/time
|
||||||
|
- `stream_packets_for_replay/1` — Lazy stream with timing preservation
|
||||||
|
- `get_historical_packet_count/1` — Area packet density
|
||||||
|
|
||||||
|
### Weather
|
||||||
|
- `get_latest_weather_packet/1` — Latest weather for station
|
||||||
|
- `has_weather_packets?/1`, `weather_callsigns/1` — Weather presence queries
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
- `clean_old_packets/0`, `clean_packets_older_than/1` — Retention cleanup
|
||||||
|
- `get_oldest_packet_timestamp/0` — Oldest data timestamp
|
||||||
|
|
||||||
|
## Submodules
|
||||||
|
|
||||||
|
### QueryBuilder (`lib/aprsme/packets/query_builder.ex`)
|
||||||
|
Composable Ecto query helpers: `with_position/1`, `for_callsign/2`, `within_bounds/2`, `weather_only/1`, `chronological/1`, `recent_first/1`, `select_map_fields/1`, etc.
|
||||||
|
|
||||||
|
### PreparedQueries (`lib/aprsme/packets/prepared_queries.ex`)
|
||||||
|
High-performance prepared statements for hot paths: KNN queries, weather queries, bounds-filtered queries, callsign lookups.
|
||||||
|
|
||||||
|
### Clustering (`lib/aprsme/packets/clustering.ex`)
|
||||||
|
Grid-based heatmap clustering for low-zoom map views (zoom ≤ 8). Reduces thousands of points to manageable grid cells.
|
||||||
|
|
||||||
|
## Supporting Utilities
|
||||||
|
|
||||||
|
| Module | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `Aprsme.PacketSanitizer` | Prevents DB field overflow |
|
||||||
|
| `Aprsme.PacketFieldWhitelist` | Filters fields for `insert_all` |
|
||||||
|
| `Aprsme.Encoding` / `EncodingUtils` | String/data normalization |
|
||||||
|
| `Aprsme.Convert` | Unit conversions |
|
||||||
|
| `Aprsme.Callsign` | Callsign parsing, normalization, SSID extraction |
|
||||||
|
| `Aprsme.Maidenhead` | Maidenhead grid locator conversion |
|
||||||
|
| `Aprsme.GeoUtils` | Geographic calculations |
|
||||||
|
| `Aprsme.WeatherCache` | ETS cache for weather callsign presence (TTL 5 min) |
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
- `packets` table: daily-partitioned, managed by `Aprsme.PartitionManager`
|
||||||
|
- `badpackets` table: standard table for error logging
|
||||||
|
- PostGIS extensions required for spatial queries
|
||||||
|
- `get_packet_count()` PostgreSQL function for efficient counting
|
||||||
129
docs/development/setup.md
Normal file
129
docs/development/setup.md
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
# Development Setup
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Elixir 1.17+
|
||||||
|
- Erlang/OTP 26+
|
||||||
|
- PostgreSQL 16+ with PostGIS extension
|
||||||
|
- Node.js 20+ (for asset compilation)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone and cd
|
||||||
|
git clone <repo-url>
|
||||||
|
cd aprs.me
|
||||||
|
|
||||||
|
# Install dependencies and set up database
|
||||||
|
mix setup
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
mix phx.server
|
||||||
|
# or with IEx:
|
||||||
|
iex -S mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at `http://localhost:4000`.
|
||||||
|
|
||||||
|
## Nix Development Environment (Optional)
|
||||||
|
|
||||||
|
A reproducible development environment is available via Nix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
# or with direnv:
|
||||||
|
direnv allow
|
||||||
|
```
|
||||||
|
|
||||||
|
This provides all required tooling (Elixir, Erlang, PostgreSQL, Node.js) without manual installation.
|
||||||
|
|
||||||
|
## Database Setup
|
||||||
|
|
||||||
|
The `mix setup` command:
|
||||||
|
1. Installs Hex and Rebar dependencies
|
||||||
|
2. Fetches Elixir dependencies
|
||||||
|
3. Creates the development database
|
||||||
|
4. Runs migrations
|
||||||
|
5. Seeds device data
|
||||||
|
|
||||||
|
### Manual Database Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mix ecto.create
|
||||||
|
mix ecto.migrate
|
||||||
|
mix run priv/repo/seeds.exs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** PostGIS extension must be enabled on the database. The migration pipeline handles this, but ensure your PostgreSQL installation includes PostGIS.
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full test suite
|
||||||
|
mix test
|
||||||
|
|
||||||
|
# Watch mode (auto-rerun on changes)
|
||||||
|
mix test.watch
|
||||||
|
|
||||||
|
# Coverage report
|
||||||
|
mix coveralls.html
|
||||||
|
# View at cover/excoveralls.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format code
|
||||||
|
mix format
|
||||||
|
|
||||||
|
# Static analysis
|
||||||
|
mix credo --strict
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
mix dialyzer
|
||||||
|
|
||||||
|
# Security scan
|
||||||
|
mix sobelow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Asset Compilation
|
||||||
|
|
||||||
|
Frontend assets use ESBuild + Tailwind CSS v4:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development (watch mode)
|
||||||
|
mix assets.build
|
||||||
|
|
||||||
|
# Production
|
||||||
|
MIX_ENV=prod mix assets.deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
JavaScript entry points are in `assets/js/`:
|
||||||
|
- `app.ts` — Core application (always loaded)
|
||||||
|
- `map.ts` — Map functionality (lazy-loaded)
|
||||||
|
- `map_helpers.ts` — Leaflet utilities
|
||||||
|
- `map_fixes.ts` — Leaflet bug workarounds
|
||||||
|
|
||||||
|
CSS: Tailwind v4 in `assets/css/app.css` with custom dark mode and Leaflet overrides.
|
||||||
|
|
||||||
|
## Useful Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `mix phx.server` | Start dev server |
|
||||||
|
| `iex -S mix phx.server` | Start with interactive shell |
|
||||||
|
| `mix test` | Run test suite |
|
||||||
|
| `mix test.watch` | Auto-rerun tests on changes |
|
||||||
|
| `mix format` | Format Elixir code |
|
||||||
|
| `mix credo --strict` | Lint check |
|
||||||
|
| `mix dialyzer` | Type checking |
|
||||||
|
| `mix sobelow` | Security scan |
|
||||||
|
| `mix coveralls.html` | Test coverage report |
|
||||||
|
| `mix phx.routes` | List all routes |
|
||||||
|
| `mix ecto.migrate` | Run pending migrations |
|
||||||
|
| `mix ecto.rollback` | Rollback last migration |
|
||||||
|
| `mix parse_file <path>` | Parse raw APRS log file |
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
See [AGENTS.md](../../AGENTS.md) for coding conventions, and [Architecture Overview](../architecture/overview.md) for system design.
|
||||||
73
docs/infrastructure/deployment.md
Normal file
73
docs/infrastructure/deployment.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Deployment
|
||||||
|
|
||||||
|
## Platforms
|
||||||
|
|
||||||
|
### Kubernetes (ArgoCD)
|
||||||
|
|
||||||
|
Primary production deployment target. Manifests in `k8s/`.
|
||||||
|
|
||||||
|
<!-- TODO: Document cluster topology, resource requirements, scaling strategy -->
|
||||||
|
|
||||||
|
### Fly.io
|
||||||
|
|
||||||
|
Alternative deployment option. Configuration in `fly.toml`.
|
||||||
|
|
||||||
|
<!-- TODO: Document Fly.io deployment steps -->
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
Multi-stage Docker build (`Dockerfile`).
|
||||||
|
- Build stage: Elixir compilation + asset bundling
|
||||||
|
- Release stage: Minimal runtime with Erlang/Elixir release
|
||||||
|
|
||||||
|
## Release Management
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MIX_ENV=prod mix assets.deploy
|
||||||
|
mix release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Migrations
|
||||||
|
|
||||||
|
`Aprsme.Release` module handles migrations during release startup.
|
||||||
|
|
||||||
|
<!-- TODO: Document migration strategy (rolling updates, advisory locks) -->
|
||||||
|
|
||||||
|
## Environment Configuration
|
||||||
|
|
||||||
|
Configuration files in `config/`:
|
||||||
|
- `config.exs` — Base configuration
|
||||||
|
- `dev.exs` — Development overrides
|
||||||
|
- `prod.exs` — Production defaults
|
||||||
|
- `runtime.exs` — Runtime configuration (from env vars)
|
||||||
|
|
||||||
|
### Key Environment Variables
|
||||||
|
|
||||||
|
<!-- TODO: Document required env vars (DATABASE_URL, SECRET_KEY_BASE, APRS_IS credentials, etc.) -->
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Nix Shell
|
||||||
|
|
||||||
|
Nix-based reproducible dev environment via `flake.nix` / `shell.nix`. Provides Elixir, Erlang, PostgreSQL, and all tooling.
|
||||||
|
|
||||||
|
### Dev Containers
|
||||||
|
|
||||||
|
`.devcontainer/` configuration for VS Code / GitHub Codespaces.
|
||||||
|
|
||||||
|
## Clustering
|
||||||
|
|
||||||
|
Optional multi-node deployment via `libcluster`:
|
||||||
|
- Leader election determines which node maintains APRS-IS connection
|
||||||
|
- Packet distribution relayed across nodes
|
||||||
|
- Configuration in cluster modules under `lib/aprsme/cluster/`
|
||||||
|
|
||||||
|
## Health Checks
|
||||||
|
|
||||||
|
- `/health` — Kubernetes liveness (basic) and readiness (DB + PubSub, shutdown-aware)
|
||||||
|
- `/ready` — App readiness check
|
||||||
|
- `/status.json` — JSON status endpoint
|
||||||
|
|
||||||
|
Handled by `HealthCheck` plug before router.
|
||||||
65
docs/infrastructure/observability.md
Normal file
65
docs/infrastructure/observability.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Observability
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
### PromEx (`Aprsme.PromEx`)
|
||||||
|
|
||||||
|
Prometheus-compatible metrics exposed at `/metrics` (PromEx plugin).
|
||||||
|
Custom plugin: `Aprsme.PromEx.Plugins.Aprsme`
|
||||||
|
|
||||||
|
<!-- TODO: Document key metrics (packet rates, DB query times, broadcast latency) -->
|
||||||
|
|
||||||
|
### Telemetry
|
||||||
|
|
||||||
|
`telemetry_metrics` + `telemetry_poller` for application-level metrics:
|
||||||
|
- `Aprsme.Telemetry.DatabaseMetrics` — DB query performance
|
||||||
|
- `Aprsme.ApiMetrics` — API usage tracking
|
||||||
|
|
||||||
|
## Error Tracking
|
||||||
|
|
||||||
|
### ErrorTracker
|
||||||
|
|
||||||
|
Self-hosted error tracking dashboard at `/errors` (authenticated).
|
||||||
|
Configuration: `error_tracker` hex package.
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- `Aprsme.ErrorHandler` — Application error handling
|
||||||
|
- `Aprsme.ErrorNotifier` — Telemetry-based error notification
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
### Log Sanitization
|
||||||
|
|
||||||
|
`Aprsme.LogSanitizer` — Strips PII from log output.
|
||||||
|
|
||||||
|
### Log Filtering
|
||||||
|
|
||||||
|
`AprsmeWeb.Plugs.LogFilter` — Filters noisy paths (`/health`, `/ready`, `/`) from access logs.
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Connection Monitor
|
||||||
|
|
||||||
|
`Aprsme.ConnectionMonitor` — Monitors APRS-IS connection health.
|
||||||
|
|
||||||
|
### Status Page
|
||||||
|
|
||||||
|
`/status` (StatusLive.Index) — Real-time system status dashboard:
|
||||||
|
- APRS-IS connection state
|
||||||
|
- Uptime
|
||||||
|
- Health score
|
||||||
|
- Cluster status (when clustered)
|
||||||
|
|
||||||
|
### Analytics
|
||||||
|
|
||||||
|
`Aprsme.PlausibleAnalytics` — Plausible analytics integration for page views.
|
||||||
|
|
||||||
|
## Signal Handling
|
||||||
|
|
||||||
|
- `Aprsme.SignalHandler` — OS signal handling (SIGTERM, SIGINT)
|
||||||
|
- `Aprsme.ShutdownHandler` — Graceful shutdown coordination
|
||||||
|
|
||||||
|
## Deployment Notifications
|
||||||
|
|
||||||
|
`Aprsme.DeploymentNotifier` — Notifies external systems of deploy events.
|
||||||
56
docs/web/api-rest.md
Normal file
56
docs/web/api-rest.md
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# REST API v1
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Public REST API for programmatic access to APRS packet data.
|
||||||
|
|
||||||
|
**Base URL:** `/api/v1`
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
**Auth Pipeline:** `:api` — JSON accepts, rate-limited (100 req/min), API CSRF protection
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### `GET /api/v1/callsign/:callsign`
|
||||||
|
|
||||||
|
Returns the latest packet for a given amateur radio callsign.
|
||||||
|
|
||||||
|
**Controller:** `AprsmeWeb.Api.V1.CallsignController`
|
||||||
|
**View:** `AprsmeWeb.Api.V1.CallsignJSON`
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `callsign` | string | yes | Valid amateur radio callsign (path param) |
|
||||||
|
|
||||||
|
<!-- TODO: Document response schema, example request/response -->
|
||||||
|
|
||||||
|
### `GET /api/v1/weather/nearby`
|
||||||
|
|
||||||
|
Returns nearby weather station data.
|
||||||
|
|
||||||
|
**Controller:** `AprsmeWeb.Api.V1.WeatherController`
|
||||||
|
**View:** `AprsmeWeb.Api.V1.WeatherJSON`
|
||||||
|
|
||||||
|
<!-- TODO: Document query parameters (lat, lon, radius, limit), response schema, example -->
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
API errors are rendered via `ErrorJSON` and `ChangesetJSON` view modules. The `FallbackController` handles generic error cases.
|
||||||
|
|
||||||
|
<!-- TODO: Document error response format, status codes -->
|
||||||
|
|
||||||
|
## Rate Limiting
|
||||||
|
|
||||||
|
API endpoints are rate-limited to 100 requests per minute per client via `RateLimiter` plug (ETS-based).
|
||||||
|
|
||||||
|
## CSRF Protection
|
||||||
|
|
||||||
|
API requests must include either:
|
||||||
|
- `X-Requested-With: XMLHttpRequest` header, or
|
||||||
|
- Valid CSRF token
|
||||||
|
|
||||||
|
Enforced by `ApiCSRF` plug.
|
||||||
|
|
||||||
|
## Interactive Docs
|
||||||
|
|
||||||
|
A live API documentation page with interactive testing form is available at `/api` (`ApiDocsLive`).
|
||||||
28
docs/web/channels.md
Normal file
28
docs/web/channels.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Mobile WebSocket Channel
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Real-time packet streaming for iOS/Android mobile applications.
|
||||||
|
|
||||||
|
**Socket:** `MobileUserSocket` (`/mobile`, WebSocket only)
|
||||||
|
**Channel:** `MobileChannel`
|
||||||
|
|
||||||
|
## Connection
|
||||||
|
|
||||||
|
Mobile clients connect via WebSocket to `/mobile`:
|
||||||
|
```
|
||||||
|
ws://host/mobile/websocket?vsn=2.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
See [mobile-api.md](../mobile-api.md) for the complete message format and protocol specification.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The mobile channel receives real-time packet broadcasts from the same PubSub sources as the web LiveView clients:
|
||||||
|
- `StreamingPacketsPubSub` — bounds-filtered
|
||||||
|
- `SpatialPubSub` — viewport-filtered
|
||||||
|
- Per-callsign Phoenix PubSub topics
|
||||||
|
|
||||||
|
Packets are formatted into a mobile-optimized JSON structure and pushed to connected clients.
|
||||||
81
docs/web/liveview-map.md
Normal file
81
docs/web/liveview-map.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# LiveView Map
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Module:** `AprsmeWeb.MapLive.Index` (~2050 lines, the largest LiveView)
|
||||||
|
**Routes:** `/` (main map), `/:callsign` (tracked station)
|
||||||
|
|
||||||
|
The map is the primary user interface — a real-time Leaflet-based map displaying live APRS station positions, trails, weather data, and station information.
|
||||||
|
|
||||||
|
## MapLive Architecture
|
||||||
|
|
||||||
|
`MapLive.Index` is supported by 12 submodules under `lib/aprsme_web/live/map_live/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
MapLive.Index
|
||||||
|
├── Components — Reusable UI (map container, slideover panel, packet list)
|
||||||
|
├── PopupComponent — Marker popup rendering (callsign, weather, timestamp)
|
||||||
|
├── DataBuilder — Constructs map data payloads for JS hooks
|
||||||
|
├── DisplayManager — Manages what's shown on map (stations, trails, clusters)
|
||||||
|
├── HistoricalLoader — Loads historical packet data for selected station
|
||||||
|
├── Navigation — Determines map center/zoom from URL params
|
||||||
|
├── PacketBatcher — Batches incoming real-time packets for rendering
|
||||||
|
├── PacketProcessor — Processes raw packets into display-ready data
|
||||||
|
├── RfPath — RF path visualization (digipeater hops)
|
||||||
|
├── UrlParams — URL parameter sync with map state
|
||||||
|
└── (Plus shared modules in live/shared/)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-Time Updates
|
||||||
|
|
||||||
|
MapLive subscribes to:
|
||||||
|
- `StreamingPacketsPubSub` — viewport-filtered new packets
|
||||||
|
- `SpatialPubSub` — grid-indexed spatial delivery
|
||||||
|
- Phoenix PubSub topics for callsign-specific updates
|
||||||
|
|
||||||
|
Updates are batched via `PacketBatcher` to reduce DOM operations, then pushed to the Leaflet client via `push_event` (e.g., `"update_markers"`, `"update_clusters"`).
|
||||||
|
|
||||||
|
## Client-Side
|
||||||
|
|
||||||
|
The `APRSMap` JavaScript hook (`assets/js/map.ts`) handles:
|
||||||
|
- Leaflet map initialization with multiple tile layers
|
||||||
|
- Marker management (add, update, remove)
|
||||||
|
- Marker clustering at lower zoom levels
|
||||||
|
- Trail line rendering (`trail_manager.ts`)
|
||||||
|
- Viewport change tracking (pushed back to server for subscription bounds)
|
||||||
|
- URL parameter sync (center, zoom)
|
||||||
|
|
||||||
|
## Lazy Loading
|
||||||
|
|
||||||
|
The map bundle (Leaflet + plugins, ~300KB) is loaded only when a map page is visited, via `VendorLoader.loadMap()` in `assets/js/app.ts`. Non-map pages never download it.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Station Tracking
|
||||||
|
When visiting `/:callsign`, the map centers on that station and subscribes to its specific PubSub topic. A slideover panel shows station details.
|
||||||
|
|
||||||
|
### Trail Lines
|
||||||
|
Historical path of a station rendered as a polyline on the map. See [trail-line-visualization.md](../features/trail-line-visualization.md).
|
||||||
|
|
||||||
|
### RF Path
|
||||||
|
Digipeater hop visualization showing the path an APRS packet took through the network.
|
||||||
|
|
||||||
|
### Heatmap Clustering
|
||||||
|
At low zoom levels (≤8), individual markers are replaced by grid-based heatmap clusters via `Packets.Clustering` for performance.
|
||||||
|
|
||||||
|
### Weather Overlay
|
||||||
|
Weather station markers with temperature/wind indicators. Clicking opens a popup with current conditions, linking to the full weather page.
|
||||||
|
|
||||||
|
## Slideover Panel
|
||||||
|
|
||||||
|
When a station is selected, a slideover panel displays:
|
||||||
|
- Latest position and timestamp
|
||||||
|
- Course, speed, altitude
|
||||||
|
- Device/manufacturer info
|
||||||
|
- Link to full info page (`/info/:callsign`)
|
||||||
|
- Link to packet history (`/packets/:callsign`)
|
||||||
|
- Weather data (if weather station)
|
||||||
|
|
||||||
|
## URL State
|
||||||
|
|
||||||
|
Map state (center, zoom, selected callsign) is synced to URL query parameters via `UrlParams`, enabling shareable map views and browser back/forward navigation.
|
||||||
88
docs/web/liveview-pages.md
Normal file
88
docs/web/liveview-pages.md
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
# LiveView Pages
|
||||||
|
|
||||||
|
Non-map LiveView pages in the application.
|
||||||
|
|
||||||
|
## Packet Streams
|
||||||
|
|
||||||
|
### PacketsLive.Index (`/packets`)
|
||||||
|
- Real-time stream of all incoming packets (max 100)
|
||||||
|
- Subscribes to `"postgres:aprsme_packets"` Phoenix PubSub topic
|
||||||
|
- Uses `stream/4` for efficient DOM updates
|
||||||
|
|
||||||
|
### PacketsLive.CallsignView (`/packets/:callsign`)
|
||||||
|
- Filtered packet stream for a single callsign
|
||||||
|
- Subscribes to `"packets:#{callsign}"` topic
|
||||||
|
- Includes device information parsed per-packet
|
||||||
|
|
||||||
|
## Station Info
|
||||||
|
|
||||||
|
### InfoLive.Show (`/info/:callsign`)
|
||||||
|
- Comprehensive station information page
|
||||||
|
- Latest packet details, device identification
|
||||||
|
- Nearby stations via spatial query (neighbors)
|
||||||
|
- APRS path decoding — "heard by" and "stations heard" analysis
|
||||||
|
- Digipeater relationships
|
||||||
|
- APRS symbol rendering
|
||||||
|
- Embedded single-station map via `InfoMapComponent` (JS hook)
|
||||||
|
- Subscribes to `"packets:#{callsign}"` for live updates
|
||||||
|
|
||||||
|
## Weather
|
||||||
|
|
||||||
|
### WeatherLive.CallsignView (`/weather/:callsign`)
|
||||||
|
- Weather station data visualization with Chart.js charts
|
||||||
|
- Temperature, humidity, wind, pressure, rainfall trends
|
||||||
|
- Locale-aware unit conversions (metric/imperial)
|
||||||
|
- Subscribes to `"weather:#{callsign}"` for live updates
|
||||||
|
- Pushes `"update_weather_charts"` events to refresh Chart.js
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
### StatusLive.Index (`/status`)
|
||||||
|
- System status dashboard
|
||||||
|
- APRS-IS connection state, uptime, health score
|
||||||
|
- Cluster node information (when clustered)
|
||||||
|
- Polls every 5 seconds via `Process.send_after`
|
||||||
|
- Inline `render/1` (no template file)
|
||||||
|
- Subscribes to `"aprs_status"` topic
|
||||||
|
|
||||||
|
## Bad Packets
|
||||||
|
|
||||||
|
### BadPacketsLive.Index (`/badpackets`)
|
||||||
|
- Malformed/rejected packet viewer
|
||||||
|
- Shows raw packet data + error type/message
|
||||||
|
- Debounced refresh (2s delay)
|
||||||
|
- Subscribes to `"postgres:aprsme_events"`
|
||||||
|
|
||||||
|
## Static Pages
|
||||||
|
|
||||||
|
### AboutLive (`/about`)
|
||||||
|
- Simple about page, title assign only
|
||||||
|
|
||||||
|
### ApiDocsLive (`/api`)
|
||||||
|
- API documentation with interactive testing form
|
||||||
|
- Inline render (~970 lines)
|
||||||
|
- Self-documenting with example requests/responses
|
||||||
|
|
||||||
|
## Authentication Pages
|
||||||
|
|
||||||
|
All auth LiveViews use inline `render/1`:
|
||||||
|
|
||||||
|
| LiveView | Route | Auth Pipeline |
|
||||||
|
|---|---|---|
|
||||||
|
| `UserLoginLive` | `/users/log_in` | `redirect_if_user_is_authenticated` |
|
||||||
|
| `UserRegistrationLive` | `/users/register` | `redirect_if_user_is_authenticated` |
|
||||||
|
| `UserForgotPasswordLive` | `/users/reset_password` | `redirect_if_user_is_authenticated` |
|
||||||
|
| `UserResetPasswordLive` | `/users/reset_password/:token` | `redirect_if_user_is_authenticated` |
|
||||||
|
| `UserConfirmationLive` | `/users/confirm/:token` | `:current_user` |
|
||||||
|
| `UserConfirmationInstructionsLive` | `/users/confirm` | `:current_user` |
|
||||||
|
| `UserSettingsLive` | `/users/settings` | `require_authenticated_user` |
|
||||||
|
|
||||||
|
## Shared LiveView Modules
|
||||||
|
|
||||||
|
Located in `lib/aprsme_web/live/shared/`:
|
||||||
|
|
||||||
|
- `BoundsUtils` — Map boundary calculations
|
||||||
|
- `PacketHandler` — Shared packet enrichment
|
||||||
|
- `ParamUtils` — URL parameter parsing
|
||||||
|
- `PacketUtils` — Shared packet querying
|
||||||
|
- `CoordinateUtils` — Coordinate math
|
||||||
Loading…
Add table
Reference in a new issue