diff --git a/model/user.go b/model/user.go index b76aae091..917827b6f 100644 --- a/model/user.go +++ b/model/user.go @@ -81,6 +81,6 @@ type UserRepository interface { GetUserLibraries(userID string) (Libraries, error) SetUserLibraries(userID string, libraryIDs []int) error - RatingStats(ctx context.Context) ([]UserRatingStats, error) + RatingStats(ctx context.Context, userID string) ([]UserRatingStats, error) RatingItems(ctx context.Context, userID, itemType string, rating int) ([]RatedItem, error) } diff --git a/persistence/user_repository.go b/persistence/user_repository.go index 617669b6c..26c9fc13e 100644 --- a/persistence/user_repository.go +++ b/persistence/user_repository.go @@ -478,7 +478,7 @@ func (r *userRepository) SetUserLibraries(userID string, libraryIDs []int) error return nil } -func (r *userRepository) RatingStats(ctx context.Context) ([]model.UserRatingStats, error) { +func (r *userRepository) RatingStats(ctx context.Context, userID string) ([]model.UserRatingStats, error) { type row struct { UserID string `db:"user_id"` UserName string `db:"user_name"` @@ -487,10 +487,14 @@ func (r *userRepository) RatingStats(ctx context.Context) ([]model.UserRatingSta Count int `db:"cnt"` } + where := And{Gt{"a.rating": 0}, Eq{"a.item_type": []interface{}{"media_file", "album"}}} + if userID != "" { + where = append(where, Eq{"a.user_id": userID}) + } sel := Select(`a.user_id`, `u.user_name`, `a.item_type`, `a.rating`, `count(*) as cnt`). From(`annotation a`). Join(`"user" u ON a.user_id = u.id`). - Where(And{Gt{"a.rating": 0}, Eq{"a.item_type": []interface{}{"media_file", "album"}}}). + Where(where). GroupBy(`a.user_id`, `u.user_name`, `a.item_type`, `a.rating`). OrderBy(`u.user_name`, `a.item_type`, `a.rating DESC`) diff --git a/server/nativeapi/rating_stats.go b/server/nativeapi/rating_stats.go index 4e40738cb..4e15b4ce4 100644 --- a/server/nativeapi/rating_stats.go +++ b/server/nativeapi/rating_stats.go @@ -19,22 +19,16 @@ func (api *Router) addRatingStatsRoute(r chi.Router) { func getRatingStats(ds model.DataStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { currentUser, _ := request.UserFrom(r.Context()) - stats, err := ds.User(r.Context()).RatingStats(r.Context()) + filterUserID := "" + if !currentUser.IsAdmin { + filterUserID = currentUser.ID + } + stats, err := ds.User(r.Context()).RatingStats(r.Context(), filterUserID) if err != nil { log.Error(r.Context(), "Error getting rating stats", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - if !currentUser.IsAdmin { - filtered := make([]model.UserRatingStats, 0, 1) - for _, s := range stats { - if s.UserID == currentUser.ID { - filtered = append(filtered, s) - break - } - } - stats = filtered - } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(stats); err != nil { log.Error(r.Context(), "Error encoding rating stats", err) @@ -50,13 +44,21 @@ func getRatingItems(ds model.DataStore) http.HandlerFunc { ratingStr := r.URL.Query().Get("rating") rating, err := strconv.Atoi(ratingStr) - if err != nil || rating < 1 || rating > 5 || userID == "" || (itemType != "album" && itemType != "song") { - http.Error(w, "invalid parameters", http.StatusBadRequest) + if err != nil || rating < 1 || rating > 5 { + http.Error(w, "rating must be between 1 and 5", http.StatusBadRequest) + return + } + if userID == "" { + http.Error(w, "userId is required", http.StatusBadRequest) + return + } + if itemType != "album" && itemType != "song" { + http.Error(w, "type must be 'album' or 'song'", http.StatusBadRequest) return } if !currentUser.IsAdmin && currentUser.ID != userID { - http.Error(w, "forbidden", http.StatusForbidden) + http.Error(w, "non-admin users can only query their own ratings", http.StatusForbidden) return } diff --git a/tests/mock_user_repo.go b/tests/mock_user_repo.go index 81a66971a..1c8e26e61 100644 --- a/tests/mock_user_repo.go +++ b/tests/mock_user_repo.go @@ -137,7 +137,7 @@ func (u *MockedUserRepo) SetUserLibraries(userID string, libraryIDs []int) error return nil } -func (u *MockedUserRepo) RatingStats(ctx context.Context) ([]model.UserRatingStats, error) { +func (u *MockedUserRepo) RatingStats(ctx context.Context, userID string) ([]model.UserRatingStats, error) { if u.Error != nil { return nil, u.Error } diff --git a/ui/src/common/AverageRatingField.jsx b/ui/src/common/AverageRatingField.jsx index 7f3ed21e7..78b14d513 100644 --- a/ui/src/common/AverageRatingField.jsx +++ b/ui/src/common/AverageRatingField.jsx @@ -17,7 +17,7 @@ export const AverageRatingField = ({ className, size, record = {}, ...rest }) => const classes = useStyles() const translate = useTranslate() - const avg = record.averageRating || 0 + const avg = Number(record.averageRating) || 0 if (avg <= 0) return null return ( diff --git a/ui/src/userRatings/UserRatingItems.jsx b/ui/src/userRatings/UserRatingItems.jsx index 5e0582897..7c1201808 100644 --- a/ui/src/userRatings/UserRatingItems.jsx +++ b/ui/src/userRatings/UserRatingItems.jsx @@ -62,10 +62,8 @@ const UserRatingItems = ({ match }) => { const title = `${userName} · ${typeLabel} · ${rating}★` const getCoverUrl = (item) => { - return subsonic.getCoverArtUrl( - { id: item.id, albumArtist: type === 'album' ? item.artist : undefined, album: type === 'song' ? item.name : undefined, updatedAt: item.updatedAt }, - 40, - ) + const coverArtId = type === 'album' ? item.id : item.albumId + return subsonic.getCoverArtUrl({ id: coverArtId, updatedAt: item.updatedAt }, 40) } return ( diff --git a/ui/src/userRatings/UserRatings.jsx b/ui/src/userRatings/UserRatings.jsx index 6fa852de9..4402387bc 100644 --- a/ui/src/userRatings/UserRatings.jsx +++ b/ui/src/userRatings/UserRatings.jsx @@ -131,7 +131,12 @@ const RatingTable = ({ stats, label, userId, userName, type }) => { onClick={() => count > 0 && history.push(linkTo)} tabIndex={count > 0 ? 0 : undefined} role={count > 0 ? 'button' : undefined} - onKeyDown={count > 0 ? (e) => (e.key === 'Enter' || e.key === ' ') && history.push(linkTo) : undefined} + onKeyDown={count > 0 ? (e) => { + if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') { + e.preventDefault() + history.push(linkTo) + } + } : undefined} > {r}.0 {count}