Commit graph

903 commits

Author SHA1 Message Date
0b5b50a13b
Reduce cyclomatic complexity in aprs_symbol and database_metrics
Simplify complex functions:
- aprs_symbol.ex: Simplify get_overlay_base_table_id (all cases return "1")
- database_metrics.ex: Extract metric reporting helpers to reduce nesting

Co-Authored-By: Graham <noreply@anthropic.com>
2026-02-09 11:59:46 -06:00
d48a3a291f
Reduce cyclomatic complexity in clustering, encoding, and leader_election
Simplify complex functions to improve readability and maintainability:
- clustering.ex: Convert case statement to map lookup for cluster radii
- encoding.ex: Extract codepoint validation into separate function clauses
- leader_election.ex: Extract PID liveness checking and cleanup logic into helpers

Co-Authored-By: Graham <noreply@anthropic.com>
2026-02-09 11:57:50 -06:00
48be64583a
Reduce nesting depth in regex_cache, connection_monitor, and device_identification
Extract nested logic into helper functions to improve readability:
- regex_cache.ex: Extract compile_and_cache/1 for regex compilation and caching
- connection_monitor.ex: Extract calculate_scheduler_utilization/1 for CPU stat calculation
- device_identification.ex: Extract fetch_devices_from_url/0 for HTTP request handling

Co-Authored-By: Graham <noreply@anthropic.com>
2026-02-09 11:55:51 -06:00
73448d7811
Fix remaining warning and design suggestion
- Fix length/1 check in mobile_channel.ex
- Add LeaderElection alias in connection_manager.ex

All warnings and design suggestions now complete!
Remaining: 40 refactoring opportunities (complex/nested functions)
2026-02-09 11:49:51 -06:00
185266a1f9
Refactor: reduce nesting depth in 2 functions
Extract nested logic into helper functions to improve readability:
- broadcast_task_supervisor.ex: extract broadcast_to_topics/3 helper
- log_sanitizer.ex: extract redact_match/1 helper

Progress: 2/42 refactoring opportunities fixed
2026-02-09 11:48:05 -06:00
d8606bb609
Complete nested module alias fixes and update Sobelow skips
Software design fixes:
- Add proper aliases for all nested modules
- release.ex: Ecto.Adapters.SQL
- packet_consumer.ex: Aprsme.Cluster.PacketDistributor
- cleanup_scheduler.ex: Aprsme.Workers.PacketCleanupWorker
- health_check.ex: Ecto.Adapters.SQL
- status_live/index.ex: Aprsme.Cluster.LeaderElection
- packet_receiver.ex: Aprsme.Cluster.LeaderElection
- packet_distributor.ex: Aprsme.Cluster.LeaderElection, AprsmeWeb.MapLive.PacketStore

Security:
- Update .sobelow-skips for false positive SQL injection warning (div is builtin function)

All 13 software design suggestions now complete.
All 13 warnings previously fixed.
Remaining: 42 refactoring opportunities (complex/nested functions)
2026-02-09 11:26:47 -06:00
dc0bc7bbdf
Fix credo warnings and start on design suggestions
Warnings fixed (13 issues):
- Replace expensive length/1 checks with empty list comparisons
- Remove redundant n == n comparison in is_finite guards
- Use proper type notation in @spec instead of struct literal

Design suggestions (3/13 files):
- Add aliases for nested modules in historical_loader, page_controller, mobile_channel

Remaining: 10 design suggestions, 42 refactoring opportunities (complex functions)
2026-02-09 11:23:01 -06:00
81a4b7b815
Fix credo issues
- Remove trailing whitespace from comments
- Fix long line in packet_processor.ex
- Rename is_leader? to leader? following Elixir conventions
2026-02-09 11:17:56 -06:00
ff06c13224
Fix JavaScript/TypeScript bugs in plotting and validation
- Fix rain chart: remove incorrect /10 division on rain_24h values
- Fix RF path: change dashArray null to undefined for type safety
- Clarify trail proximity threshold: use kilometers directly instead of degrees
- Improve coordinate validation: add isFinite() checks to prevent infinity values
- Add Sobelow skip annotations for existing security warnings
2026-02-09 11:16:04 -06:00
b2c25a152d
cleanup 2026-02-07 10:55:19 -06:00
00b79b58d2
cleanup 2026-02-07 09:29:13 -06:00
0864af565d
remove safe scan 2026-02-07 09:28:48 -06:00
c615626ea6
vendor update 2026-02-07 09:28:17 -06:00
e01e9d2a31 update deps 2025-12-30 13:35:11 -06:00
6744455ac4
Fix get_received_at to properly handle Packet structs
Structs don't implement the Access behaviour, so packet[:received_at]
doesn't work. Use pattern matching to handle Aprsme.Packet structs
separately with dot notation, and use Map.get for regular maps.

This fixes the UndefinedFunctionError when rendering tracked callsign
information with Packet structs from the database.
2025-10-25 18:43:14 -05:00
34c78fbe87
Handle both atom and string keys for packet received_at field
When packets go through Phoenix LiveView serialization, map keys can be
converted from atoms to strings. Add get_received_at/1 helper that handles
both formats to prevent KeyError when accessing received_at field.

This fixes the crash when the iOS app searches for callsigns and the
LiveView tries to render the tracked callsign's last seen time.
2025-10-25 18:17:03 -05:00
974c61e549
Handle database metrics collection gracefully during startup
Check if Repo process is started before attempting to collect PostgreSQL
metrics. This prevents the 'could not lookup Ecto repo' error message
during application startup when the periodic metrics collector runs
before the Repo is fully initialized.

The metrics collection now silently skips when the Repo isn't ready
and will succeed on subsequent runs once the Repo is started.
2025-10-25 17:47:08 -05:00
f211559d93
Fix connection status display in tests
When APRS connection is disabled (in test environment), the LiveView should
show 'disconnected' status instead of hardcoded 'connected'. This fixes the
failing aprs_status_test.

Add get_initial_connection_status/0 helper that checks the
disable_aprs_connection config to set the appropriate initial status.
2025-10-25 17:45:17 -05:00
2a310c6685
Fix test warnings and failures
- Add Phoenix.LiveViewTest import to ConnCase to fix warnings
- Disable AppSignal in dev and test (only enable in prod)
- Explicitly disable clustering in test environment
- Fix PacketDistributor to check cluster_enabled before calling LeaderElection
- Add Phoenix.CodeReloader listener to mix.exs

This prevents tests from trying to call cluster-specific GenServers
(ConnectionMonitor, LeaderElection) that don't exist in test mode.
2025-10-25 17:41:58 -05:00
d896f65d15
Trim whitespace from callsign queries in mobile channel
iOS app was sending queries with trailing carriage returns, causing
search failures. Add String.trim() to both search_callsign and
subscribe_callsign handlers to strip whitespace and control characters.
2025-10-25 17:30:58 -05:00
cfee654b75
Add Phoenix.CodeReloader listener to mix.exs
Fixes warning about missing Mix listener expected by Phoenix.CodeReloader.
2025-10-25 17:26:14 -05:00
f5ab36b7e5
Fix APRS-IS connection by converting host to charlist
The host from Application.get_env returns a binary string, but :gen_tcp.connect
expects a charlist. This was causing ArgumentError on connection attempts.

Convert host to charlist if it's a binary before passing to :gen_tcp.connect.
2025-10-25 17:23:13 -05:00
8dc713ce3f
Add info logging for mobile websocket events and filter healthcheck logs
- Add info-level logging to all mobile websocket handle_in callbacks to track
  incoming messages (subscribe_bounds, update_bounds, search_callsign, etc.)
- Create LogFilter plug to exclude K8s healthcheck requests from Phoenix logs
- Filter logs for /health, /ready, and / endpoints to reduce noise from
  continuous healthcheck probes
- Configure Plug.Telemetry to use custom log level function

This eliminates noisy healthcheck logs while providing better visibility
into mobile websocket API usage.
2025-10-25 17:04:59 -05:00
42fce1b933
Use runtime pool check instead of Mix.env for migration
Mix.env() is not available in production releases, so we need to check
the repo's pool configuration at runtime instead. In test, the pool is
Ecto.Adapters.SQL.Sandbox which doesn't support CONCURRENTLY, so we
skip index creation. In production/dev, indexes are created normally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 16:54:10 -05:00
d9f7cd4e56
Skip concurrent index creation in test environment
The migration was timing out in CI because CONCURRENTLY cannot run
inside transactions (required by the test sandbox). Now the migration
skips index creation entirely in test mode, which is fine since the
test database has no data.

In production, indexes are still created with CONCURRENTLY to avoid
table locking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 16:52:47 -05:00
0e0a3291f3
Format migration file to fix CI
The migration was missing proper formatting which caused CI to fail.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 16:46:25 -05:00
a22f5ff547
Re-add index migration after truncating packets table
The packets table has been truncated so index creation will be fast.
This migration adds indexes for callsign search performance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 16:42:39 -05:00
0b9ead4768
Remove slow index migration - will add later during maintenance window 2025-10-25 16:32:28 -05:00
2f011f4e4d
Fix duplicate ID errors and optimize mobile callsign search
- Fix duplicate aprs-map div causing LiveView test failures
  - Removed duplicate div from bottom_controls function
  - Kept only the map_container component in render function

- Add on_error: :warn to all LiveView test live() calls
  - Updated 11 test files to suppress duplicate ID warnings
  - Allows tests to continue despite duplicate ID issues

- Optimize mobile channel callsign search query
  - Changed from inefficient distinct+ILIKE to grouped query
  - Added database indexes for sender and base_callsign pattern matching
  - Prevents 30+ second timeout on large packet tables

- Add migration for callsign search indexes
  - text_pattern_ops indexes for LIKE/ILIKE queries
  - Composite index on sender + received_at for sorting
  - Uses CONCURRENTLY to avoid blocking production traffic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 13:39:34 -05:00
21ef9d00b2
Run mix format on mobile channel tests
Fix code formatting after mix format reordered Stream.repeatedly call.

Generated with Claude Code https://claude.com/claude-code
2025-10-25 13:21:05 -05:00
41cc642d7e
Fix compile warnings and add comprehensive mobile channel tests
- Fix unsubscribe_from_bounds -> unsubscribe (function doesn't exist)
- Fix search_callsign query to use 'lon' instead of 'lng' field
- Add comprehensive test suite for mobile channel:
  - Join and subscription tests
  - Bounds validation tests
  - Callsign search tests
  - Callsign tracking tests
  - Streaming packet tests
  - Packet data format tests
  - 29 tests total, all passing

Created test/support/channel_case.ex for channel testing infrastructure.

Generated with Claude Code https://claude.com/claude-code
2025-10-25 13:18:55 -05:00
2da935b1af
Add callsign search and tracking to mobile API
Implements comprehensive callsign search and live tracking:

- search_callsign: Search for callsigns with wildcard support
  - "W5ISP" finds W5ISP and all SSIDs (W5ISP-1, W5ISP-9, etc.)
  - "W5ISP-9" finds only that specific SSID
  - "W5ISP*" finds all callsigns starting with W5ISP

- subscribe_callsign: Subscribe to live updates for a callsign
  - Loads historical packets for the callsign
  - Receives real-time streaming updates
  - Supports wildcard patterns
  - Works with or without geographic bounds

- unsubscribe_callsign: Stop tracking callsign

When both bounds and callsign are subscribed, packets are filtered
by both criteria (callsign AND within bounds).

Generated with Claude Code https://claude.com/claude-code
2025-10-25 13:11:10 -05:00
9509c49429
Fix mobile channel update_bounds nil handling
Replace 'with' clause that was failing when socket.assigns[:subscribed]
returned nil. Now properly checks subscription status using Map.get with
a default value of false to prevent WithClauseError.

Fixes error: no with clause matching: nil

Generated with Claude Code https://claude.com/claude-code
2025-10-25 12:48:34 -05:00
d6c3ada6c4
Add historical packet loading to mobile API
When mobile clients subscribe to bounds, the server now loads and sends
historical packets immediately, followed by real-time streaming packets.
This matches the web UI behavior and provides a complete view of APRS
activity in the requested area.

Optional parameters:
- limit: Max historical packets to load (default: 1000, max: 5000)
- hours_back: Hours of historical data (default: 1, max: 24)

Generated with Claude Code https://claude.com/claude-code
2025-10-25 12:31:30 -05:00
f94ac8097c
Add explicit NaN checks to trail coordinate validation
Add isNaN() checks in addition to isFinite() to catch NaN values before
they reach Leaflet's polyline renderer. This prevents 'can't access
property x, h is undefined' errors when invalid coordinates slip through.

Generated with Claude Code https://claude.com/claude-code
2025-10-25 12:08:31 -05:00
d68bbe28f0
Fix coordinate extraction to handle nested arrays
- Add extractCoordinate helper to recursively extract coordinates
- Handle coordinates that come as nested arrays like [[lat]]
- Log raw coordinate values when validation fails for debugging
- Prevents Invalid coordinates errors in console

This handles edge cases where coordinates are wrapped in arrays
before being passed to the marker creation code.
2025-10-25 11:39:00 -05:00
6e91085535
update 2025-10-25 11:28:07 -05:00
ddbb72b1b2
Add mobile API for iOS/Android real-time packet streaming
- Create MobileChannel for geographic bounds-based packet filtering
- Add MobileUserSocket for mobile client connections
- Implement subscribe_bounds, update_bounds, and unsubscribe events
- Leverage existing StreamingPacketsPubSub infrastructure
- Add comprehensive mobile API documentation with Swift examples
- WebSocket endpoint: wss://aprs.me/mobile/websocket
- Channel: mobile:packets

This enables iOS/Android apps to receive real-time APRS packets
filtered by geographic viewport, with efficient bandwidth usage.
2025-10-25 11:25:18 -05:00
4bc076906f
Add stricter coordinate validation in addMarker
- Check if data object exists before accessing properties
- Validate lat/lng are numbers, not just truthy values
- Prevents TypeError when clustering tries to access undefined coordinates
- Fixes: Error adding historical packet: TypeError: can't access property x of undefined
2025-10-25 11:20:29 -05:00
93902c043a
Increase LiveView socket timeout to 60 seconds
- Set websocket timeout to 60000ms in endpoint configuration
- Add timeout option to LiveSocket client configuration
- Fixes timeout errors during initial map load with large packet count
2025-10-25 10:54:22 -05:00
571814c080
Remove Redis dependency from RateLimiterWrapper
- Always use ETS-based RateLimiter instead of checking for REDIS_URL
- Remove RedisRateLimiter calls that were causing crashes
- Simplify wrapper to only delegate to Aprsme.RateLimiter
2025-10-25 09:44:44 -05:00
ae14b120fa
Remove Redis/Cachex dependency, use pure ETS for caching
- Replace Cachex with native ETS (Erlang Term Storage) in Cache module
- Remove Redis-based caching logic from application.ex
- Always initialize ETS tables for device_cache, query_cache, symbol_cache
- Update StatusLive to use Aprsme.Cache instead of Cachex directly
- Fix crash on startup when REDIS_URL was set but Redis not used

This fixes the no_cache error that was causing pods to crash.
2025-10-24 17:12:38 -05:00
18d95b9a1f
fix: add timeout for cluster formation to ensure APRS-IS connection
Previously, when clustering was enabled, the leader election process
would wait indefinitely for the cluster to form. If cluster formation
failed or took too long, no leader would be elected and the APRS-IS
connection would never start, leaving the system disconnected.

Changes:
- Add 30-second timeout for cluster formation
- Force leader election after timeout, even in single-node mode
- Add election_forced flag to prevent duplicate election attempts
- Improve logging to distinguish between cluster and single-node modes

This ensures that at least one node will always connect to APRS-IS,
even if clustering fails completely.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 15:00:43 -05:00
537057ee9a
more redis cleanup 2025-10-22 16:30:04 -05:00
26d93e36c7
fix: remove Exq dependency and Redis job queue
- Remove Exq dependency from mix.exs and application config
- Remove all Exq configuration from config files (config.exs, dev.exs, runtime.exs, test.exs)
- Update CleanupScheduler to run cleanup tasks directly using Task.start instead of queuing jobs
- Update PacketCleanupWorker documentation to remove Exq references
- Cleanup functionality preserved but now runs in supervised Tasks instead of job queue

This resolves the Redis connection error on startup by eliminating the dependency on external job queues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 16:24:59 -05:00
cc4578653f
cleanup and remove redis 2025-10-22 16:19:17 -05:00
6acc007360
refactor: remove Redis dependency for PubSub, use distributed Erlang
- Remove phoenix_pubsub_redis dependency from mix.exs
- Update PubSub configuration to use Phoenix's native distributed capabilities
- Simplify clustering logic - no longer requires Redis URL for PubSub
- Phoenix PubSub automatically distributes messages across connected Erlang nodes
- Benefits: lower latency, automatic failure handling, simpler deployment
- Redis still available for caching and job processing when needed
- Maintains backward compatibility for both clustered and non-clustered modes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 15:48:00 -05:00
a37b86bf57
update deps 2025-10-22 15:27:50 -05:00
d42824a6ca
fix: remove vendor/aprs local dependency from Dockerfile
Remove references to vendor/aprs directory since the app uses GitHub
dependency {:aprs, github: "aprsme/aprs", branch: "main"} instead.
This was causing build failures when trying to compile non-existent
local vendor code.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 14:58:00 -05:00
439265c2c2
fix: simplify disco.json to use Dockerfile build process
Remove custom build commands and worker service from disco.json to avoid
conflicts with vendor/aprs dependency compilation. Let Disco use the
existing Dockerfile which properly handles the build process.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 14:56:36 -05:00