* the form VRFS-922, VRFS-918 (mix checks user permissions), VRFS-945 (verify download permissions for tracks)

This commit is contained in:
Seth Call 2013-12-30 18:34:15 +00:00
parent d8a5dbd031
commit ab70d15eaf
27 changed files with 606 additions and 146 deletions

View File

@ -81,3 +81,4 @@ notification_band_invite.sql
band_photo_filepicker.sql
bands_geocoding.sql
store_s3_filenames.sql
discardable_recorded_tracks.sql

View File

@ -0,0 +1,5 @@
-- there are no valid recordings and mixes at this time
DELETE FROM recorded_tracks;
DELETE FROM mixes;
ALTER TABLE recorded_tracks ADD COLUMN discard BOOLEAN DEFAULT FALSE NOT NULL;

View File

@ -1,8 +1,13 @@
module JamRuby
class ClaimedRecording < ActiveRecord::Base
validates :name, no_profanity: true
validates :description, no_profanity: true
validates :name, no_profanity: true, length: {minimum: 3, maximum: 64}, presence: true
validates :description, no_profanity: true, length: {maximum: 8000}
validates :is_public, :inclusion => {:in => [true, false]}
validates :is_downloadable, :inclusion => {:in => [true, false]}
validates :genre, presence: true
validates_uniqueness_of :recording_id, :scope => :user_id
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :claimed_recordings
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :claimed_recordings

View File

@ -8,6 +8,8 @@ module JamRuby
self.table_name = "recorded_tracks"
self.primary_key = 'id'
attr_accessible :discard
SOUND = %w(mono stereo)
MAX_PART_FAILURES = 3
MAX_UPLOAD_FAILURES = 10
@ -28,6 +30,10 @@ module JamRuby
validate :validate_too_many_upload_failures
def can_download?(some_user)
!ClaimedRecording.find_by_user_id_and_recording_id(some_user.id, recording.id).nil?
end
def upload_starting?
next_part_to_upload_was == 0 && next_part_to_upload == 1
end

View File

@ -28,6 +28,17 @@ module JamRuby
end
end
def recorded_tracks_for_user(user)
unless self.users.exists?(user)
raise PermissionError, "user was not in this session"
end
RecordedTrack.where(:recording_id=> self.id).where(:user_id=> user.id)
end
def has_access?(user)
return users.exists?(user)
end
# Start recording a session.
def self.start(music_session, owner)
recording = nil
@ -75,22 +86,11 @@ module JamRuby
# Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording.
def claim(user, name, description, genre, is_public, is_downloadable)
# if self.users.include?(user)
# raise PermissionError, "user already claimed this recording"
# end
unless self.users.exists?(user)
raise PermissionError, "user was not in this session"
end
if self.music_session.is_recording?
raise PermissionError, "recording cannot be claimed while it is being recorded"
end
if name.nil? || genre.nil? || is_public.nil? || is_downloadable.nil?
raise PermissionError, "recording must have name, genre and flags"
end
claimed_recording = ClaimedRecording.new
claimed_recording.user = user
claimed_recording.recording = self
@ -117,41 +117,22 @@ module JamRuby
self.destroy
end
# Returns the list of files the user needs to upload. This will only ever be recordings
def self.upload_file_list(user)
files = []
User.joins(:recordings).joins(:recordings => :recorded_tracks)
.where(%Q{ recordings.duration IS NOT NULL })
.where("recorded_tracks.user_id = '#{user.id}'")
.where(%Q{ recorded_tracks.fully_uploaded = FALSE }).each do |user|
user.recordings.each.do |recording|
recording.recorded_tracks.each do |recorded_track|
files.push(
{
:type => "recorded_track",
:id => recorded_track.client_track_id,
:url => recorded_track.url # FIXME IS THIS RIGHT?
}
)
end
end
files
end
def self.list_downloads(user, limit = 100, since = 0)
since = 0 unless since || since == '' # guard against nil
downloads = []
# That second join is important. It's saying join off of recordings, NOT user. If you take out the
# ":recordings =>" part, you'll just get the recorded_tracks that I played. Very different!
User.joins(:recordings).joins(:recordings => :recorded_tracks)
# we also only allow you to be told about downloads if you have claimed the recording
User.joins(:recordings).joins(:recordings => :recorded_tracks).joins(:recordings => :claimed_recordings)
.order(%Q{ recorded_tracks.id })
.where(%Q{ recorded_tracks.fully_uploaded = TRUE })
.where('recorded_tracks.id > ?', since)
.where('claimed_recordings.user_id = ?', user)
.where(:id => user.id).limit(limit).each do |theuser|
theuser.recordings.each do |recording|
recording.recorded_tracks.each do |recorded_track|
# recorded_track = user.claimed_recordings.first.recording.recorded_tracks.first
downloads.push(
{
:type => "recorded_track",
@ -159,7 +140,7 @@ module JamRuby
:recording_id => recording.id,
:length => recorded_track.length,
:md5 => recorded_track.md5,
:url => recorded_track.filename,
:url => recorded_track.url,
:next => recorded_track.id
}
)
@ -169,11 +150,11 @@ module JamRuby
latest_recorded_track = downloads[-1][:next] if downloads.length > 0
# HOW TO LIMIT ON USER
User.joins(:recordings).joins(:recordings => :mixes)
.order('mixes.id')
.where('mixes.completed_at IS NOT NULL')
.where('mixes.id > ?', since)
.where(:id => user.id)
.limit(limit).each do |theuser|
theuser.recordings.each do |recording|
recording.mixes.each do |mix|
@ -184,7 +165,7 @@ module JamRuby
:recording_id => recording.id,
:length => mix.length,
:md5 => mix.md5,
:url => mix.filename,
:url => mix.url,
:created_at => mix.created_at,
:next => mix.id
}

View File

@ -0,0 +1,102 @@
require 'spec_helper'
def valid_claimed_recording
@claimed_recording.name = "hello"
@claimed_recording.description = "description"
@claimed_recording.genre = Genre.first
@claimed_recording.is_public = true
@claimed_recording.is_downloadable = true
@claimed_recording
end
describe ClaimedRecording do
before(:each) do
@user = FactoryGirl.create(:user)
@connection = FactoryGirl.create(:connection, :user => @user)
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
@music_session.save
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload
@genre = FactoryGirl.create(:genre)
@recording.claim(@user, "name", "description", @genre, true, true)
@recording.reload
@claimed_recording = @recording.claimed_recordings.first
end
describe "sucessful save" do
it "with default case" do
valid_claimed_recording
@claimed_recording.save.should be_true
end
end
describe "update name" do
it "with nil" do
valid_claimed_recording
@claimed_recording.name = nil
@claimed_recording.save.should be_false
@claimed_recording.errors[:name].length.should == 2
@claimed_recording.errors[:name].select { |value| value.include?("can't be blank") }.length.should == 1
@claimed_recording.errors[:name].select { |value| value.include?("is too short") }.length.should == 1
end
it "too short" do
valid_claimed_recording
@claimed_recording.name = "a"
@claimed_recording.save.should be_false
@claimed_recording.errors[:name].length.should == 1
@claimed_recording.errors[:name].select { |value| value.include?("is too short") }.length.should == 1
end
end
describe "update description" do
it "with nil" do
valid_claimed_recording
@claimed_recording.description = nil
@claimed_recording.save.should be_true
end
end
describe "update is_public" do
it "with nil" do
valid_claimed_recording
@claimed_recording.is_public = nil
@claimed_recording.save.should be_false
@claimed_recording.errors[:is_public].length.should == 1
@claimed_recording.errors[:is_public].should == ["is not included in the list"]
end
end
describe "update is_downloadable" do
it "with nil" do
valid_claimed_recording
@claimed_recording.is_downloadable = nil
@claimed_recording.save.should be_false
@claimed_recording.errors[:is_downloadable].length.should == 1
@claimed_recording.errors[:is_downloadable].should == ["is not included in the list"]
end
end
describe "update genre" do
it "with nil" do
valid_claimed_recording
@claimed_recording.genre = nil
@claimed_recording.save.should be_false
@claimed_recording.errors[:genre].length.should == 1
@claimed_recording.errors[:genre].should == ["can't be blank"]
end
end
describe "multiple claims" do
it "not valid" do
duplicate = @recording.claim(@user, "name", "description", @genre, true, true)
duplicate.valid?.should be_false
duplicate.errors[:recording_id].should == ['has already been taken']
end
end
end

View File

@ -11,6 +11,7 @@ describe Mix do
@music_session.save
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.claim(@user, "name", "description", Genre.first, true, true)
@mix = Mix.schedule(@recording, "{}")
end
@ -62,6 +63,23 @@ describe Mix do
@mix.sign_url.should_not be_nil
end
it "mixes are restricted by user" do
@mix.finish(1, "abc")
@mix.reload
@mix.errors.any?.should be_false
@user2 = FactoryGirl.create(:user)
recordings = Recording.list_downloads(@user)["downloads"]
recordings.length.should == 1
recordings[0][:type].should == "mix"
recordings[0][:id].should == @mix.id
recordings = Recording.list_downloads(@user2)["downloads"]
recordings.length.should == 0
end
end

View File

@ -51,6 +51,18 @@ describe RecordedTrack do
@recorded_track.sign_url.should_not be_nil
end
it "can not be downloaded if no claimed recording" do
user2 = FactoryGirl.create(:user)
@recorded_track = RecordedTrack.create_from_track(@track, @recording)
@recorded_track.can_download?(user2).should be_false
@recorded_track.can_download?(@user).should be_false
end
it "can be downloaded if there is a claimed recording" do
@recorded_track = RecordedTrack.create_from_track(@track, @recording)
@recording.claim(@user, "my recording", "my description", Genre.first, true, true).errors.any?.should be_false
@recorded_track.can_download?(@user).should be_true
end
describe "aws-based operations", :aws => true do

View File

@ -8,7 +8,26 @@ describe Recording do
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
end
end
it "should allow finding of recorded tracks" do
user2 = FactoryGirl.create(:user)
connection2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session)
track2 = FactoryGirl.create(:track, :connection => connection2, :instrument => @instrument)
@recording = Recording.start(@music_session, @user)
user1_recorded_tracks = @recording.recorded_tracks_for_user(@user)
user1_recorded_tracks[0].should == @user.recorded_tracks[0]
user1_recorded_tracks.length.should == 1
user2_recorded_tracks = @recording.recorded_tracks_for_user(user2)
user2_recorded_tracks.length.should == 1
user2_recorded_tracks[0].should == user2.recorded_tracks[0]
RecordedTrack.update(user1_recorded_tracks, :discard => true)
user1_recorded_tracks[0].reload
user1_recorded_tracks[0].discard.should be_true
end
it "should set up the recording properly when recording is started with 1 user in the session" do
@music_session.is_recording?.should be_false
@ -118,16 +137,6 @@ describe Recording do
expect { @recording.claim(user2, "name", "description", @genre, true, true) }.to raise_error
end
it "should fail if a user tries to claim a recording twice" do
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload
@genre = FactoryGirl.create(:genre)
@recording.claim(@user, "name", "description", @genre, true, true)
@recording.reload
expect { @recording.claim(@user, "name", "description", @genre, true, true) }.to raise_error
end
it "should allow editing metadata for claimed recordings" do
@recording = Recording.start(@music_session, @user)
@recording.stop
@ -206,6 +215,20 @@ describe Recording do
Recording.list_uploads(@user, 10, uploads["next"])["uploads"].length.should == 0
end
it "should return a download only if claimed" do
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload
@genre = FactoryGirl.create(:genre)
@recording.claim(@user, "Recording", "Recording Description", @genre, true, true)
downloads = Recording.list_downloads(@user)
downloads["downloads"].length.should == 0
@recorded_track = RecordedTrack.where(:recording_id => @recording.id)[0]
@recorded_track.update_attribute(:fully_uploaded, true)
downloads = Recording.list_downloads(@user)
downloads["downloads"].length.should == 1
end
it "should return a file list for a user properly" do
pending
stub_const("APP_CONFIG", app_config)
@ -292,6 +315,7 @@ describe Recording do
timeline.last["timestamp"].should == @recording.duration
timeline.last["end"].should == true
end
end

View File

@ -91,7 +91,7 @@ if ENV['JAMWEB_QT5'] == '1'
else
gem "capybara-webkit"
end
gem 'capybara-screenshot'
gem 'capybara-screenshot', :path => '/Users/seth/workspace/capybara-screenshot'
gem 'cucumber-rails', :require => false #, '1.3.0', :require => false
gem 'guard-spork', '0.3.2'
gem 'spork', '0.9.0'

View File

@ -65,6 +65,7 @@
$.each(genreList, function(index, value) {
values.push(value.toLowerCase());
});
console.log("OH HAI O")
var selectedVal = $('select', parentSelector).val(values);
}

View File

@ -507,6 +507,30 @@
})
}
function claimRecording(options) {
var recordingId = options["id"];
return $.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
url: "/api/recordings/" + recordingId + "/claim",
data: JSON.stringify(options)
})
}
function discardRecording(options) {
var recordingId = options["id"];
return $.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
url: "/api/recordings/" + recordingId + "/discard",
data: JSON.stringify(options)
})
}
function putTrackSyncChange(options) {
var musicSessionId = options["id"]
delete options["id"];
@ -556,6 +580,8 @@
this.startRecording = startRecording;
this.stopRecording = stopRecording;
this.getRecording = getRecording;
this.claimRecording = claimRecording;
this.discardRecording = discardRecording;
this.putTrackSyncChange = putTrackSyncChange;
this.createBand = createBand;
this.updateBand = updateBand;

View File

@ -0,0 +1,155 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.RecordingFinishedDialog = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
function resetForm() {
// remove all display errors
$('#recording-finished-dialog form .error-text').remove()
$('#recording-finished-dialog form .error').removeClass("error")
}
function beforeShow() {
resetForm();
var parentSelector = '#recording-finished-dialog div.genre-selector';
context.JK.GenreSelectorHelper.render(parentSelector);
// preset genre from 1st genre in current session
var currentOrLastSession = JK.CurrentSessionModel.getCurrentOrLastSession();
if(currentOrLastSession && currentOrLastSession.genres.length > 0) {
var genreDescription = currentOrLastSession.genres[0];
context.JK.GenreSelectorHelper.setSelectedGenres(parentSelector, [genreDescription]);
}
}
function afterHide() {
}
function discardRecording(e) {
resetForm();
registerDiscardRecordingHandlers(false);
var recordingId = JK.CurrentSessionModel.recordingModel.currentOrLastRecordingId();
rest.discardRecording({
id: recordingId
})
.done(function() {
console.error("recording discarded by user. recordingId=%o", recordingId);
})
.fail(function(jqXHR){
console.error("recording discard by user failed. recordingId=%o. reason: %o", recordingId, jqXHR.responseText);
})
.always(function() {
app.layout.closeDialog('recordingFinished')
registerDiscardRecordingHandlers(true);
})
return false;
}
function claimRecording(e){
resetForm();
registerClaimRecordingHandlers(false);
var recordingId = JK.CurrentSessionModel.recordingModel.currentOrLastRecordingId();
var name = $('#recording-finished-dialog form input[name=name]').val();
var description = $('#recording-finished-dialog form textarea[name=description]').val();
var genre = $('#recording-finished-dialog form select[name=genre]').val();
var is_public = $('#recording-finished-dialog form input[name=is_public]').is(':checked')
var is_downloadable = $('#recording-finished-dialog form input[name=is_downloadable]').is(':checked');
rest.claimRecording({
id : recordingId,
name: name,
description: description,
genre: genre,
is_public: is_public,
is_downloadable: is_downloadable
})
.done(function() {
app.layout.closeDialog('recordingFinished')
})
.fail(function(jqXHR) {
if(jqXHR.status == 422) {
var errors = JSON.parse(jqXHR.responseText);
var $name_errors = context.JK.format_errors('name', errors);
if($name_errors) $('#recording-finished-dialog form input[name=name]').closest('div.field').addClass('error').end().after($name_errors);
var $description_errors = context.JK.format_errors('description', errors);
if($description_errors) $('#recording-finished-dialog form input[name=description]').closest('div.field').addClass('error').end().after($description_errors);
var $genre_errors = context.JK.format_errors('genre', errors);
if($genre_errors) $('#recording-finished-dialog form select[name=genre]').closest('div.field').addClass('error').end().after($genre_errors);
var $is_public_errors = context.JK.format_errors('is_public', errors);
if($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors);
var $is_downloadable_errors = context.JK.format_errors('is_downloadable', errors);
if($is_downloadable_errors) $('#recording-finished-dialog form input[name=is_downloadable]').closest('div.field').addClass('error').end().after($is_downloadable_errors);
var recording_error = context.JK.get_first_error('recording_id', errors);
if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error);
}
else {
logger.error("unable to claim recording %o", arguments);
context.JK.showErrorDialog(app, "Unable to claim recording.", jqXHR.responseText);
}
})
.always(function() {
registerClaimRecordingHandlers(true);
});
return false;
}
function registerClaimRecordingHandlers(onOff) {
if(onOff) {
$('#keep-session-recording').on('click', claimRecording);
$('#recording-finished-dialog form').on('submit', claimRecording);
}
else {
$('#keep-session-recording').off('click', claimRecording)
$('#recording-finished-dialog form').off('submit', claimRecording);
}
}
function registerDiscardRecordingHandlers(onOff) {
if(onOff) {
$('#discard-session-recording').on('click', discardRecording);
}
else {
$('#discard-session-recording').off('click', discardRecording);
}
}
function registerStaticEvents() {
registerClaimRecordingHandlers(true);
registerDiscardRecordingHandlers(true);
}
function initialize(){
var dialogBindings = {
'beforeShow' : beforeShow,
'afterHide': afterHide
};
app.bindDialog('recordingFinished', dialogBindings);
registerStaticEvents();
};
this.initialize = initialize;
}
return this;
})(window,jQuery);

View File

@ -20,6 +20,7 @@
context.JK.RecordingModel = function(app, sessionModel, _rest, _jamClient) {
var currentRecording = null; // the JSON response from the server for a recording
var currentOrLastRecordingId = null;
var currentRecordingId = null;
var rest = _rest;
var currentlyRecording = false;
@ -57,6 +58,7 @@
currentRecordingId = null;
}
function groupTracksToClient(recording) {
// group N tracks to the same client Id
var groupedTracks = {};
@ -83,6 +85,7 @@
currentRecording = rest.startRecording({"music_session_id": sessionModel.id()})
.done(function(recording) {
currentRecordingId = recording.id;
currentOrLastRecordingId = recording.id;
// ask the backend to start the session.
var groupedTracks = groupTracksToClient(recording);
@ -220,6 +223,7 @@
})
.done(function(recording) {
currentRecordingId = recording.id;
currentOrLastRecordingId = recording.id;
});
$self.triggerHandler('startingRecording', {recordingId: recordingId});
@ -320,6 +324,7 @@
this.isRecording = isRecording;
this.reset = reset;
this.stopRecordingIfNeeded = stopRecordingIfNeeded;
this.currentOrLastRecordingId = function () { return currentOrLastRecordingId; };
context.JK.HandleRecordingStartResult = handleRecordingStartResult;
context.JK.HandleRecordingStopResult = handleRecordingStopResult;

View File

@ -11,6 +11,7 @@
var clientId = client.clientID;
var currentSessionId = null; // Set on join, prior to setting currentSession.
var currentSession = null;
var currentOrLastSession = null;
var subscribers = {};
var users = {}; // User info for session participants
var rest = context.JK.Rest();
@ -146,7 +147,11 @@
}
}
// you should only update currentSession with this function
function updateCurrentSession(sessionData) {
if(sessionData != null) {
currentOrLastSession = sessionData;
}
currentSession = sessionData;
}
@ -464,6 +469,9 @@
this.getCurrentSession = function() {
return currentSession;
};
this.getCurrentOrLastSession = function() {
return currentOrLastSession;
};
};
})(window,jQuery);

View File

@ -270,7 +270,7 @@
return null;
}
var ul = $('<ul class="error-text"></ul>')
var ul = $('<ul class="error-text"></ul>');
$.each(field_errors, function(index, item) {
ul.append(context.JK.fillTemplate("<li>{message}</li>", {message: item}))

View File

@ -29,6 +29,7 @@
*= require ./search
*= require ./ftue
*= require ./invitationDialog
*= require ./recordingFinishedDialog
*= require ./createSession
*= require ./genreSelector
*= require ./sessionList

View File

@ -0,0 +1,12 @@
#recording-finished-dialog {
width:1000px;
div[purpose=description], div[purpose=is_public], div[purpose=is_downloadable] {
margin-top:20px;
}
label[for=is_downloadable], label[for=is_public] {
display:inline;
}
}

View File

@ -1,7 +1,7 @@
class ApiRecordingsController < ApiController
before_filter :api_signed_in_user
before_filter :look_up_recording, :only => [ :show, :stop, :claim, :keep ]
before_filter :look_up_recording, :only => [ :show, :stop, :claim, :discard, :keep ]
before_filter :parse_filename, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
respond_to :json
@ -32,15 +32,15 @@ class ApiRecordingsController < ApiController
end
def download
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.can_download?(current_user)
redirect_to @recorded_track.sign_url
end
def start
music_session = MusicSession.find(params[:music_session_id])
unless music_session.users.exists?(current_user)
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
end
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless music_session.users.exists?(current_user)
@recording = Recording.start(music_session, current_user)
@ -54,10 +54,6 @@ class ApiRecordingsController < ApiController
def stop
unless @recording.users.exists?(current_user)
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
end
@recording.stop
if @recording.errors.any?
@ -68,9 +64,9 @@ class ApiRecordingsController < ApiController
end
end
# keep will kick off a mix, as well as create a claimed recording for the creator
# claim will create a claimed recording for the creator
def claim
claim = @recording.claim(current_user, params[:name], params[:description], Genre.find(params[:genre_id]), params[:is_public], params[:is_downloadable])
claim = @recording.claim(current_user, params[:name], params[:description], Genre.find_by_id(params[:genre]), params[:is_public], params[:is_downloadable])
claim.save
if claim.errors.any?
@ -81,6 +77,13 @@ class ApiRecordingsController < ApiController
end
end
# discard will tell the server the user has no interest in the recording
def discard
@recorded_tracks = @recording.recorded_tracks_for_user(current_user)
RecordedTrack.update(@recorded_tracks, :discard => true)
render :json => {}, :status => 200
end
def upload_next_part
length = params[:length]
md5 = params[:md5]
@ -141,14 +144,13 @@ class ApiRecordingsController < ApiController
private
def parse_filename
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id(params[:id], params[:track_id])
unless @recorded_track
render :json => { :message => ValidationMessages::RECORDING_NOT_FOUND }, :status => 404
end
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.recording.has_access?(current_user)
end
def look_up_recording
@recording = Recording.find(params[:id])
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recording.has_access?(current_user)
end
end

View File

@ -5,7 +5,6 @@ class ApiResponder < ActionController::Responder
logger.debug("REST API entity has error: #{resource.errors.inspect}")
controller.response.status = :unprocessable_entity
when post?
logger.debug("REST API post")
controller.response.status = :created
end

View File

@ -7,89 +7,83 @@
</div>
<div class="dialog-inner">
Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to keep the recording, click the "DISCARD" button.
<br />
<br />
Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
keep the recording, click the "DISCARD" button.
<br/>
<br/>
<div class="left w40 mr20">
<div class="left w40 mr20">
Recording name:<br />
<input type="text" class="w100" />
<form class="left w40 mr20">
<div class="left w50 mr20">
<div class="field w100">
<label for="name">Recording name:</label><br/>
<input type="text" name="name" id="claim-recording-name" class="w100"/>
</div>
</div>
<div class="left w40">
<!-- genre box -->
Genre:<br />
<div class="right w40 genre-selector">
<div class="field">
<!-- genre box -->
<label for="genre">Genre:</label><br/>
<select name="genre"></select>
</div>
</div>
<div class="field w100 left" purpose="description">
<label for="description">Description:</label>
<textarea class="w100" name="description" id="claim-recording-description"></textarea>
</div>
<div class="field left" purpose="is_public">
<input type="checkbox" checked="checked" name="is_public"/><label for="is_public">Public Recording</label> <!--<a href="#"><<img src="images/shared/icon_help.png" width="12" height="12" /></a>-->
</div>
<div class="field right" purpose="is_downloadable">
<input type="checkbox" checked="checked" name="is_downloadable"/><label for="is_downloadable"> Downloadable</label> <!--<a href="#"><img src="images/shared/icon_help.png" width="12" height="12" /></a>-->
</div>
</form>
<select>
<option>Choose a Genre</option>
<option>African</option>
<option>Acoustic</option>
<option>Adult Contemporary</option>
<option>Rock</option>
<option>Hard Rock</option>
<option>Metal</option>
<option>Jazz</option>
<option>Classical</option>
</select>
</div>
<br clear="left" />
<br />
Description:<br />
<textarea class="w100"></textarea>
</div>
<div class="left w50 ml30">
Preview Recording:<br/>
<!-- recording play controls -->
<div class="recording-controls">
<div class="left w50 ml30">
Preview Recording:<br />
<!-- recording play controls -->
<div class="recording-controls">
<!-- play button -->
<a class="left" href="#"><%= image_tag "content/icon_playbutton.png", {:height => 20, :width => 20} %></a>
<!-- play button -->
<a class="left" href="#"><%= image_tag "content/icon_playbutton.png", {:height => 20, :width => 20} %></a>
<!-- playback position -->
<div class="recording-position">
<!-- playback position -->
<div class="recording-position">
<!-- start time -->
<div class="recording-time">0:00</div>
<!-- start time -->
<div class="recording-time">0:00</div>
<!-- playback background & slider -->
<div class="recording-playback">
<div class="recording-slider"><%= image_tag "content/slider_playcontrols.png", {:height => 16, :width => 5} %></div>
</div>
<!-- playback background & slider -->
<div class="recording-playback">
<div class="recording-slider"><%= image_tag "content/slider_playcontrols.png", {:height => 16, :width => 5} %></div>
<!-- end time -->
<div class="recording-time">4:59</div>
</div>
<!-- end playback position -->
<!-- current playback time -->
<div class="recording-current">
1:23
</div>
<!-- end time -->
<div class="recording-time">4:59</div>
</div>
<!-- end playback position -->
<!-- current playback time -->
<div class="recording-current">
1:23
</div>
<!-- end recording play controls -->
<br/>
<br/>
</div>
<!-- end recording play controls -->
<br />
<br />
<input type="checkbox" checked="checked" /> Make this Recording Public <!--<a href="#"><<img src="images/shared/icon_help.png" width="12" height="12" /></a>--><br />
<input type="checkbox" checked="checked" /> Make this Recording Downloadable <!--<a href="#"><img src="images/shared/icon_help.png" width="12" height="12" /></a>--><br />
</div>
<br clear="left" /><br />
<br clear="left"/><br/>
<div class="right">
<a href="#" class="button-grey">DISCARD</a>&nbsp;<a href="#" class="button-orange">SAVE</a>
<a href="#" class="button-grey" id="discard-session-recording">DISCARD</a>&nbsp;<a href="#" class="button-orange" id="keep-session-recording">SAVE</a>
</div>
<br clear="all" />
<br clear="all"/>
</div>
<a href="#" class="button-grey" layout-action="close">DISCARD</a><br>
</div>

View File

@ -163,6 +163,8 @@
var sessionSettingsDialog = new JK.SessionSettingsDialog(JK.app, sessionScreen);
sessionSettingsDialog.initialize();
var recordingFinishedDialog = new JK.RecordingFinishedDialog(JK.app);
recordingFinishedDialog.initialize();
var whatsNextDialog = new JK.WhatsNextDialog(JK.app);
whatsNextDialog.initialize(invitationDialog);

View File

@ -274,6 +274,7 @@ SampleApp::Application.routes.draw do
match '/recordings/:id' => 'api_recordings#show', :via => :get, :as => 'api_recordings_detail'
match '/recordings/:id/stop' => 'api_recordings#stop', :via => :post, :as => 'api_recordings_stop'
match '/recordings/:id/claim' => 'api_recordings#claim', :via => :post, :as => 'api_recordings_claim'
match '/recordings/:id/discard' => 'api_recordings#discard', :via => :post, :as => 'api_recordings_discard'
match '/recordings/:id/tracks/:track_id/download' => 'api_recordings#download', :via => :get, :as => 'api_recordings_download'
match '/recordings/:id/tracks/:track_id/upload_next_part' => 'api_recordings#upload_next_part', :via => :get
match '/recordings/:id/tracks/:track_id/upload_sign' => 'api_recordings#upload_sign', :via => :get

View File

@ -86,4 +86,14 @@ describe ApiRecordingsController do
response.status.should == 403
end
end
describe "download" do
it "should only allow a user to download a track if they have claimed the recording" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, { :format => 'json', :id => recording.id }
response.should be_success
end
end
end

View File

@ -34,12 +34,12 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
in_client(creator) do
find('#recording-start-stop').trigger(:click)
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(joiner1) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
end
@ -60,12 +60,12 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
in_client(joiner1) do
find('#recording-start-stop').trigger(:click)
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(creator) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
end
@ -87,12 +87,12 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
find('#session-leave').trigger(:click)
expect(page).to have_selector('h2', text: 'feed')
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(joiner1) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
end
@ -111,7 +111,7 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
end
in_client(creator) do
visit "http://www.google.com" # kills websocket, looking like an abrupt leave
visit "/downloads" # kills websocket, looking like an abrupt leave
end
in_client(joiner1) do
@ -142,17 +142,17 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
in_client(creator) do
find('#recording-start-stop').trigger(:click)
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(joiner1) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(joiner2) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
end
@ -177,17 +177,17 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
find('#session-leave').trigger(:click)
expect(page).to have_selector('h2', text: 'feed')
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(joiner1) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
in_client(joiner2) do
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', 'Recording Finished')
should have_selector('h1', text: 'recording finished')
end
end
@ -211,7 +211,7 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
end
in_client(creator) do
visit "http://www.google.com" # kills websocket, looking like an abrupt leave
visit "/downloads" # kills websocket, looking like an abrupt leave
end
in_client(joiner1) do
@ -226,5 +226,94 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature
find('#recording-status').should have_content 'Make a Recording'
end
end
# creates a recording, and stops it, and confirms the 'Finished Recording' dialog shows for both
describe "Finished Recording Dialog" do
before(:each) do
RecordedTrack.delete_all
ClaimedRecording.delete_all
create_join_session(creator, [joiner1])
in_client(creator) do
find('#recording-start-stop').trigger(:click)
find('#recording-status').should have_content 'Stop Recording'
end
in_client(joiner1) do
find('#notification').should have_content 'started a recording'
find('#recording-status').should have_content 'Stop Recording'
end
in_client(creator) do
find('#recording-start-stop').trigger(:click)
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', text: 'recording finished')
end
end
it "discard the recording" do
in_client(creator) do
find('#recording-finished-dialog h1')
find('#discard-session-recording').trigger(:click)
should have_no_selector('h1', text: 'recording finished')
music_session = MusicSession.first()
recording = music_session.recordings.first()
tracks = recording.recorded_tracks_for_user(creator)
tracks.length.should == 1
tracks[0].discard.should be_true
end
in_client(joiner1) do
find('#recording-finished-dialog h1')
find('#discard-session-recording').trigger(:click)
should have_no_selector('h1', text: 'recording finished')
music_session = MusicSession.first()
recording = music_session.recordings.first()
tracks = recording.recorded_tracks_for_user(joiner1)
tracks.length.should == 1
tracks[0].discard.should be_true
end
end
it "claim recording" do
in_client(creator) do
find('#recording-finished-dialog h1')
fill_in "claim-recording-name", with: "my recording"
fill_in "claim-recording-description", with: "my description"
find('#keep-session-recording').trigger(:click)
should have_no_selector('h1', text: 'recording finished')
music_session = MusicSession.first()
recording = music_session.recordings.first()
claimed_recording = ClaimedRecording.find_by_user_id(creator.id)
claimed_recording.name.should == "my recording"
claimed_recording.description.should == "my description"
claimed_recording.is_public.should be_true
claimed_recording.is_downloadable.should be_true
claimed_recording.genre = music_session.genres[0]
end
in_client(joiner1) do
find('#recording-finished-dialog h1')
fill_in "claim-recording-name", with: "my recording"
fill_in "claim-recording-description", with: "my description"
find('#keep-session-recording').trigger(:click)
should have_no_selector('h1', text: 'recording finished')
music_session = MusicSession.first()
recording = music_session.recordings.first()
claimed_recording = ClaimedRecording.find_by_user_id(joiner1.id)
claimed_recording.name.should == "my recording"
claimed_recording.description.should == "my description"
claimed_recording.is_public.should be_true
claimed_recording.is_downloadable.should be_true
claimed_recording.genre = music_session.genres[0]
end
end
end
end

View File

@ -117,11 +117,12 @@ Spork.prefork do
config.append_after(:each) do
Capybara.reset_sessions!
reset_session_mapper
end
config.after(:each) do
Capybara.reset_sessions!
reset_session_mapper
if example.metadata[:js]
end

View File

@ -121,7 +121,7 @@ def create_session(creator = FactoryGirl.create(:user), unique_session_desc = "c
# create session in one client
in_client(creator) do
page.driver.resize(1500, 600) # makes sure all the elements are visible
page.driver.resize(1500, 800) # makes sure all the elements are visible
sign_in_poltergeist creator
wait_until_curtain_gone
visit "/client#/createSession"