require 'aws-sdk' class ApiMusicSessionsController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user, :except => [ :add_like, :show, :show_history, :add_session_info_comment, :auth ] before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync, :jam_track_open, :jam_track_close, :backing_track_open, :backing_track_close, :metronome_open, :metronome_close, :attach_recording] before_filter :lookup_perm_session, only: [:get_livestream, :create_livestream, :livestream_transition] skip_before_filter :api_signed_in_user, only: [:perf_upload] around_filter :transactions_filter, only:[:sms_index, :ams_index] respond_to :json def index # returns a list of sessions which are hopefully interesting to you as three buckets (concatenated). The 1st bucket # is those session to which you've been invited. The 2nd bucket is those sessions which are about a band that you # are in or in which a friend is participating. The 3rd bucket is everything else (sessions - invites - friends - bands). # params[:participants] is either nil, meaning "everything", or it's an array of musician ids # params[:genres] is either nil, meaning "everything", or it's an array of genre ids # params[:friends_only] does the obvious. you get invited and friends sessions only. # params[:my_bands_only] also does the obvious. you get invited and bands sessions only. # params[:keyword] matches only sessions with that text in the description # params[:as_musician] only returns sessions you can join as a musician (if true) or listen to a fan (if false) # Importantly, friends_only and my_bands_only are ORed not ANDed. So, if you specify both as true, you'll get more # results than if only one or the other is true, not fewer. if either is true you won't see the "everything else" # sessions. @music_sessions = ActiveMusicSession.index(current_user, participants: params[:participants], genres: params[:genres], friends_only: params[:friends_only], my_bands_only: params[:my_bands_only], keyword: params[:keyword], as_musician: params[:as_musician]) end def nindex # returns a list of sessions which are hopefully interesting to you as three buckets (concatenated). The 1st bucket # is those session to which you've been invited. The 2nd bucket is those sessions which are about a band that you # are in or in which a friend is participating. The 3rd bucket is everything else (sessions - invites - friends - bands). # pretty much the same as #index above, except scores are also returned. # params[:client_id] is the client_id of the client making the call. needed to resovle scoring. # params[:participants] is either nil, meaning "everything", or it's an array of musician ids # params[:genres] is either nil, meaning "everything", or it's an array of genre ids # params[:friends_only] does the obvious. you get invited and friends sessions only. # params[:my_bands_only] also does the obvious. you get invited and bands sessions only. # params[:keyword] matches only sessions with that text in the description # params[:as_musician] only returns sessions you can join as a musician (if true) or listen to a fan (if false) # params[:offset] also does the obvious, starts at offset in the results (e.g. 0) # params[:limit] also does the obvious, limits number of rows returned (e.g. 20) # Importantly, friends_only and my_bands_only are ORed not ANDed. So, if you specify both as true, you'll get more # results than if only one or the other is true, not fewer. if either is true you won't see the "everything else" # sessions. @music_sessions = ActiveMusicSession.nindex(current_user, client_id: params[:client_id], participants: params[:participants], genres: params[:genres], friends_only: params[:friends_only], my_bands_only: params[:my_bands_only], keyword: params[:keyword], as_musician: params[:as_musician], offset: params[:offset], limit: params[:limit]) end def friend_active_index @music_sessions = ActiveMusicSession.friend_active_index(current_user, params) end def public_index @music_sessions = ActiveMusicSession.public_index(current_user, params) end def ams_index # returns a relation which will produce a list of music_sessions which are active and augmented with attributes # tag and latency, then sorted by tag, latency, and finally music_sessions.id (for stability). the list is # filtered by genre, lang, and keyword, then paged by offset and limit (those are record numbers not page numbers). # tag is 1 for chosen rsvp'd sessions, 2 for invited sessions, 3 for all others (musician_access). # TODO: if you're the creator of a session it will be treated the same as if you had rsvp'd and been accepted. ActiveRecord::Base.transaction do @music_sessions, @user_scores = ActiveMusicSession.ams_index(current_user, params) end end def sms_index # returns a relation which will produce a list of music_sessions which are scheduled and augmented with attributes # tag and latency, then sorted by tag, latency, and finally music_sessions.id (for stability). the list is # filtered by genre, lang, and keyword, then paged by offset and limit (those are record numbers not page numbers). # tag is 1 for chosen rsvp'd sessions, 2 for invited sessions, 3 for all others (musician_access). # TODO: if you're the creator of a session it will be treated the same as if you had rsvp'd and been accepted. ActiveRecord::Base.transaction do @music_sessions, @user_scores = MusicSession.sms_index(current_user, params) return end end def sms_index_2 @music_sessions = MusicSession.scheduled_index(current_user, params) @user_scores = {} end def scheduled @music_sessions = MusicSession.scheduled(current_user) end def list_history @music_sessions = MusicSession.history(current_user, {offset: params[:offset], limit: params[:limit]}) end def scheduled_rsvp @music_sessions = MusicSession.scheduled_rsvp(current_user) end def create @music_session = MusicSession.create(current_user, params) if @music_session.errors.any? response.status = :unprocessable_entity respond_with @music_session else respond_with @music_session, responder: ApiResponder end end def create_legacy client_id = params[:client_id] if client_id.nil? raise JamArgumentError, "client_id must be specified" end unless params[:intellectual_property] raise JamArgumentError, "You must agree to the intellectual property terms" end band = Band.find(params[:band]) unless params[:band].nil? # creating the MusicSession right here was added as part of the scheduled sessions changes # Why? The new order of things is to always have a MusicSession before a ActiveMusicSession # So, we have to make MusicSession, and pass in it's .id to MusicSessionManager.new.create() # so that the ActiveMusicSession can have the same .id as the MusicSession history = MusicSession.new history.name = params[:description][0..40] history.description = params[:description] history.musician_access = params[:musician_access] history.approval_required = params[:approval_required] history.fan_chat = params[:fan_chat] history.fan_access = params[:fan_access] history.band = band history.genre_id = (params[:genres].length > 0 ? params[:genres][0] : nil) if params[:genres] history.legal_terms = params[:legal_terms] history.language = 'eng' history.legal_policy = 'standard' history.creator = current_user history.school_id = current_user.school_id history.is_platform_instructor = current_user.is_platform_instructor history.save if history.errors.any? @music_session = history response.status = :unprocessable_entity respond_with @music_session else history.reload # to get .id back @music_session = MusicSessionManager.new.create( history, current_user, client_id, params[:description], params[:musician_access], params[:approval_required], params[:fan_chat], params[:fan_access], band, params[:genres], params[:tracks], params[:legal_terms], params[:audio_latency]) if @music_session.errors.any? response.status = :unprocessable_entity respond_with @music_session else respond_with @music_session, responder: ApiResponder, :location => api_session_detail_url(@music_session) end end end def show unless @music_session.can_see? current_user # render :json => { :message => ValidationMessages::PERMISSION_VALIDATION_ERROR }, :status => 403 raise JamRuby::JamPermissionError end end def update @music_session = MusicSessionManager.new.update(current_user, @music_session.music_session, params[:name], params[:description], params[:genre] ? Genre.find(params[:genre]) : nil, params[:language], params[:musician_access], params[:approval_required], params[:fan_chat], params[:fan_access], params[:session_controller], params[:friends_can_join]) if @music_session.errors.any? # we have to do this because api_session_detail_url will fail with a bad @music_session response.status = :unprocessable_entity respond_with @music_session else @music_session = @music_session.active_music_session respond_with_model(@music_session) end end def attach_recording me = current_user recordings = params[:recordings] recordings.each do |recording_data| #recording = Recording.find(recording_data[:id]) claimed_recording = ClaimedRecording.find_by_id(recording_data[:id]) if claimed_recording.nil? || claimed_recording.user != current_user raise JamPermissionError, 'only owner of claimed_recording can associated it with a lesson' end msg = ChatMessage.create(me, @music_session, '', ChatMessage::CHANNEL_SESSION, nil, nil, nil, 'JamKazam Recording', nil, claimed_recording) end render :json => {}, :status => 200 end def session_update begin @music_session = MusicSession.find(params[:id]) if @music_session.creator == current_user @music_session.name = params[:name] if params.include? :name @music_session.description = params[:description] if params.include? :description @music_session.musician_access = params[:musician_access] if params.include? :musician_access @music_session.approval_required = params[:approval_required] if params.include? :approval_required @music_session.fan_chat = params[:fan_chat] if params.include? :fan_chat @music_session.fan_access = params[:fan_access] if params.include? :fan_access @music_session.friends_can_join = params[:friends_can_join] if params.include? :friends_can_join @music_session.genre = Genre.find_by_id(params[:genres][0]) if params.include?(:genres) && params[:genres] && params[:genres].length > 0 @music_session.legal_policy = params[:legal_policy] if params.include? :legal_policy @music_session.language = params[:language] if params.include? :language @music_session.scheduled_start = MusicSession.parse_scheduled_start(params[:start], params[:timezone]) if params.include?(:start) && params.include?(:timezone) @music_session.scheduled_duration = params[:duration] + ' minutes' if params.include? :duration @music_session.timezone = params[:timezone] if params.include? :timezone @music_session.recurring_mode = params[:reoccurrence] if params.include? :reoccurrence @music_session.open_rsvps = params[:open_rsvps] if params.include? :open_rsvps @music_session.band = (params[:band] ? Band.find(params[:band]) : nil) if params.include? :band @music_session.save if params.include? :music_notations notations = JSON.parse(params[:music_notations]) notations.each do |n| notation = MusicNotation.find(n["id"]) notation.music_session = @music_session notation.save @music_session.music_notations << notation end end if @music_session.errors.any? response.status = :unprocessable_entity respond_with @music_session else Notification.send_scheduled_session_rescheduled(@music_session) if @music_session.scheduling_info_changed respond_with @music_session, responder: ApiResponder, :location => api_session_history_detail_url(@music_session) end else render :json => { :message => ValidationMessages::PERMISSION_VALIDATION_ERROR }, :status => 403 end rescue ActiveRecord::RecordNotFound render :json => { :message => ValidationMessages::SESSION_NOT_FOUND }, :status => 404 end end def destroy begin music_session = MusicSession.find(params[:id]) if music_session.can_cancel? current_user Notification.send_scheduled_session_cancelled music_session music_session.canceled = true music_session.save respond_with responder: ApiResponder, :status => 204 else render :json => { :message => ValidationMessages::PERMISSION_VALIDATION_ERROR }, :status => 403 end rescue ActiveRecord::RecordNotFound render :json => { :message => ValidationMessages::SESSION_NOT_FOUND }, :status => 404 end end def participant_show @connection = Connection.find_by_client_id(params[:id]) end def participant_create_legacy @connection = MusicSessionManager.new.participant_create( current_user, params[:id], params[:client_id], params[:as_musician], params[:tracks], params[:audio_latency]) if @connection.errors.any? response.status = :unprocessable_entity respond_with @connection else respond_with @connection, responder: ApiResponder, :location => api_session_participant_detail_url(@connection.client_id) end end def participant_create client_id = params[:client_id] if client_id.nil? raise JamArgumentError, "client_id must be specified" 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], params[:client_id], params[:as_musician], params[:tracks], params[:audio_latency], params[:client_role], params[:parent_client_id] ) if @connection.errors.any? response.status = :unprocessable_entity 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 respond_with @music_session, responder: ApiResponder, :status => 201, :location => api_session_detail_url(@connection.music_session) end rescue ActiveRecord::RecordNotFound render :json => { :message => ValidationMessages::SESSION_NOT_FOUND }, :status => 404 end end def participant_delete client_id = params[:id] if client_id.present? && client_id != 'undefined' @connection = Connection.find_by_client_id!(client_id) active_music_session = ActiveMusicSession.find(@connection.music_session_id) MusicSessionManager.new.participant_delete(current_user, @connection, active_music_session) end respond_with @connection, responder: ApiResponder end def participant_rating if @history = MusicSessionUserHistory.latest_history(params[:client_id]) if request.post? rating = params[:rating].to_i rating_val = params[:rating] comment = params[:comment] backend_details = params[:backend_details] @history.add_rating(rating, comment, backend_details) @history.save if @history.errors.any? response.status = :unprocessable_entity respond_with @history return else if @history.good_rating? && @history.user.first_good_music_session_at.nil? @history.user.first_good_music_session_at = Time.now @history.user.save end end begin lesson_link = nil session_link = @history.music_session.admin_url if @history.music_session.lesson_session session_type = "Lesson" lesson_link = @history.music_session.lesson_session.admin_url else session_type = "Session" end rating_type = rating > 0 ? "Good" : "Bad" with_comment = (comment.nil? || comment.strip.empty?) ? '' : ' - With Comment' subject = "#{current_user.name} Rated Their #{session_type} #{rating_type}#{with_comment}!" body = "Session Type: #{session_type}\n" body << "Session Rating: #{rating_type}\n" body << "Session Rating Arg: #{rating_val}\n" body << "User: #{current_user.email}\n" body << "Music Session URL: #{session_link}\n" if lesson_link body << "Lesson URL: #{lesson_link}\n" end body << "Session Comments: #{comment}\n" if backend_details begin body << "Backend Detail:\n" + JSON.pretty_generate(backend_details) + "\n" rescue Exception => e puts "Unable to send out retails ratings due to bad backend data #{e}" logger.error("Unable to send out retails ratings due to bad backend data #{e}") end else body << "Backend Detail: NONE in POST!!\n" end backend_details["rating_type"] = rating_type backend_details["comment"] = comment succeeded = JamRuby::ElasticSearch.new.session_ratings(@history.music_session, current_user, backend_details) body << (succeeded ? "Stored in ElasticSearch" : "Unable to store in ElasticSearch!") AdminMailer.jamclass_alerts({subject: subject, body: body}).deliver_now rescue Exception => e puts "Exception sending out ratings email. Boo #{e}" logger.error("Exception sending out ratings email. Boo #{e}") end render :json => {}, :status => :ok elsif request.get? render :json => { :should_rate_session => @history.should_rate_session? }, :status => :ok end else render :json => { :message => ValidationMessages::SESSION_NOT_FOUND }, :status => 404 end end def track_index @tracks = Track.index(current_user, params[:id]) end def track_show begin @track = Track.joins(:connection) .where(:connections => {:user_id => "#{current_user.id}"}) .find(params[:track_id]) rescue ActiveRecord::RecordNotFound render :json => { :message => ValidationMessages::TRACK_NOT_FOUND }, :status => 404 end end def track_sync @tracks = MusicSessionManager.new.sync_tracks(@music_session, params[:client_id], params[:tracks], params[:backing_tracks], params[:metronome_open]) unless @tracks.kind_of? Array # we have to do this because api_session_detail_url will fail with a bad @tracks response.status = :unprocessable_entity respond_with @tracks else respond_with @tracks, responder: ApiResponder end end def track_create @track = Track.save(nil, params[:connection_id], params[:instrument_id], params[:sound], params[:client_track_id], params[:client_resource_id]) respond_with @track, responder: ApiResponder, :status => 201, :location => api_session_track_detail_url(@track.connection.music_session, @track) end def track_update begin @track = Track.save(params[:track_id], nil, params[:instrument_id], params[:sound], params[:client_track_id], params[:client_resource_id]) respond_with @track, responder: ApiResponder, :status => 200 rescue ActiveRecord::RecordNotFound render :json => { :message => ValidationMessages::TRACK_NOT_FOUND }, :status => 404 end end def track_destroy begin @track = Track.find(params[:track_id]) unless @track.nil? @track.delete respond_with responder: ApiResponder, :status => 204 end rescue ActiveRecord::RecordNotFound render :json => { :message => ValidationMessages::TRACK_NOT_FOUND }, :status => 404 end end def perf_upload # example of using curl to access this API: # curl -L -T some_file -X PUT http://localhost:3000/api/sessions/[SESSION_ID]/perf.json?client_id=[CLIENT_ID] music_session = MusicSession.find(params[:id]) msuh = MusicSessionUserHistory.find_by_client_id(params[:client_id]) @perfdata = MusicSessionPerfData.new @perfdata.client_id = params[:client_id] @perfdata.music_session = music_session unless @perfdata.save # we have to do this because api_session_detail_url will fail with a bad @music_session response.status = :unprocessable_entity respond_with @perfdata return end if Rails.application.config.storage_type == :fog uri = @perfdata.uri s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id, :secret_access_key => Rails.application.config.aws_secret_access_key) bucket = s3.buckets[SampleApp::Application.config.aws_bucket] expire = Time.now + 20.years read_url = bucket.objects[uri].url_for(:read, :expires => expire, :'response_content_type' => 'text/csv').to_s @perfdata.update_attribute(:uri, read_url) logger.debug("*** client can read url #{read_url}") write_url = bucket.objects[uri].url_for(:write, :expires => expire, :'response_content_type' => 'text/csv').to_s logger.debug("*** client can upload to url #{write_url}") redirect_to write_url else if params[:redirected_back].nil? || !params[:redirected_back] # first time that a client has asked to do a PUT (not redirected back here) redirect_to request.fullpath + '&redirected_back=true' else # we should store it here to aid in development, but we don't have to until someone wants the feature # so... just return 200 render :json => { :id => @perfdata.id }, :status => 200 end end end def add_comment if params[:id].blank? render :json => { :message => "Session ID is required" }, :status => 400 return end if params[:user_id].blank? render :json => { :message => "User ID is required" }, :status => 400 return end if params[:comment].blank? render :json => { :message => "Comment is required" }, :status => 400 return end comment = MusicSessionComment.new comment.music_session_id = params[:id] comment.creator_id = params[:user_id] comment.comment = @@html_encoder.encode(params[:comment]) comment.ip_address = request.remote_ip comment.save if comment.errors.any? render :json => { :errors => comment.errors }, :status => 422 return else render :json => {}, :status => 201 return end end def add_session_info_comment if params[:id].blank? render :json => { :message => "Session ID is required" }, :status => 400 return end if params[:comment].blank? render :json => { :message => "Comment is required" }, :status => 400 return end comment = SessionInfoComment.new comment.music_session_id = params[:id] comment.creator_id = current_user.id comment.comment = @@html_encoder.encode(params[:comment]) comment.save if comment.errors.any? render :json => { :errors => comment.errors }, :status => 422 return else music_session = MusicSession.find(params[:id]) Notification.send_scheduled_session_comment(music_session, current_user, params[:comment]) render :json => {}, :status => 201 return end end def add_like if params[:id].blank? render :json => { :message => "Session ID is required" }, :status => 400 return end liker = MusicSessionLiker.new liker.music_session_id = params[:id] liker.liker_id = params[:user_id] liker.ip_address = request.remote_ip liker.save if liker.errors.any? render :json => { :message => "Unexpected error occurred" }, :status => 500 return else render :json => {}, :status => 201 return end end def show_history if current_user ActiveRecord::Base.transaction do @history, @user_scores = MusicSession.session_with_scores(current_user, params[:id], params[:includePending]) end else @history = MusicSession.find(params[:id]) end end def claimed_recording_start @music_session.claimed_recording_start(current_user, ClaimedRecording.find(params[:claimed_recording_id])) if @music_session.errors.any? # we have to do this because api_session_detail_url will fail with a bad @music_session response.status = :unprocessable_entity respond_with @music_session else respond_with @music_session, responder: ApiResponder end end def claimed_recording_stop @music_session.claimed_recording_stop if @music_session.errors.any? # we have to do this because api_session_detail_url will fail with a bad @music_session response.status = :unprocessable_entity respond_with @music_session else respond_with @music_session, responder: ApiResponder end end def jam_track_open unless @music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @jam_track = JamTrack.find(params[:jam_track_id]) jam_track_right = @jam_track.right_for_user(current_user) unless jam_track_right raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end if jam_track_right.redeemed == true && current_user.email_needs_verification raise JamPermissionError, ValidationMessages::VERIFY_EMAIL end @music_session.open_jam_track(current_user, @jam_track) respond_with_model(@music_session) end def jam_track_close unless @music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @music_session.close_jam_track respond_with_model(@music_session) end def backing_track_open unless @music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @backing_track_path = params[:backing_track_path] @music_session.open_backing_track(current_user, @backing_track_path) respond_with_model(@music_session) end def backing_track_close unless @music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @music_session.close_backing_track() respond_with_model(@music_session) end def metronome_open unless @music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @music_session.open_metronome(current_user) respond_with_model(@music_session) end def metronome_close unless @music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @music_session.close_metronome() respond_with_model(@music_session) end def get_livestream unless @music_session.active_music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @livestream = @music_session.get_broadcast(current_user) if @livestream @music_session.refresh_stream(current_user, @livestream) respond_with (@livestream), responder: ApiResponder else render :json => { :message => "No broadcast associated with this session" }, :status => 404 end end def create_livestream unless @music_session.active_music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end @livestream = @music_session.create_stream(current_user, params[:options]) respond_with (@livestream), responder: ApiResponder end def livestream_transition unless @music_session.active_music_session.users.exists?(current_user.id) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end broadcast = @music_session.get_broadcast(current_user) if broadcast @livestream = @music_session.transition_broadcast(current_user, broadcast, params[:broadcastStatus]) respond_with (@livestream), responder: ApiResponder else render :json => { :message => "No broadcast associated with this session" }, :status => 404 end end def auth token = nil begin token = TempToken.find_by_token!(params[:token]) rescue ActiveRecord::RecordNotFound return render json: { code: "invalid_token", message: "No token found for '#{params[:token]}'" }, status: :forbidden end if token.token == Rails.application.config.video_open_room return render json: { name: token.user.name, user_id: token.user.id }, status: 200 end begin music_session = ActiveMusicSession.find(params[:session_id]) if !music_session.users.exists?(token.user.id) return render json: { code: "not_in_session", message: "Not a member of the session" }, status: :forbidden end return render json: { name: token.user.name, user_id: token.user.id }, status: 200 rescue ActiveRecord::RecordNotFound return render json: { code: "session_ended", message: "The session is over" }, status: 404 end end private def lookup_session @music_session = ActiveMusicSession.find(params[:id]) end def lookup_perm_session @music_session = MusicSession.find(params[:id]) end end