From 1262b9fd60cb751aad0a36d901741d3ccb326477 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 16 Jan 2021 19:37:34 -0600 Subject: [PATCH] ARS vs P2P, subscription fixes, no block on max time --- admin/app/admin/jam_ruby_users.rb | 43 ++++++++++++++++++- ruby/lib/jam_ruby/connection_manager.rb | 10 +++++ .../models/music_session_user_history.rb | 4 +- ruby/lib/jam_ruby/models/user.rb | 16 +++++-- ruby/lib/jam_ruby/recurly_client.rb | 26 +++++++++-- .../jam_ruby/resque/scheduled/hourly_job.rb | 1 + ruby/lib/jam_ruby/subscription_definitions.rb | 17 ++++++++ ruby/spec/jam_ruby/connection_manager_spec.rb | 35 ++++++++++++--- .../CurrentSubscription.js.jsx.coffee | 20 ++++++--- .../SessionStatsHover.js.jsx.coffee | 13 ++++-- .../stores/SessionStatsStore.js.coffee | 14 ++++++ .../client/react-components/SessionTrack.scss | 2 +- .../api_music_sessions_controller.rb | 39 +++++++++++------ 13 files changed, 198 insertions(+), 42 deletions(-) diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index fff7b460d..58dbe92f1 100644 --- a/admin/app/admin/jam_ruby_users.rb +++ b/admin/app/admin/jam_ruby_users.rb @@ -74,6 +74,12 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do redirect_to :back, {notice: "Check the Subscription Plan Code, Subscription Sync Code, Subscription Sync Msg"} end + member_action :reset_monthly_play, :method => :get do + resource.used_month_play_time = 0 + resource.save! + redirect_to :back, {notice: "Reset user's monthly play time to 0"} + end + member_action :change_to_plan, :method => :get do @client = RecurlyClient.new plan_code = params[:plan_code] @@ -128,6 +134,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do row :gender row :email_confirmed row :remember_token +=begin row "Session Ready" do |user| div do if user.ready_for_session_at @@ -154,6 +161,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do end end end +=end row "Delete Forever" do |user| span do link_to("delete forever", delete_forever_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'}) @@ -201,6 +209,24 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do row :subscription_sync_msg row :is_past_due row :stored_credit_card + row "Monthly Time Used" do |user| + div do + remaining_month_play_time = user.subscription_rules[:remaining_month_play_time] + if remaining_month_play_time.nil? + span do + "No limit" + end + elsif user.played_this_month? + span do + "Used: #{user.used_month_play_time / 60} min | Remaining #{remaining_month_play_time / 60} min" + end + else + span do + "Did not play this month. Last played #{user.used_current_month}" + end + end + end + end end end div do @@ -225,9 +251,19 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do 'sets secret override to give user a free plan (link goes to another page)' end div do - link_to("Give No-Payment Plan", edit_admin_user_override_path(user.id)) + link_to("give no-payment plan", edit_admin_user_override_path(user.id)) + end + end + div do + h3 do + 'Reset Monthly Play Time' + end + h4 do + 'sets the user\'s monthly play time to 0' + end + div do + link_to("reset monthly play time", reset_monthly_play_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'}) end - end div do h3 do @@ -331,6 +367,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do end end +=begin panel "Teacher Setting" do attributes_table do @@ -396,6 +433,8 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do end +=end + panel "JamTracks" do div do link_to "Give JamTrack", "../jam_track_rights/new" diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb index 183f4a5ff..4ae6e9311 100644 --- a/ruby/lib/jam_ruby/connection_manager.rb +++ b/ruby/lib/jam_ruby/connection_manager.rb @@ -181,6 +181,16 @@ SQL clients end + # deletes any connections in active music sessions older than 2 days. + def cleanup_dangling + + ConnectionManager.active_record_transaction do |connection_manager, conn| + sql = "update connections set music_session_id = null where id in (select id from connections where music_session_id in (select id from active_music_sessions where updated_at::date < (current_date - 2)))" + conn.exec(sql) do |result| + end + end + + end # returns the number of connections that this user currently has across all clients # this number is used by notification logic elsewhere to know diff --git a/ruby/lib/jam_ruby/models/music_session_user_history.rb b/ruby/lib/jam_ruby/models/music_session_user_history.rb index 7e35f39d5..3836b4231 100644 --- a/ruby/lib/jam_ruby/models/music_session_user_history.rb +++ b/ruby/lib/jam_ruby/models/music_session_user_history.rb @@ -98,7 +98,7 @@ module JamRuby # update the users monthly play time def update_remaining_play_time now = Time.now - current_month = (now.year * 100 + now.month) + current_month = User.current_month remaining_time_so_far = self.user.used_month_play_time.to_i if current_month != self.user.used_current_month @@ -117,7 +117,7 @@ module JamRuby # we use the database to get all other connections that occurred while their this user was connected, # and then sort through them using custom logic to increase/decrease the count as people come and go def determine_max_concurrent - overlapping_connections = MusicSessionUserHistory.where("music_session_id = ? AND + overlapping_connections = MusicSessionUserHistory.where("music_session_id = ? AND ((created_at >= ? AND (session_removed_at is NULL OR session_removed_at <= ?)) OR (created_at <= ? AND (session_removed_at is NULL OR session_removed_at >= ?)))", self.music_session_id, self.created_at, self.session_removed_at, self.created_at, self.created_at).select('id, created_at, session_removed_at').order(:created_at) diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index ee73d9bbe..5cf5b4f4b 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -2879,12 +2879,11 @@ module JamRuby if play_time_per_month.nil? rules[:remaining_month_play_time] = nil else - now = Time.now - if used_current_month != (now.year * 100 + now.month) + if played_this_month? + rules[:remaining_month_play_time] = (play_time_per_month * 3600) - self.used_month_play_time.to_i + else # if this is a new month, then they get full play time rules[:remaining_month_play_time] = (play_time_per_month * 3600) - else - rules[:remaining_month_play_time] = (play_time_per_month * 3600) - self.used_month_play_time.to_i end end end @@ -2892,6 +2891,15 @@ module JamRuby rules end + def self.current_month + now = Time.now + (now.year * 100 + now.month) + end + + def played_this_month? + used_current_month == User.current_month + end + def update_admin_override_plan_code(plan_code) self.admin_override_plan_code = plan_code self.subscription_plan_code = plan_code diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index c37cf21cd..507c127e9 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -64,6 +64,11 @@ module JamRuby if subscription puts "Canceling user's #{current_user.email} subscription" subscription.cancel + # upon cancelation, take the user's current monthly payment plan + subscription = Recurly::Subscription.find(subscription.uuid) + current_user.subscription_plan_code = get_highest_plan(subscription) + current_user.subscription_plan_code_set_at = DateTime.now + current_user.save(validate: false) # do not delete the recurly_subscription_id ; we'll use that to try and reactivate later if they user re-activates their account else # if no subscription and past trial, you goin down -- because there must have never been payment?? @@ -323,12 +328,24 @@ module JamRuby raise RecurlyClientError.new(plan.errors) if plan.errors.any? end + def get_pending_plan_code(subscription) + if subscription && subscription.pending_subscription + return subscription.pending_subscription.plan.plan_code + else + return nil + end + end + + def get_highest_plan(subscription) + SubscriptionDefinitions.higher_plan(subscription.plan.plan_code, get_pending_plan_code(subscription)) + end + def handle_create_subscription(current_user, plan_code, account) begin subscription = create_subscription(current_user, plan_code, account, current_user.subscription_trial_ended? ? nil : current_user.subscription_trial_ends_at) current_user.recurly_subscription_id = subscription.uuid if current_user.subscription_trial_ended? - current_user.subscription_plan_code = plan_code + current_user.subscription_plan_code = get_highest_plan(subscription) current_user.subscription_plan_code_set_at = DateTime.now else # we could force a platinum plan since the user has put forward payment already, even in trial @@ -364,13 +381,14 @@ module JamRuby begin old_subscription.reactivate puts "reactivated plan! Let's check if it needs changing" - if plan_code != old_subscription.plan.plan_code + #if plan_code != old_subscription.plan.plan_code result = old_subscription.update_attributes( :plan_code => plan_code, :timeframe => starts_at.nil? ? 'bill_date' : 'now' ) - end - return old_subscription + # end + # fetch it again. because it's got staleness after update_attributes operation + return Recurly::Subscription.find(old_subscription_id) rescue => e puts "Unable to reactivate/update old plan #{e}" user.update_attribute(:recurly_subscription_id, nil) diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index 6efbe7d50..94a42af21 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -12,6 +12,7 @@ module JamRuby #LessonSession.hourly_check #TeacherPayment.hourly_check User.hourly_check + ConnectionManager.new.cleanup_dangling @@log.info("done") end diff --git a/ruby/lib/jam_ruby/subscription_definitions.rb b/ruby/lib/jam_ruby/subscription_definitions.rb index 500b4ee39..00be941e2 100644 --- a/ruby/lib/jam_ruby/subscription_definitions.rb +++ b/ruby/lib/jam_ruby/subscription_definitions.rb @@ -117,5 +117,22 @@ module JamRuby next_plan_rank < current_plan_rank end + + def self.higher_plan(plan_a, plan_b) + if plan_a.nil? + plan_b + elsif plan_b.nil? + plan_a + else + plan_a_rank = rules(plan_a)[:rank] + plan_b_rank = rules(plan_b)[:rank] + # if plan a is higher, take plan_a + if plan_a_rank > plan_b_rank + plan_a + else + plan_b + end + end + end end end diff --git a/ruby/spec/jam_ruby/connection_manager_spec.rb b/ruby/spec/jam_ruby/connection_manager_spec.rb index 117995963..0fe4584db 100644 --- a/ruby/spec/jam_ruby/connection_manager_spec.rb +++ b/ruby/spec/jam_ruby/connection_manager_spec.rb @@ -11,7 +11,7 @@ describe ConnectionManager, no_transaction: true do GATEWAY = 'gateway1' REACHABLE = true - let(:channel_id) {'1'} + let(:channel_id) { '1' } before do @conn = PG::Connection.new(:dbname => SpecDb::TEST_DB_NAME, :user => "postgres", :password => "postgres", :host => "localhost") @@ -42,6 +42,32 @@ describe ConnectionManager, no_transaction: true do end end + + describe "cleanup_dangling" do + + it "success" do + @connman.cleanup_dangling + + client_id = "client_id9" + user_id = create_user("test", "user9", "user9@jamkazam.com") + music_session = FactoryGirl.create(:active_music_session, user_id: user_id) + music_session_id = music_session.id + user = User.find(user_id) + + @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) + connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) + + Connection.where('music_session_id is not null').count.should == 1 + @connman.cleanup_dangling + Connection.where('music_session_id is not null').count.should == 1 + # well in the past + music_session.update_attribute("updated_at", '2020-01-01 00:00:00') + @connman.cleanup_dangling + Connection.where('music_session_id is not null').count.should == 0 + end + + end + it "can't create two client_ids of same value" do client_id = "client_id1" user_id = create_user("test", "user1", "user1@jamkazam.com") @@ -263,7 +289,7 @@ describe ConnectionManager, no_transaction: true do user_id = create_user("test", "user8", "user8@jamkazam.com") @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) - num = JamRuby::Connection.where('aasm_state = ?','connected').count + num = JamRuby::Connection.where('aasm_state = ?', 'connected').count num.should == 1 assert_num_connections(client_id, num) @connman.flag_stale_connections(GATEWAY) @@ -346,7 +372,6 @@ describe ConnectionManager, no_transaction: true do end - it "join_music_session fails if no connection" do client_id = "client_id10" @@ -429,7 +454,7 @@ describe ConnectionManager, no_transaction: true do @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) # specify real user id, but not associated with this session - expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(JamRuby::JamPermissionError) + expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(JamRuby::JamPermissionError) end it "join_music_session fails if no music_session" do @@ -455,7 +480,7 @@ describe ConnectionManager, no_transaction: true do @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE, GATEWAY, false) # specify real user id, but not associated with this session - expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(JamRuby::JamPermissionError) + expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(JamRuby::JamPermissionError) end diff --git a/web/app/assets/javascripts/react-components/CurrentSubscription.js.jsx.coffee b/web/app/assets/javascripts/react-components/CurrentSubscription.js.jsx.coffee index ff8fe5184..c6ca70a68 100644 --- a/web/app/assets/javascripts/react-components/CurrentSubscription.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/CurrentSubscription.js.jsx.coffee @@ -205,6 +205,9 @@ AppStore = context.AppStore displayTime: (until_time) -> + if until_time < 0 + return 'no time' + untilTime = @getTimeRemaining(until_time * 1000) timeString = '' @@ -213,9 +216,9 @@ AppStore = context.AppStore if untilTime.hours != 0 || timeString.length > 0 timeString += "#{untilTime.hours} hours, " if untilTime.minutes != 0 || timeString.length > 0 - timeString += "#{untilTime.minutes} minutes, " - if untilTime.seconds != 0 || timeString.length > 0 - timeString += "#{untilTime.seconds} seconds" + timeString += "#{untilTime.minutes} minutes " + #if untilTime.seconds != 0 || timeString.length > 0 + # timeString += "#{untilTime.seconds} seconds" if timeString == '' 'now!' @@ -295,15 +298,18 @@ AppStore = context.AppStore explanation = `You have successfully upgraded your plan to the {desired_plan_name} level, thank you` warning = `

However, you must provide a payment method (e.g. a credit card), for the monthly subscription charge. Please click the Update Payment Method button to do this now.

` else - explanation = `You have successfully upgraded your plan to the {desired_plan_name} level, thank you!` + explanation = `You are currently on the {effective_plan_name} level, thank you!` else # free plan situation - not much to go on about - explanation = `You are currently on the {desired_plan_name} plan.` + explanation = `You are currently on the {effective_plan_name} plan.` if show_payment_info update_payment_btn = `UPDATE PAYMENT METHOD` if has_pending_subscription - billingAddendum = null #`

You will be billed next at the {this.getDisplayNameTier(this.props.subscription.subscription.plan.plan_code)} on the next billing cycle.
` + if this.props.subscription.subscription.plan.plan_code != this.props.subscription.plan_code + billingAddendum = `You have paid only for the {this.getDisplayNameTier(this.props.subscription.subscription.plan.plan_code)} level for the current billing cycle, so there will be a change to the {this.getDisplayNameTier(this.props.subscription.subscription.pending_subscription.plan.plan_code)} level on the next billing cycle.` + else + billingAddendum = `And your plan and billing will switch to the {this.getDisplayNameTier(this.props.subscription.subscription.pending_subscription.plan.plan_code)} level on the next billing cycle.` else if cancelled_subscription && this.props.subscription.desired_plan_code == null && this.props.subscription.plan_code != null billingAddendum = `However, your cancelled {effective_plan_name} plan is still active until the end of the billing cycle.`# `

You will be billed a final time at the {this.getDisplayNameTier(this.props.subscription.subscription.plan.plan_code)} at end of this billing cycle.
` else @@ -315,7 +321,7 @@ AppStore = context.AppStore #until_time.setSeconds(until_time.getSeconds() + @subscriptionRules.remaining_month_play_time) playtime = `
-

You have {this.displayTime(remaining_month_play_time)} time remaining this month. Only the time you spend in a session with 2 or more people uses your session play time.

+

You have {this.displayTime(remaining_month_play_time)} remaining this month. Only the time you spend in a session with 2 or more people uses your session play time.

` else playtime = `
diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee index 14966c736..e6dcebbed 100644 --- a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -39,15 +39,20 @@ StatsInfo = { poor: (user, stats) -> "You have not enough bandwidth to send you a decent quality audio stream.", }, video_rtpbw_rx: { - good: (user, stats) -> "#{user.name} have enough bandwidth to send you a high quality video stream.", - warn: (user, stats) -> "#{user.name} have enough bandwidth to send you a degraded, but sufficient, video stream.", - poor: (user, stats) -> "#{user.name} have not enough bandwidth to send you a decent quality video stream.", + good: (user, stats) -> "#{user.name} has enough bandwidth to send you a high quality video stream.", + warn: (user, stats) -> "#{user.name} has enough bandwidth to send you a degraded, but sufficient, video stream.", + poor: (user, stats) -> "#{user.name} has not enough bandwidth to send you a decent quality video stream.", }, video_rtpbw_tx: { good: (user, stats) -> "You have enough bandwidth to send you a high quality video stream.", warn: (user, stats) -> "You have enough bandwidth to send you a degraded, but sufficient, video stream.", poor: (user, stats) -> "You have not enough bandwidth to send you a decent quality video stream.", }, + ars_vs_p2p: { + good: (user, stats) -> "Your network path is also the lowest latency path available", + warn: (user, stats) -> "Your network path is not the lowest latency path available", + poor: (user, stats) -> "Your network path is not the lowest latency path available", + }, ping: { good: (user, stats) -> "The internet connection between you and #{user.name} has very low latency.", warn: (user, stats) -> "The internet connection between you and #{user.name} has average latency, which may affect staying in sync.", @@ -204,6 +209,8 @@ StatsInfo = { networkStats.push(@stat(network, 'network', 'Video Bw Rx', 'video_rtpbw_rx', Math.round(network.video_rtpbw_rx) + ' k')) if network.video_rtpbw_tx? networkStats.push(@stat(network, 'network', 'Video Bw Tx', 'video_rtpbw_tx', Math.round(network.video_rtpbw_tx) + ' k')) + if network.ars_vs_p2p? + networkStats.push(@stat(network, 'network', 'Net Path', 'ars_vs_p2p', network.ars_vs_p2p)) networkTag = `
diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee index 30c8e2f26..cf288d18d 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -112,6 +112,20 @@ AggregateThresholds = SessionStatThresholds.aggregate total_latency += network.audiojq_median * 2.5 aggregate.one_way = network.ping / 2 aggregate.jq = network.audiojq_median * 2.5 + + if network.using_ars_path == 0 + network.ars_vs_p2p = 'P2P' + if network.preferred_path == 'p2p' + network.ars_vs_p2p_level = 'good' + else + network.ars_vs_p2p_level = 'warn' + else + network.ars_vs_p2p = 'ARS' + if network.preferred_path == 'p2p' + network.ars_vs_p2p_level = 'warn' + else + network.ars_vs_p2p_level = 'good' + console.log("NETWORK!", network) else total_latency = null diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.scss index 8197318cf..19ad759a0 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.scss @@ -422,7 +422,7 @@ &.SessionStatsHover { width:385px; - height:615px; + height:645px; @include border_box_sizing; h3 { diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index b0b1b6280..f94cbeda2 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -328,6 +328,28 @@ class ApiMusicSessionsController < ApiController end begin + @subscription_rules = current_user.subscription_rules + @music_session = ActiveMusicSession.find_by_id(params[:id]) + if @music_session && @music_session.users.count >= 1 + if @music_session.users.count == 1 && @music_session.users.first.id == current_user.id + # if somehow we find ourselves in the session, just ignore that case. + else + @session_rules = { remaining_session_play_time: @music_session.play_time_remaining(current_user) } + + # check if the user has gone past acceptable play time + if !@session_rules[:remaining_session_play_time].nil? && @session_rules[:remaining_session_play_time] <= 0 + # user has no session time for this session left. + render :json => { :errors => {:remaining_session_play_time => ['none remaining']}}, :status => 422 + return + elsif !@subscription_rules[:remaining_month_play_time].nil? && @subscription_rules[:remaining_month_play_time] <= 0 + # user has no session time this month. + render :json => { :errors => {:remaining_month_play_time=> ['none remaining']}}, :status => 422 + return + end + end + + end + @connection = ActiveMusicSession.participant_create( current_user, params[:id], @@ -345,23 +367,12 @@ class ApiMusicSessionsController < ApiController respond_with @connection else @music_session = @connection.music_session + if @session_rules.nil? + @session_rules = { remaining_session_play_time: @music_session.play_time_remaining(current_user) } + end # used in rabl to render extra data @on_join = true - @subscription_rules = current_user.subscription_rules - @session_rules = { remaining_session_play_time: @music_session.play_time_remaining(current_user) } - - # check if the user has gone past acceptable play time - if !@session_rules[:remaining_session_play_time].nil? && @session_rules[:remaining_session_play_time] <= 0 - # user has no session time for this session left. - render :json => { :errors => {:remaining_session_play_time => ['none remaining']}}, :status => 422 - return - elsif !@subscription_rules[:remaining_month_play_time].nil? && @subscription_rules[:remaining_month_play_time] <= 0 - # user has no session time this month. - render :json => { :errors => {:remaining_month_play_time=> ['none remaining']}}, :status => 422 - return - end - respond_with @music_session, responder: ApiResponder, :status => 201, :location => api_session_detail_url(@connection.music_session) end