feat: Add avatar upload and fix authentication errors

Avatars:
- Add avatar_path column to USERS table (migration 006)
- Upload API endpoint /api/asteroid/user/avatar/upload
- Profile page shows avatar with hover-to-change overlay
- Default SVG avatar for users without uploaded image
- Avatars stored in static/avatars/ directory

Fixes:
- 401 errors now return proper JSON instead of 500
- SQL escaping for history recording (single quotes)
- Added debug logging for history/record API
- Avatar container has background color for visibility

For production: run migrations/006-user-avatars.sql
This commit is contained in:
glenneth 2025-12-21 09:07:56 +03:00
parent a2ebc415f2
commit 8f5fe7534d
11 changed files with 459 additions and 104 deletions

View File

@ -0,0 +1,14 @@
-- Migration 006: User Avatars
-- Adds avatar support to user profiles
-- Add avatar_path column to USERS table
ALTER TABLE "USERS" ADD COLUMN IF NOT EXISTS avatar_path TEXT;
-- Grant permissions
GRANT ALL PRIVILEGES ON "USERS" TO asteroid;
-- Verification
DO $$
BEGIN
RAISE NOTICE 'Migration 006: User avatars column added successfully!';
END $$;

View File

@ -232,6 +232,45 @@
(defun load-more-favorites ()
(show-message "Loading more favorites..." "info"))
(defun load-avatar ()
(ps:chain
(fetch "/api/asteroid/user/avatar")
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
(let ((data (or (ps:@ result data) result)))
(when (and (= (ps:@ data status) "success")
(ps:@ data avatar_path))
(let ((img (ps:chain document (get-element-by-id "user-avatar"))))
(when img
(setf (ps:@ img src) (ps:@ data avatar_path))))))))
(catch (lambda (error)
(ps:chain console (log "No avatar set or error loading:" error))))))
(defun upload-avatar (input)
(let ((file (ps:getprop (ps:@ input files) 0)))
(when file
(let ((form-data (ps:new (-form-data))))
(ps:chain form-data (append "avatar" file))
(ps:chain form-data (append "filename" (ps:@ file name)))
(show-message "Uploading avatar..." "info")
(ps:chain
(fetch "/api/asteroid/user/avatar/upload"
(ps:create :method "POST"
:body form-data))
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
(let ((data (or (ps:@ result data) result)))
(if (= (ps:@ data status) "success")
(progn
(let ((img (ps:chain document (get-element-by-id "user-avatar"))))
(when img
(setf (ps:@ img src) (+ (ps:@ data avatar_path) "?" (ps:chain -date (now))))))
(show-message "Avatar updated!" "success"))
(show-message "Failed to upload avatar" "error")))))
(catch (lambda (error)
(ps:chain console (error "Error uploading avatar:" error))
(show-message "Error uploading avatar" "error"))))))))
(defun load-activity-chart ()
(ps:chain
(fetch "/api/asteroid/user/activity?days=30")
@ -303,7 +342,8 @@
(load-recent-tracks)
(load-favorites)
(load-top-artists)
(load-activity-chart))
(load-activity-chart)
(load-avatar))
;; Action functions
(defun load-more-recent-tracks ()

View File

@ -1,83 +1,207 @@
#EXTM3U
#PLAYLIST:Midnight Ambient
#PHASE:Midnight Ambient
#PLAYLIST:Morning Drift
#PHASE:Morning Drift
#DURATION:6 hours (approx)
#CURATOR:Asteroid Radio
#DESCRIPTION:Deep, dark ambient for the late night hours (00:00-06:00)
#DESCRIPTION:Lighter, awakening ambient for the morning hours (06:00-12:00)
#EXTINF:-1,Biosphere - The Petrified Forest
/app/music/Biosphere - The Petrified Forest (2017) - CD FLAC/04. Biosphere - The Petrified Forest.flac
#EXTINF:-1,Labradford - S
/app/music/Labradford/1997 - Mi Media Naranja/1 S.flac
#EXTINF:-1,Tim Hecker - Seasick
/app/music/Tim Hecker - The North Water Original Score (2021 - WEB - FLAC)/Tim Hecker - The North Water (Original Score) - 01 Seasick.flac
#EXTINF:-1,Pye Corner Audio - Hollow Earth
/app/music/Pye Corner Audio/2019 - Hollow Earth (WEB, #GBX032 DL)/01 - Hollow Earth.mp3
#EXTINF:-1,Dead Voices On Air - Red Howls
/app/music/Dead Voices On Air - Ghohst Stories (FLAC)/01 - Red Howls.flac
#EXTINF:-1,Tangerine Dream - The Seventh Propellor of Silence
/app/music/Tangerine Dream - Ambient Monkeys (flac)/03 Tangerine Dream - The Seventh Propellor of Silence.flac
#EXTINF:-1,Biosphere - Fall Asleep For Me
/app/music/Biosphere - Departed Glories (2016) - FLAC WEB/17 - Fall Asleep For Me.flac
#EXTINF:-1,Locrian - Arc of Extinction
/app/music/Locrian - Infinite Dissolution (2015) - WEB FLAC/01 - Arc of Extinction.flac
#EXTINF:-1,FSOL - Polarize
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/01 - Polarize.flac
#EXTINF:-1,Marconi Union - Sleeper
/app/music/Marconi Union - Ghost Stations (2016 - WEB - FLAC)/01. Marconi Union - Sleeper.flac
#EXTINF:-1,Labradford - G
/app/music/Labradford/1997 - Mi Media Naranja/2 G.flac
#EXTINF:-1,Biosphere - This Is The End
/app/music/Biosphere - The Petrified Forest (2017) - CD FLAC/06. Biosphere - This Is The End.flac
#EXTINF:-1,Pye Corner Audio - Descent
/app/music/Pye Corner Audio/2019 - Hollow Earth (WEB, #GBX032 DL)/02 - Descent.mp3
#EXTINF:-1,Brian Eno - Reflection
/app/music/Brian Eno/2017 - Reflection/01. Reflection.mp3
#EXTINF:-1,Dead Voices On Air - On Winters Gibbet
/app/music/Dead Voices On Air - Ghohst Stories (FLAC)/02 - On Winters Gibbet.flac
#EXTINF:-1,Tim Hecker - Left On The Ice
/app/music/Tim Hecker - The North Water Original Score (2021 - WEB - FLAC)/Tim Hecker - The North Water (Original Score) - 05 Left On The Ice.flac
#EXTINF:-1,Autechre - Further
/app/music/Autechre/1994 - Amber/08 Further.flac
#EXTINF:-1,Tangerine Dream - Moon Marble
/app/music/Tangerine Dream - Ambient Monkeys (flac)/06 Tangerine Dream - Moon Marble.flac
#EXTINF:-1,Biosphere - Sweet Dreams Form A Shade
/app/music/Biosphere - Departed Glories (2016) - FLAC WEB/07 - Sweet Dreams Form A Shade.flac
#EXTINF:-1,Locrian - Dark Shales
/app/music/Locrian - Infinite Dissolution (2015) - WEB FLAC/02 - Dark Shales.flac
#EXTINF:-1,FSOL - Forest Soundbed
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/06 - Forest Soundbed.flac
#EXTINF:-1,Pye Corner Audio - Subterranean Lakes
/app/music/Pye Corner Audio/2019 - Hollow Earth (WEB, #GBX032 DL)/09 - Subterranean Lakes.mp3
#EXTINF:-1,Labradford - WR
/app/music/Labradford/1997 - Mi Media Naranja/3 WR.flac
#EXTINF:-1,Bark Psychosis - Hex
/app/music/Bark Psychosis/1994 - Hex/01 The Loom.flac
#EXTINF:-1,Biosphere - Turned To Stone
/app/music/Biosphere - The Petrified Forest (2017) - CD FLAC/03. Biosphere - Turned To Stone.flac
#EXTINF:-1,Tim Hecker - Winter's Coming
/app/music/Tim Hecker - The North Water Original Score (2021 - WEB - FLAC)/Tim Hecker - The North Water (Original Score) - 10 Winter's Coming.flac
#EXTINF:-1,Dead Voices On Air - On Wicca Way
/app/music/Dead Voices On Air - Ghohst Stories (FLAC)/03 - On Wicca Way.flac
#EXTINF:-1,Marconi Union - Abandoned - In Silence
/app/music/Marconi Union - Ghost Stations (2016 - WEB - FLAC)/03. Marconi Union - Abandoned - In Silence.flac
#EXTINF:-1,Pye Corner Audio - Deeper Dreaming
/app/music/Pye Corner Audio/2019 - Hollow Earth (WEB, #GBX032 DL)/13 - Deeper Dreaming.mp3
#EXTINF:-1,Tangerine Dream - Myopia World
/app/music/Tangerine Dream - Ambient Monkeys (flac)/12 Tangerine Dream - Myopia World.flac
#EXTINF:-1,Biosphere - Departed Glories
/app/music/Biosphere - Departed Glories (2016) - FLAC WEB/09 - Departed Glories.flac
#EXTINF:-1,Locrian - The Future of Death
/app/music/Locrian - Infinite Dissolution (2015) - WEB FLAC/04 - The Future of Death.flac
#EXTINF:-1,Labradford - V
/app/music/Labradford/1997 - Mi Media Naranja/6 V.flac
#EXTINF:-1,FSOL - Solace
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/23 - Solace.flac
#EXTINF:-1,Pye Corner Audio - Buried Memories
/app/music/Pye Corner Audio/2019 - Hollow Earth (WEB, #GBX032 DL)/12 - Buried Memories.mp3
#EXTINF:-1,Tim Hecker - Twinkle In The Wasteland
/app/music/Tim Hecker - The North Water Original Score (2021 - WEB - FLAC)/Tim Hecker - The North Water (Original Score) - 14 Twinkle In The Wasteland.flac
#EXTINF:-1,Locrian - Heavy Water
/app/music/Locrian - Infinite Dissolution (2015) - WEB FLAC/08 - Heavy Water.flac
#EXTINF:-1,Tape Loop Orchestra - 1953 Culture Festival
/app/music/Tape Loop Orchestra/2009 - 1953 Culture Festival/01 - 1953 Culture Festival.flac
#EXTINF:-1,Brian Eno - Emerald And Lime
/app/music/Brian Eno/2011 - Small Craft On a Milk Sea/A1 Emerald And Lime.flac
#EXTINF:-1,Tycho - Glider
/app/music/Tycho - Epoch (Deluxe Version) (2019) [WEB FLAC16-44.1]/01 - Glider.flac
#EXTINF:-1,Biosphere - Drifter
/app/music/Biosphere - The Petrified Forest (2017) - CD FLAC/01. Biosphere - Drifter.flac
#EXTINF:-1,Four Tet - Alap
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/01 Alap.flac
#EXTINF:-1,Johann Johannsson - Cambridge, 1963
/app/music/Johann Johannsson - The Theory of Everything (2014) [FLAC]/01 - Cambridge, 1963.flac
#EXTINF:-1,Ulrich Schnauss - Negative Sunrise (2019 Version)
/app/music/Ulrich Schnauss - No Further Ahead Than Tomorrow (2020) - WEB FLAC/09. Negative Sunrise (2019 Version).flac
#EXTINF:-1,Kiasmos - Lit
/app/music/Kiasmos/2014 - Kiasmos/01 - Lit.flac
#EXTINF:-1,FSOL - Mountain Path
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/02 - Mountain Path.flac
#EXTINF:-1,Brian Eno - Garden of Stars
/app/music/Brian Eno/2022 - ForeverAndEverNoMore/04 Garden of Stars.flac
#EXTINF:-1,Clark - Kiri's Glee
/app/music/Clark - Kiri Variations (2019) [WEB FLAC]/04 - Kiri's Glee.flac
#EXTINF:-1,Tycho - Source
/app/music/Tycho - Epoch (Deluxe Version) (2019) [WEB FLAC16-44.1]/07 - Source.flac
#EXTINF:-1,Biosphere - Out Of The Cradle
/app/music/Biosphere - Departed Glories (2016) - FLAC WEB/01 - Out Of The Cradle.flac
#EXTINF:-1,Tangerine Dream - Token from Birdland
/app/music/Tangerine Dream - Ambient Monkeys (flac)/01 Tangerine Dream - Token from Birdland.flac
#EXTINF:-1,Four Tet - Scientists
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/06 Scientists.flac
#EXTINF:-1,Ulrich Schnauss & Jonas Munk - Solitary Falling
/app/music/Ulrich Schnauss & Jonas Munk - Eight Fragments Of An Illusion (2021) - WEB FLAC/03. Solitary Falling.flac
#EXTINF:-1,Proem - Modern Rope
/app/music/Proem - 2018 Modern Rope (WEB)/05. Modern Rope.flac
#EXTINF:-1,Johann Johannsson - Rowing
/app/music/Johann Johannsson - The Theory of Everything (2014) [FLAC]/02 - Rowing.flac
#EXTINF:-1,Kiasmos - Held
/app/music/Kiasmos/2014 - Kiasmos/02 - Held.flac
#EXTINF:-1,Brian Eno - Complex Heaven
/app/music/Brian Eno/2011 - Small Craft On a Milk Sea/A2 Complex Heaven.flac
#EXTINF:-1,Biosphere - Skålbrekka
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/01 - Skålbrekka.flac
#EXTINF:-1,FSOL - Thought Pattern
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/03 - Thought Pattern.flac
#EXTINF:-1,Tycho - Into The Woods
/app/music/Tycho - Simulcast (2020) [WEB FLAC]/04 - Into The Woods.flac
#EXTINF:-1,arovane - hymn
/app/music/arovane - Wirkung (2020) [WEB FLAC16]/12. arovane - hymn.flac
#EXTINF:-1,Four Tet - Green
/app/music/Four Tet - Sixteen Oceans (2020) {Text Records - TEXT051} [CD FLAC]/12 - Four Tet - Green.flac
#EXTINF:-1,Clark - Primary Pluck
/app/music/Clark - Kiri Variations (2019) [WEB FLAC]/08 - Primary Pluck.flac
#EXTINF:-1,Biosphere - Strandby
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/02 - Strandby.flac
#EXTINF:-1,Johann Johannsson - The Dreams That Stuff Is Made Of
/app/music/Johann Johannsson - The Theory of Everything (2014) [FLAC]/11 - The Dreams That Stuff Is Made Of.flac
#EXTINF:-1,Ulrich Schnauss & Jonas Munk - Polychrome
/app/music/Ulrich Schnauss & Jonas Munk - Eight Fragments Of An Illusion (2021) - WEB FLAC/08. Polychrome.flac
#EXTINF:-1,Kiasmos - Swayed
/app/music/Kiasmos/2014 - Kiasmos/04 - Swayed.flac
#EXTINF:-1,Brian Eno - These Small Noises
/app/music/Brian Eno/2022 - ForeverAndEverNoMore/09 These Small Noises.flac
#EXTINF:-1,Tycho - Ascension
/app/music/Thievery Corporation and Tycho - Fragments Ascension EP (flac)/3. Tycho - Ascension.flac
#EXTINF:-1,FSOL - Imagined Friends
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/15 - Imagined Friends.flac
#EXTINF:-1,Biosphere - Lysbotn
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/11 - Lysbotn.flac
#EXTINF:-1,Boards of Canada - In a Beautiful Place Out in the Country
/app/music/Boards of Canada/In a Beautiful Place Out in the Country/01 Kid for Today.flac
#EXTINF:-1,Brian Eno - Making Gardens Out of Silence
/app/music/Brian Eno/2022 - ForeverAndEverNoMore/10 Making Gardens Out of Silence.flac
#EXTINF:-1,Tangerine Dream - Virtue Is Its Own Reward
/app/music/Tangerine Dream - Ambient Monkeys (flac)/10 Tangerine Dream - Virtue Is Its Own Reward.flac
#EXTINF:-1,Brian Eno - Small Craft On A Milk Sea
/app/music/Brian Eno/2011 - Small Craft On a Milk Sea/A3 Small Craft On A Milk Sea.flac
#EXTINF:-1,Tycho - Horizon
/app/music/Tycho - Epoch (Deluxe Version) (2019) [WEB FLAC16-44.1]/02 - Horizon.flac
#EXTINF:-1,Four Tet - Two Thousand And Seventeen
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/02 Two Thousand And Seventeen.flac
#EXTINF:-1,Boards of Canada - Dayvan Cowboy
/app/music/Boards of Canada/Trans Canada Highway/01 - Dayvan Cowboy.mp3
#EXTINF:-1,Ulrich Schnauss - Melts into Air (2019 Version)
/app/music/Ulrich Schnauss - No Further Ahead Than Tomorrow (2020) - WEB FLAC/01. Melts into Air (2019 Version).flac
#EXTINF:-1,arovane - olopp_eleen
/app/music/arovane - Wirkung (2020) [WEB FLAC16]/01. arovane - olopp_eleen.flac
#EXTINF:-1,Biosphere - Bergsbotn
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/03 - Bergsbotn.flac
#EXTINF:-1,F.S.Blumm & Nils Frahm - Perff
/app/music/F.S Blumm and Nils Frahm - Music For Wobbling Music Versus Gravity (2013 - WEB - FLAC)/02. F.S.Blumm & Nils Frahm - Perff.flac
#EXTINF:-1,Clark - Simple Homecoming Loop
/app/music/Clark - Kiri Variations (2019) [WEB FLAC]/02 - Simple Homecoming Loop.flac
#EXTINF:-1,Tycho - Slack
/app/music/Tycho - Epoch (Deluxe Version) (2019) [WEB FLAC16-44.1]/03 - Slack.flac
#EXTINF:-1,Johann Johannsson - A Game Of Croquet
/app/music/Johann Johannsson - The Theory of Everything (2014) [FLAC]/07 - A Game Of Croquet.flac
#EXTINF:-1,FSOL - Motioned
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/04 - Motioned.flac
#EXTINF:-1,Brian Eno - Flint March
/app/music/Brian Eno/2011 - Small Craft On a Milk Sea/A4 Flint March.flac
#EXTINF:-1,Four Tet - Lush
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/05 Lush.flac
#EXTINF:-1,Boards of Canada - Turquoise Hexagon Sun
/app/music/Boards of Canada/Hi Scores/02 - Turquoise Hexagon Sun.mp3
#EXTINF:-1,Ulrich Schnauss - Love Grows Out of Thin Air (2019 Version)
/app/music/Ulrich Schnauss - No Further Ahead Than Tomorrow (2020) - WEB FLAC/02. Love Grows Out of Thin Air (2019 Version).flac
#EXTINF:-1,arovane - wirkung
/app/music/arovane - Wirkung (2020) [WEB FLAC16]/02. arovane - wirkung.flac
#EXTINF:-1,Biosphere - Berg
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/05 - Berg.flac
#EXTINF:-1,God is an Astronaut - Komorebi
/app/music/God is an Astronaut - Epitaph (2018) WEB FLAC/05. Komorebi.flac
#EXTINF:-1,Tycho - Weather
/app/music/Tycho - Simulcast (2020) [WEB FLAC]/01 - Weather.flac
#EXTINF:-1,Port Blue - Sunset Cruiser
/app/music/Port Blue - The Airship (2007)/04. Sunset Cruiser.flac
#EXTINF:-1,F.S.Blumm & Nils Frahm - Exercising Levitation
/app/music/F.S Blumm and Nils Frahm - Music For Wobbling Music Versus Gravity (2013 - WEB - FLAC)/07. F.S.Blumm & Nils Frahm - Exercising Levitation.flac
#EXTINF:-1,Four Tet - You Are Loved
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/08 You Are Loved.flac
#EXTINF:-1,Ulrich Schnauss & Jonas Munk - Asteroid 2467
/app/music/Ulrich Schnauss & Jonas Munk - Eight Fragments Of An Illusion (2021) - WEB FLAC/01. Asteroid 2467.flac
#EXTINF:-1,Boards of Canada - Left Side Drive
/app/music/Boards of Canada/Trans Canada Highway/02 - Left Side Drive.mp3
#EXTINF:-1,Brian Eno - Who Gives a Thought
/app/music/Brian Eno/2022 - ForeverAndEverNoMore/01 Who Gives a Thought.flac
#EXTINF:-1,arovane - find
/app/music/arovane - Wirkung (2020) [WEB FLAC16]/03. arovane - find.flac
#EXTINF:-1,Tycho - Receiver
/app/music/Tycho - Epoch (Deluxe Version) (2019) [WEB FLAC16-44.1]/04 - Receiver.flac
#EXTINF:-1,Johann Johannsson - The Origins Of Time
/app/music/Johann Johannsson - The Theory of Everything (2014) [FLAC]/08 - The Origins Of Time.flac
#EXTINF:-1,FSOL - Lichaen
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/05 - Lichaen.flac
#EXTINF:-1,Biosphere - Kyle
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/06 - Kyle.flac
#EXTINF:-1,Port Blue - The Grand Staircase
/app/music/Port Blue - The Airship (2007)/03. The Grand Staircase.flac
#EXTINF:-1,Vector Lovers - City Lights From A Train
/app/music/Vector Lovers/2005 - Capsule For One/01 - City Lights From A Train.mp3
#EXTINF:-1,Four Tet - Teenage Birdsong
/app/music/Four Tet - Sixteen Oceans (2020) {Text Records - TEXT051} [CD FLAC]/04 - Four Tet - Teenage Birdsong.flac
#EXTINF:-1,Ulrich Schnauss - The Magic in You (2019 Version)
/app/music/Ulrich Schnauss - No Further Ahead Than Tomorrow (2020) - WEB FLAC/03. The Magic in You (2019 Version).flac
#EXTINF:-1,Boards of Canada - Oirectine
/app/music/Boards of Canada/Twoism/02 - Oirectine.mp3
#EXTINF:-1,Clark - Bench
/app/music/Clark - Kiri Variations (2019) [WEB FLAC]/03 - Bench.flac
#EXTINF:-1,Tycho - Alright
/app/music/Tycho - Simulcast (2020) [WEB FLAC]/02 - Alright.flac
#EXTINF:-1,Brian Eno - Lesser Heaven
/app/music/Brian Eno/2011 - Small Craft On a Milk Sea/C3 Lesser Heaven.flac
#EXTINF:-1,arovane - sloon
/app/music/arovane - Wirkung (2020) [WEB FLAC16]/05. arovane - sloon.flac
#EXTINF:-1,God is an Astronaut - Epitaph
/app/music/God is an Astronaut - Epitaph (2018) WEB FLAC/01. Epitaph.flac
#EXTINF:-1,F.S.Blumm & Nils Frahm - Silently Sharing
/app/music/F.S Blumm and Nils Frahm - Music For Wobbling Music Versus Gravity (2013 - WEB - FLAC)/10. F.S.Blumm & Nils Frahm - Silently Sharing.flac
#EXTINF:-1,Biosphere - Fjølhøgget
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/07 - Fjølhøgget.flac
#EXTINF:-1,Port Blue - Over Atlantic City
/app/music/Port Blue - The Airship (2007)/02. Over Atlantic City.flac
#EXTINF:-1,Johann Johannsson - Viva Voce
/app/music/Johann Johannsson - The Theory of Everything (2014) [FLAC]/09 - Viva Voce.flac
#EXTINF:-1,Tangerine Dream - Symphony in A-minor
/app/music/Tangerine Dream - Ambient Monkeys (flac)/02 Tangerine Dream - Symphony in A-minor.flac
#EXTINF:-1,Tycho - Epoch
/app/music/Tycho - Epoch (Deluxe Version) (2019) [WEB FLAC16-44.1]/05 - Epoch.flac
#EXTINF:-1,Four Tet - Memories
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/11 Memories.flac
#EXTINF:-1,Ulrich Schnauss & Jonas Munk - Return To Burlington
/app/music/Ulrich Schnauss & Jonas Munk - Eight Fragments Of An Illusion (2021) - WEB FLAC/02. Return To Burlington.flac
#EXTINF:-1,Boards of Canada - Skyliner
/app/music/Boards of Canada/Trans Canada Highway/04 - Skyliner.mp3
#EXTINF:-1,Vector Lovers - Melodies And Memory
/app/music/Vector Lovers/2005 - Capsule For One/07 - Melodies And Memory.mp3
#EXTINF:-1,Brian Eno - Icarus or Blériot
/app/music/Brian Eno/2022 - ForeverAndEverNoMore/03 Icarus or Blériot.flac
#EXTINF:-1,arovane - noondt
/app/music/arovane - Wirkung (2020) [WEB FLAC16]/07. arovane - noondt.flac
#EXTINF:-1,FSOL - Symphony for Halia
/app/music/The Future Sound of London - Environment Six (2016 - WEB - FLAC)/14 - Symphony for Halia.flac
#EXTINF:-1,Biosphere - Straumen
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/12 - Straumen.flac
#EXTINF:-1,Port Blue - The Gentle Descent
/app/music/Port Blue - The Airship (2007)/12. The Gentle Descent.flac
#EXTINF:-1,Proem - As They Go
/app/music/Proem/2019 - As They Go/Proem - As They Go - 01 As They Go.flac
#EXTINF:-1,Tycho - Outer Sunset
/app/music/Tycho - Simulcast (2020) [WEB FLAC]/03 - Outer Sunset.flac
#EXTINF:-1,Four Tet - Planet
/app/music/Four Tet - New Energy {CD} [FLAC] (2017)/14 Planet.flac
#EXTINF:-1,Ulrich Schnauss - New Day Starts at Dawn (2019 Version)
/app/music/Ulrich Schnauss - No Further Ahead Than Tomorrow (2020) - WEB FLAC/07. New Day Starts at Dawn (2019 Version).flac
#EXTINF:-1,Clark - Goodnight Kiri
/app/music/Clark - Kiri Variations (2019) [WEB FLAC]/14 - Goodnight Kiri.flac
#EXTINF:-1,Biosphere - Steinfjord
/app/music/Biosphere - The Senja Recordings (2019) [FLAC]/13 - Steinfjord.flac
#EXTINF:-1,Tangerine Dream - Calyx Calamander
/app/music/Tangerine Dream - Ambient Monkeys (flac)/04 Tangerine Dream - Calyx Calamander.flac
#EXTINF:-1,Vector Lovers - To The Stars
/app/music/Vector Lovers/2005 - Capsule For One/12 - To The Stars.mp3

View File

@ -1703,6 +1703,57 @@ body.popout-body .status-mini{
font-size: 0.8em;
}
.profile-header{
display: flex;
gap: 30px;
align-items: flex-start;
}
.avatar-section{
flex-shrink: 0;
}
.avatar-container{
position: relative;
width: 120px;
height: 120px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #00cc00;
cursor: pointer;
background: #1a1a1a;
}
.avatar-image{
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-overlay{
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
color: #00cc00;
text-align: center;
padding: 8px;
font-size: 0.8em;
opacity: 0;
-moz-transition: opacity 0.2s ease;
-o-transition: opacity 0.2s ease;
-webkit-transition: opacity 0.2s ease;
-ms-transition: opacity 0.2s ease;
transition: opacity 0.2s ease;
}
.avatar-container:hover .avatar-overlay{
opacity: 1;
}
.activity-chart{
padding: 15px;
}

View File

@ -1360,6 +1360,47 @@
:padding "4px 8px"
:font-size "0.8em"))
;; Avatar styling
(.profile-header
:display "flex"
:gap "30px"
:align-items "flex-start")
(.avatar-section
:flex-shrink "0")
(.avatar-container
:position "relative"
:width "120px"
:height "120px"
:border-radius "50%"
:overflow "hidden"
:border "3px solid #00cc00"
:cursor "pointer"
:background "#1a1a1a")
(.avatar-image
:width "100%"
:height "100%"
:object-fit "cover")
(.avatar-overlay
:position "absolute"
:bottom "0"
:left "0"
:right "0"
:background "rgba(0, 0, 0, 0.7)"
:color "#00cc00"
:text-align "center"
:padding "8px"
:font-size "0.8em"
:opacity "0"
:transition "opacity 0.2s ease")
((:and .avatar-container :hover)
(.avatar-overlay
:opacity "1"))
;; Activity chart styling
(.activity-chart
:padding "15px"

0
static/avatars/.gitkeep Normal file
View File

BIN
static/avatars/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<circle cx="60" cy="60" r="58" fill="#1a1a1a" stroke="#00cc00" stroke-width="2"/>
<circle cx="60" cy="45" r="20" fill="#00cc00"/>
<path d="M 25 95 Q 25 70 60 70 Q 95 70 95 95" fill="#00cc00"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@ -21,22 +21,33 @@
<!-- User Profile Header -->
<div class="admin-section">
<h2>🎧 User Profile</h2>
<div class="profile-info">
<div class="info-group">
<span class="info-label">Username:</span>
<span class="info-value" data-text="username">user</span>
<div class="profile-header">
<div class="avatar-section">
<div class="avatar-container" id="avatar-container">
<img id="user-avatar" src="/asteroid/static/icons/default-avatar.svg" alt="User Avatar" class="avatar-image">
<div class="avatar-overlay" onclick="document.getElementById('avatar-input').click()">
<span>Change</span>
</div>
</div>
<input type="file" id="avatar-input" accept="image/*" style="display: none" onchange="uploadAvatar(this)">
</div>
<div class="info-group">
<span class="info-label">Role:</span>
<span class="info-value" data-text="user-role">listener</span>
</div>
<div class="info-group">
<span class="info-label">Member Since:</span>
<span class="info-value" data-text="join-date">2024-01-01</span>
</div>
<div class="info-group">
<span class="info-label">Last Active:</span>
<span class="info-value" data-text="last-active">Today</span>
<div class="profile-info">
<div class="info-group">
<span class="info-label">Username:</span>
<span class="info-value" data-text="username">user</span>
</div>
<div class="info-group">
<span class="info-label">Role:</span>
<span class="info-value" data-text="user-role">listener</span>
</div>
<div class="info-group">
<span class="info-label">Member Since:</span>
<span class="info-value" data-text="join-date">2024-01-01</span>
</div>
<div class="info-group">
<span class="info-label">Last Active:</span>
<span class="info-value" data-text="last-active">Today</span>
</div>
</div>
</div>
</div>

View File

@ -174,12 +174,12 @@
;; Not authenticated - emit error and signal to stop processing
(progn
(if is-api-request
;; API request - emit JSON error with 401 status
;; API request - return JSON error with 401 status using api-output
(progn
(format t "Authentication failed - returning JSON 401~%")
(setf (radiance:return-code *response*) 401)
(setf (radiance:content-type *response*) "application/json")
(error 'radiance:request-denied :message "Authentication required"))
(api-output `(("status" . "error")
("message" . "Authentication required"))
:status 401))
;; Page request - redirect to login
(progn
(format t "Authentication failed - redirecting to login~%")

View File

@ -73,6 +73,12 @@
;;; Listening History - Per-user track play history
;;; ==========================================================================
(defun sql-escape-string (str)
"Escape a string for SQL by doubling single quotes"
(if str
(cl-ppcre:regex-replace-all "'" str "''")
""))
(defun record-listen (user-id &key track-id track-title (duration 0) (completed nil))
"Record a track listen in user's history. Can use track-id or track-title."
(with-db
@ -80,12 +86,12 @@
(postmodern:query
(:raw (format nil "INSERT INTO listening_history (\"user-id\", \"track-id\", track_title, \"listen-duration\", completed) VALUES (~a, ~a, ~a, ~a, ~a)"
user-id track-id
(if track-title (format nil "$$~a$$" track-title) "NULL")
(if track-title (format nil "'~a'" (sql-escape-string track-title)) "NULL")
duration (if completed 1 0))))
(when track-title
(postmodern:query
(:raw (format nil "INSERT INTO listening_history (\"user-id\", track_title, \"listen-duration\", completed) VALUES (~a, $$~a$$, ~a, ~a)"
user-id track-title duration (if completed 1 0))))))))
(:raw (format nil "INSERT INTO listening_history (\"user-id\", track_title, \"listen-duration\", completed) VALUES (~a, '~a', ~a, ~a)"
user-id (sql-escape-string track-title) duration (if completed 1 0))))))))
(defun get-listening-history (user-id &key (limit 20) (offset 0))
"Get user's listening history - works with title-based history"
@ -230,8 +236,10 @@
(parse-integer track-id :junk-allowed t)))
(duration-int (if duration (parse-integer duration :junk-allowed t) 0))
(completed-bool (and completed (string-equal completed "true"))))
(record-listen user-id :track-id track-id-int :track-title title
:duration (or duration-int 0) :completed completed-bool)
(format t "Recording listen: user-id=~a title=~a~%" user-id title)
(when (and user-id title)
(record-listen user-id :track-id track-id-int :track-title title
:duration (or duration-int 0) :completed completed-bool))
(api-output `(("status" . "success")
("message" . "Listen recorded"))))))
@ -256,3 +264,64 @@
`(("day" . ,(cdr (assoc :day a)))
("track_count" . ,(cdr (assoc :track-count a)))))
activity)))))))
;;; ==========================================================================
;;; Avatar Management
;;; ==========================================================================
(defun get-avatars-directory ()
"Get the path to the avatars directory"
(merge-pathnames "static/avatars/" (asdf:system-source-directory :asteroid)))
(defun save-avatar (user-id temp-file-path original-filename)
"Save an avatar file from temp path and return the relative path"
(let* ((extension (pathname-type original-filename))
(safe-ext (if (member extension '("png" "jpg" "jpeg" "gif" "webp") :test #'string-equal)
extension
"png"))
(new-filename (format nil "~a.~a" user-id safe-ext))
(full-path (merge-pathnames new-filename (get-avatars-directory)))
(relative-path (format nil "/asteroid/static/avatars/~a" new-filename)))
;; Copy from temp file to avatars directory
(uiop:copy-file temp-file-path full-path)
;; Update database
(with-db
(postmodern:query
(:raw (format nil "UPDATE \"USERS\" SET avatar_path = '~a' WHERE _id = ~a"
relative-path user-id))))
relative-path))
(defun get-user-avatar (user-id)
"Get the avatar path for a user"
(with-db
(postmodern:query
(:raw (format nil "SELECT avatar_path FROM \"USERS\" WHERE _id = ~a" user-id))
:single)))
(define-api asteroid/user/avatar/upload () ()
"Upload a new avatar image"
(require-authentication)
(with-error-handling
(let* ((user-id (session:field "user-id"))
;; Radiance wraps hunchentoot - post-var returns (path filename content-type) for files
(file-info (radiance:post-var "avatar"))
(temp-path (when (listp file-info) (first file-info)))
(original-name (when (listp file-info) (second file-info))))
(format t "Avatar upload: file-info=~a temp-path=~a original-name=~a~%" file-info temp-path original-name)
(if (and temp-path (probe-file temp-path))
(let ((avatar-path (save-avatar user-id temp-path (or original-name "avatar.png"))))
(api-output `(("status" . "success")
("message" . "Avatar uploaded successfully")
("avatar_path" . ,avatar-path))))
(api-output `(("status" . "error")
("message" . "No file provided"))
:status 400)))))
(define-api asteroid/user/avatar () ()
"Get current user's avatar path"
(require-authentication)
(with-error-handling
(let* ((user-id (session:field "user-id"))
(avatar-path (get-user-avatar user-id)))
(api-output `(("status" . "success")
("avatar_path" . ,avatar-path))))))