- Add shuffle source to Liquidsoap config (96kbps MP3)
- Add shuffle option to all UI quality selectors (frame, popout, front page)
- Make now-playing APIs mount-aware for correct metadata display
- Implement separate recently-played lists for curated vs shuffle streams
- Speed up now-playing and recently-played refresh on stream change
- Fix clean shutdown of stats polling thread (positional timeout arg)
- Widen quality selector dropdown for shuffle label
- Fix listener_count accumulation bug: use GREATEST() instead of + to track
peak concurrent listeners per day rather than cumulative count
- Migrate all inline JavaScript from templates to ParenScript:
- admin.ctml: listener stats, geo stats, password reset -> admin.lisp
- audio-player-frame.ctml: stream player logic -> stream-player.lisp (new)
- popout-player.ctml: popout player logic -> stream-player.lisp (shared)
- frameset-wrapper.ctml: frame-busting -> frameset-utils.lisp (new)
- profile.ctml: removed redundant inline init (already in profile.lisp)
- Fix API route detection: handle URIs with or without leading slash
- Add routes to serve new ParenScript-generated JS files
- Update ASDF system definition with new ParenScript components
Tested locally for 8+ hours with no issues.
- Add get-geo-stats-by-city function for city-level queries
- Add /api/asteroid/stats/geo/cities endpoint
- Add expandable country rows in admin geo stats table
- Click country to expand/collapse city breakdown
- Update update-geo-stats to accept optional city parameter
- Update get-cached-geo to cache and return city along with country
- Update collect-geo-stats-for-mount and collect-geo-stats-from-web-listeners
to track by country+city
- Revert migration to keep UNIQUE(date, country_code, city) constraint
Instead of relying on Icecast's listener IPs (which show proxy IPs
behind HAProxy), capture real client IPs from X-Forwarded-For header
when users visit the front page or audio player frame.
Radiance automatically extracts X-Forwarded-For into (remote *request*).
Changes:
- Add *web-listeners* hash table to track visitors with real IPs
- Add register-web-listener to capture IPs during page requests
- Add collect-geo-stats-from-web-listeners for polling
- Call register-web-listener from front-page and audio-player-frame
- Filter out private/internal IPs (172.x, 192.168.x, 10.x, 127.x)
- Remove session requirement - use IP hash as key for anonymous visitors
Drakma returns a byte vector, not a string. The code was passing
this directly to cl-json:decode-json-from-string which expects
a string, causing the lookup to silently fail.
- Add geo IP lookup for listener locations (ip-api.com)
- Add geo stats table with country flags to admin dashboard
- Fix listener stats table alignment with proper centering
- Fix Now Playing display to update without requiring audio playback
- Add caching for geo lookups to reduce API calls
- Add database schema for listener snapshots, sessions, and aggregates
- Implement background polling of Icecast admin XML stats
- Add API endpoints for current, daily, and geo stats
- Add listener stats section to admin dashboard with auto-refresh
- GDPR compliant: IP hashing, data retention cleanup