From 0735a9ae65f0b86ae7d81965f4278e4e91bc1c1f Mon Sep 17 00:00:00 2001 From: Robbe Van Petegem Date: Thu, 30 Jun 2022 19:28:26 +0200 Subject: [PATCH] Add endpoint to get play stats (#340) --- app/controllers/application_controller.rb | 2 +- app/controllers/plays_controller.rb | 17 +++++++- app/models/play.rb | 5 ++- app/policies/play_policy.rb | 4 ++ app/serializers/play_stat_serializer.rb | 3 ++ config/routes.rb | 7 +++- test/controllers/plays_controller_test.rb | 47 +++++++++++++++++++++++ 7 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 app/serializers/play_stat_serializer.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8f0a2dca..2af78545 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,7 +14,7 @@ class ApplicationController < ActionController::API rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized rescue_from ActiveRecord::RecordNotFound, with: :model_not_found - has_scope :sorted, default: nil, allow_blank: true + has_scope :sorted, default: nil, allow_blank: true, except: :stats serialization_scope :url_options diff --git a/app/controllers/plays_controller.rb b/app/controllers/plays_controller.rb index a32ea222..1a74f7a4 100644 --- a/app/controllers/plays_controller.rb +++ b/app/controllers/plays_controller.rb @@ -1,10 +1,12 @@ class PlaysController < ApplicationController has_scope :by_album, as: 'album_id' + has_scope :by_artist, as: 'artist_id' + has_scope :played_before, as: 'played_before' + has_scope :played_after, as: 'played_after' def index authorize Play @plays = apply_scopes(policy_scope(Play)) - .order(id: :desc) .paginate(page: params[:page], per_page: params[:per_page]) add_pagination_headers(@plays) @@ -22,6 +24,19 @@ def create end end + def stats + authorize Play + @stats = apply_scopes(policy_scope(Play)) + .left_joins(track: :audio_file) + .select('COUNT(*)', 'MAX(played_at) as last_played_at', 'SUM(COALESCE("audio_files"."length", 0)) as total_length', :track_id, :user_id) + .group(:track_id, :user_id) + .order(track_id: :desc) + .paginate(page: params[:page], per_page: params[:per_page]) + add_pagination_headers(@stats) + + render json: @stats, each_serializer: PlayStatSerializer + end + private def transformed_attributes diff --git a/app/models/play.rb b/app/models/play.rb index d7d99f24..e0b3a7ae 100644 --- a/app/models/play.rb +++ b/app/models/play.rb @@ -13,5 +13,8 @@ class Play < ApplicationRecord validates :played_at, presence: true - scope :by_album, ->(album) { joins(:track).where(track: { album_id: album }) } + scope :by_album, ->(album) { where(track_id: Track.by_album(album)) } + scope :by_artist, ->(artist) { where(track_id: Track.by_artist(artist)) } + scope :played_before, ->(date) { where('played_at < ?', date) } + scope :played_after, ->(date) { where('played_at > ?', date) } end diff --git a/app/policies/play_policy.rb b/app/policies/play_policy.rb index 11433e91..a51574c0 100644 --- a/app/policies/play_policy.rb +++ b/app/policies/play_policy.rb @@ -13,6 +13,10 @@ def create? user.present? end + def stats? + user.present? + end + def permitted_attributes %i[track_id played_at] end diff --git a/app/serializers/play_stat_serializer.rb b/app/serializers/play_stat_serializer.rb new file mode 100644 index 00000000..ec94f741 --- /dev/null +++ b/app/serializers/play_stat_serializer.rb @@ -0,0 +1,3 @@ +class PlayStatSerializer < ActiveModel::Serializer + attributes :count, :track_id, :last_played_at, :total_length +end diff --git a/config/routes.rb b/config/routes.rb index 1b904583..700ea1ee 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,6 +71,7 @@ # PATCH /api/playlists/:id(.:format) playlists#update # PUT /api/playlists/:id(.:format) playlists#update # DELETE /api/playlists/:id(.:format) playlists#destroy +# stats_plays GET /api/plays/stats(.:format) plays#stats # plays GET /api/plays(.:format) plays#index # POST /api/plays(.:format) plays#create # destroy_empty_tracks POST /api/tracks/destroy_empty(.:format) tracks#destroy_empty @@ -143,7 +144,11 @@ end resources :locations, only: %i[index show create destroy] resources :playlists - resources :plays, only: %i[index create] + resources :plays, only: %i[index create] do + collection do + get 'stats' + end + end resources :tracks do collection do post 'destroy_empty' diff --git a/test/controllers/plays_controller_test.rb b/test/controllers/plays_controller_test.rb index 6bc1a088..23a14491 100644 --- a/test/controllers/plays_controller_test.rb +++ b/test/controllers/plays_controller_test.rb @@ -41,4 +41,51 @@ class PlaysControllerTest < ActionDispatch::IntegrationTest assert_response 422 end + + test 'should get stats and not return play stats for other users' do + create(:play, track: @track, user: create(:user)) + get stats_plays_url + assert_response :success + body = JSON.parse response.body + assert_empty body + end + + test 'should get stats and return play stats for users' do + create(:play, track: @track, user: @user, played_at: DateTime.new(2022, 0o1, 0o2, 0o3, 0o4, 0o5)) + get stats_plays_url + assert_response :success + body = JSON.parse response.body + assert_equal 1, body[0]['count'] + assert_equal '2022-01-02T03:04:05.000Z', body[0]['last_played_at'] + end + + test 'should get stats and filter plays by album' do + create(:play, track: @track, user: @user) + create(:play, track: create(:track), user: @user) + get stats_plays_url(album_id: @track.album_id) + assert_response :success + body = JSON.parse response.body + assert_equal 1, body.length + end + + test 'should get stats and filter plays by date' do + create(:play, track: @track, user: @user, played_at: 5.days.ago) + create(:play, track: @track, user: @user, played_at: 3.days.ago) + create(:play, track: @track, user: @user, played_at: DateTime.current) + create(:play, track: @track, user: @user) + get stats_plays_url(played_before: 2.days.ago, played_after: 4.days.ago) + assert_response :success + body = JSON.parse response.body + assert_equal 1, body[0]['count'] + end + + test 'should get stats and filter plays by artist' do + ta = create(:track_artist, track: @track) + create(:play, track: @track, user: @user) + create(:play, track: create(:track_artist).track, user: @user) + get stats_plays_url(artist_id: ta.artist_id) + assert_response :success + body = JSON.parse response.body + assert_equal 1, body.length + end end