From d84519fa213f359003aad1d7c7978d18b14b5946 Mon Sep 17 00:00:00 2001 From: Gabe Farrell Date: Sun, 6 Jul 2025 21:27:54 -0400 Subject: [PATCH] feat: add minutes listened to ui and fix image drop --- CHANGELOG.md | 9 +++++++-- Makefile | 9 +++++++++ client/api/api.ts | 2 +- client/app/components/AllTimeStats.tsx | 2 +- client/app/components/ImageDropHandler.tsx | 9 ++++++--- client/app/routes/MediaItems/Album.tsx | 2 +- client/app/routes/MediaItems/Artist.tsx | 2 +- client/app/routes/MediaItems/MediaLayout.tsx | 2 +- client/app/routes/MediaItems/Track.tsx | 2 +- engine/handlers/stats.go | 20 ++++++++++---------- engine/long_test.go | 2 +- 11 files changed, 39 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb320dd..803a077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ # v0.1.0 ## Features -- You can now search and merge items by their ID! Just preface the id with `id:`. E.g. `id:123` (#26) ## Enhancements -- Track durations will now be updated using MusicBrainz data where possible, if the duration was not provided by the request. (#27) \ No newline at end of file +- Track durations will now be updated using MusicBrainz data where possible, if the duration was not provided by the request. (#27) +- You can now search and merge items by their ID! Just preface the id with `id:`. E.g. `id:123` (#26) +- Hovering over any "hours listened" statistic will now also show the minutes listened. + +## Fixes +- Navigating from one page directly to another and then changing the image via drag-and-drop now works as expected. (#25) +- Fixed a bug that caused updated usernames with uppercase letters to create login failures. \ No newline at end of file diff --git a/Makefile b/Makefile index 5167863..82fbd89 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,9 @@ postgres.schemadump: postgres.run: docker run --name koito-db -p 5432:5432 -e POSTGRES_PASSWORD=secret -d postgres +postgres.run-scratch: + docker run --name koito-scratch -p 5433:5432 -e POSTGRES_PASSWORD=secret -d postgres + postgres.start: docker start koito-db @@ -21,9 +24,15 @@ postgres.stop: postgres.remove: docker stop koito-db && docker rm koito-db +postgres.remove-scratch: + docker stop koito-scratch && docker rm koito-scratch + api.debug: KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5432?sslmode=disable go run cmd/api/main.go +api.scratch: + KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir/scratch KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5433?sslmode=disable go run cmd/api/main.go + api.test: go test ./... -timeout 60s diff --git a/client/api/api.ts b/client/api/api.ts index be2cda0..b744a05 100644 --- a/client/api/api.ts +++ b/client/api/api.ts @@ -288,7 +288,7 @@ type Stats = { track_count: number album_count: number artist_count: number - hours_listened: number + minutes_listened: number } type SearchResponse = { albums: Album[] diff --git a/client/app/components/AllTimeStats.tsx b/client/app/components/AllTimeStats.tsx index 0a54daa..cc45c65 100644 --- a/client/app/components/AllTimeStats.tsx +++ b/client/app/components/AllTimeStats.tsx @@ -26,7 +26,7 @@ export default function AllTimeStats() {

All Time Stats

- {data.hours_listened} Hours Listened + {Math.floor(data.minutes_listened / 60)} Hours Listened
{data.listen_count} Plays diff --git a/client/app/components/ImageDropHandler.tsx b/client/app/components/ImageDropHandler.tsx index 8557ff9..9e686ea 100644 --- a/client/app/components/ImageDropHandler.tsx +++ b/client/app/components/ImageDropHandler.tsx @@ -3,11 +3,10 @@ import { useEffect } from 'react'; interface Props { itemType: string, - id: number, onComplete: Function } -export default function ImageDropHandler({ itemType, id, onComplete }: Props) { +export default function ImageDropHandler({ itemType, onComplete }: Props) { useEffect(() => { const handleDragOver = (e: DragEvent) => { console.log('dragover!!') @@ -25,7 +24,11 @@ export default function ImageDropHandler({ itemType, id, onComplete }: Props) { const formData = new FormData(); formData.append('image', imageFile); - formData.append(itemType.toLowerCase()+'_id', String(id)) + const pathname = window.location.pathname; + const segments = pathname.split('/'); + const filteredSegments = segments.filter(segment => segment !== ''); + const lastSegment = filteredSegments[filteredSegments.length - 1]; + formData.append(itemType.toLowerCase()+'_id', lastSegment) replaceImage(formData).then((r) => { if (r.status >= 200 && r.status < 300) { onComplete() diff --git a/client/app/routes/MediaItems/Album.tsx b/client/app/routes/MediaItems/Album.tsx index 955baec..a262ac3 100644 --- a/client/app/routes/MediaItems/Album.tsx +++ b/client/app/routes/MediaItems/Album.tsx @@ -43,7 +43,7 @@ export default function Album() { }} subContent={
{album.listen_count &&

{album.listen_count} play{ album.listen_count > 1 ? 's' : ''}

} - {

{timeListenedString(album.time_listened)}

} + {

{timeListenedString(album.time_listened)}

}
} >
diff --git a/client/app/routes/MediaItems/Artist.tsx b/client/app/routes/MediaItems/Artist.tsx index 41a185a..b698a27 100644 --- a/client/app/routes/MediaItems/Artist.tsx +++ b/client/app/routes/MediaItems/Artist.tsx @@ -49,7 +49,7 @@ export default function Artist() { }} subContent={
{artist.listen_count &&

{artist.listen_count} play{ artist.listen_count > 1 ? 's' : ''}

} - {

{timeListenedString(artist.time_listened)}

} + {

{timeListenedString(artist.time_listened)}

}
} >
diff --git a/client/app/routes/MediaItems/MediaLayout.tsx b/client/app/routes/MediaItems/MediaLayout.tsx index 309e347..f9762bb 100644 --- a/client/app/routes/MediaItems/MediaLayout.tsx +++ b/client/app/routes/MediaItems/MediaLayout.tsx @@ -61,7 +61,7 @@ export default function MediaLayout(props: Props) { transition: '1000', }} > - + {title} appears on {album.title} {track.listen_count &&

{track.listen_count} play{ track.listen_count > 1 ? 's' : ''}

} - {

{timeListenedString(track.time_listened)}

} + {

{timeListenedString(track.time_listened)}

}
} >
diff --git a/engine/handlers/stats.go b/engine/handlers/stats.go index 3e01816..7dbfc29 100644 --- a/engine/handlers/stats.go +++ b/engine/handlers/stats.go @@ -10,11 +10,11 @@ import ( ) type StatsResponse struct { - ListenCount int64 `json:"listen_count"` - TrackCount int64 `json:"track_count"` - AlbumCount int64 `json:"album_count"` - ArtistCount int64 `json:"artist_count"` - HoursListened int64 `json:"hours_listened"` + ListenCount int64 `json:"listen_count"` + TrackCount int64 `json:"track_count"` + AlbumCount int64 `json:"album_count"` + ArtistCount int64 `json:"artist_count"` + MinutesListened int64 `json:"minutes_listened"` } func StatsHandler(store db.DB) http.HandlerFunc { @@ -79,11 +79,11 @@ func StatsHandler(store db.DB) http.HandlerFunc { l.Debug().Msg("StatsHandler: Successfully fetched statistics") utils.WriteJSON(w, http.StatusOK, StatsResponse{ - ListenCount: listens, - TrackCount: tracks, - AlbumCount: albums, - ArtistCount: artists, - HoursListened: timeListenedS / 60 / 60, + ListenCount: listens, + TrackCount: tracks, + AlbumCount: albums, + ArtistCount: artists, + MinutesListened: timeListenedS / 60, }) } } diff --git a/engine/long_test.go b/engine/long_test.go index 20dcc01..a947a79 100644 --- a/engine/long_test.go +++ b/engine/long_test.go @@ -447,7 +447,7 @@ func TestStats(t *testing.T) { assert.EqualValues(t, 3, actual.TrackCount) assert.EqualValues(t, 3, actual.AlbumCount) assert.EqualValues(t, 3, actual.ArtistCount) - assert.EqualValues(t, 0, actual.HoursListened) + assert.EqualValues(t, 11, actual.MinutesListened) } func TestListenActivity(t *testing.T) {