diff --git a/app/controllers/api_bands_controller.rb b/app/controllers/api_bands_controller.rb index 662095b66..14267ab5a 100644 --- a/app/controllers/api_bands_controller.rb +++ b/app/controllers/api_bands_controller.rb @@ -115,6 +115,7 @@ class ApiBandsController < ApplicationController params[:public], params[:description], params[:id], + params[:id], true) if @recording.errors.nil? || @recording.errors.size == 0 @@ -129,7 +130,24 @@ class ApiBandsController < ApplicationController end def recording_destroy + # TODO: ensure current_user is band member @recording = Recording.find(params[:recording_id]) @recording.delete + respond_with responder: ApiResponder, :status => 204 + end + + ###################### INVITATIONS ###################### + def invitation_index + # verify current_user is member of Band + @band = Band.find_by_id(params[:id]) + + unless @band.users.exists? current_user + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + + @invitations = BandInvitation.find_by_band_id(@band.id) + end + + def invitation_create end end \ No newline at end of file diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 433edf9ec..3a4ab32e4 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -26,6 +26,13 @@ class ApiController < ApplicationController else raise exception end - end + + protected + def auth_user(id) + if current_user.id != id + #respond_with "You do not have permissions to perform this action.", responder: ApiResponder, :status => 403 + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + end end \ No newline at end of file diff --git a/app/controllers/api_users_controller.rb b/app/controllers/api_users_controller.rb index ae4b8aa32..44e9bb99e 100644 --- a/app/controllers/api_users_controller.rb +++ b/app/controllers/api_users_controller.rb @@ -84,7 +84,7 @@ class ApiUsersController < ApiController def delete @user = User.find(params[:id]) @user.destroy # required to make 'tire' integration work - respond_with @user, responder: ApiResponder + respond_with responder: ApiResponder, :status => 204 end ###################### FOLLOWERS ######################## @@ -119,7 +119,7 @@ class ApiUsersController < ApiController def following_destroy auth_user(params[:id]) User.delete_user_following(params[:user_id], params[:id]) - respond_with responder: ApiResponder + respond_with responder: ApiResponder, :status => 204 end ###################### RECORDINGS ####################### @@ -205,8 +205,9 @@ class ApiUsersController < ApiController def recording_destroy auth_user(params[:id]) - recording = Recording.find(params[:recording_id]) - recording.delete + @recording = Recording.find(params[:recording_id]) + @recording.delete + respond_with responder: ApiResponder, :status => 204 #Recording.delete(params[:recording_id], params[:id], false) end @@ -229,7 +230,7 @@ class ApiUsersController < ApiController def favorite_destroy auth_user(params[:id]) User.delete_favorite(params[:id], params[:recording_id]) - respond_with responder: ApiResponder + respond_with responder: ApiResponder, :status => 204 end ###################### FRIENDS ########################## @@ -285,7 +286,7 @@ class ApiUsersController < ApiController auth_user(params[:id]) # clean up both records representing this "friendship" JamRuby::Friendship.delete_all "(user_id = '#{params[:id]}' AND friend_id = '#{params[:friend_id]}') OR (user_id = '#{params[:friend_id]}' AND friend_id = '#{params[:id]}')" - respond_with responder: ApiResponder + respond_with responder: ApiResponder, :status => 204 end ###################### AUTHENTICATION ################### @@ -304,7 +305,7 @@ class ApiUsersController < ApiController sign_out render :json => { :success => true }, :status => 200 end - +=begin protected def auth_user(id) if current_user.id != id @@ -312,4 +313,5 @@ class ApiUsersController < ApiController raise PermissionError, "You do not have permissions to perform this action." end end +=end end \ No newline at end of file diff --git a/app/views/api_bands/invitation_create.rabl b/app/views/api_bands/invitation_create.rabl new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/api_bands/invitation_index.rabl b/app/views/api_bands/invitation_index.rabl new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/api_users/band_invitation_index.rabl b/app/views/api_users/band_invitation_index.rabl new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/api_users/band_invitation_show.rabl b/app/views/api_users/band_invitation_show.rabl new file mode 100644 index 000000000..e69de29bb diff --git a/config/routes.rb b/config/routes.rb index 33c970019..ce5eb5a20 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,9 +60,9 @@ SampleApp::Application.routes.draw do # friend requests match '/users/:id/friend_requests' => 'api_users#friend_request_index', :via => :get - match '/friend_requests/:id' => 'api_users#friend_request_show', :via => :get, :as => 'api_friend_request_detail' - match '/friend_requests' => 'api_users#friend_request_create', :via => :post - match '/friend_requests/:id' => 'api_users#friend_request_update', :via => :put + match '/users/:id/friend_requests/:friend_request_id' => 'api_users#friend_request_show', :via => :get, :as => 'api_friend_request_detail' + match '/users/:id/friend_requests' => 'api_users#friend_request_create', :via => :post + match '/users/:id/friend_requests/:friend_request_id' => 'api_users#friend_request_update', :via => :post # friends match '/users/:id/friends' => 'api_users#friend_index', :via => :get @@ -84,6 +84,11 @@ SampleApp::Application.routes.draw do match '/users/:id/recordings/:recording_id' => 'api_users#recording_update', :via => :post match '/users/:id/recordings/:recording_id' => 'api_users#recording_destroy', :via => :delete + # user band invitations (NOT DONE) + match '/users/:id/band_invitations' => 'api_users#band_invitation_index', :via => :get + match '/users/:id/band_invitations/:invitation_id' => 'api_users#band_invitation_show', :via => :get, :as => 'api_band_invitation_detail' + match '/users/:id/band_invitations/:band_invitation_id' => 'api_users#band_invitation_update', :via => :post + # favorites match '/users/:id/favorites' => 'api_users#favorite_index', :via => :get, :as => 'api_favorite_index' match '/users/:id/favorites' => 'api_users#favorite_create', :via => :post @@ -95,6 +100,11 @@ SampleApp::Application.routes.draw do match '/bands' => 'api_bands#create', :via => :post match '/bands/:id' => 'api_bands#update', :via => :post + # band members (NOT DONE) + match '/bands/:id/musicians' => 'api_bands#musician_index', :via => :get + match '/bands/:id/musicians' => 'api_bands#musician_create', :via => :post + match '/bands/:id/musicians/:user_id' => 'api_bands#musician_destroy', :via => :delete + # band followers match '/bands/:id/followers' => 'api_bands#follower_index', :via => :get @@ -105,6 +115,10 @@ SampleApp::Application.routes.draw do match '/bands/:id/recordings/:recording_id' => 'api_bands#recording_update', :via => :post match '/bands/:id/recordings/:recording_id' => 'api_bands#recording_destroy', :via => :delete + # band invitations (NOT DONE) + match '/bands/:id/invitations' => 'api_bands#invitation_index', :via => :get + match '/bands/:id/invitations' => 'api_bands#invitation_create', :via => :post + # invitations match '/invitations/:id' => 'api_invitations#show', :via => :get, :as => 'api_invitation_detail' match '/invitations/:id' => 'api_invitations#delete', :via => :delete diff --git a/spec/requests/bands_api_spec.rb b/spec/requests/bands_api_spec.rb index 8cdb37e21..a3bf7e74c 100644 --- a/spec/requests/bands_api_spec.rb +++ b/spec/requests/bands_api_spec.rb @@ -11,10 +11,96 @@ describe "Band API", :type => :api do let(:user) { FactoryGirl.create(:user) } let(:fan) { FactoryGirl.create(:fan) } - it "should allow musician to create band" do + def login(email, password, http_code, success) + # login as fan + post '/api/auth_session.json', { :email => email, :password => password }.to_json, "CONTENT_TYPE" => 'application/json' + last_response.status.should == http_code + JSON.parse(last_response.body).should == { "success" => success } end - it "should not allow fan to create band" do + context "when logged in as musician" do + it "should allow band creation" do + # ensure creator is member and admin of band + end + end + + context "when logged in as musician who is in Band A" do + context "who is band admin" do + it "should allow user to send invitation" do + end + end + + context "who is not band admin" do + it "should not allow user to send invitation" do + end + end + + it "should allow user to update attributes of band A" do + end + + it "should allow user to create invitation to band A" do + end + + it "should allow user to create recording for band A" do + end + + it "should allow user to update recording of band A" do + end + + it "should allow user to delete recording of Band A" do + end + + it "should allow user to view public recording of Band A" do + end + + it "should allow user to view private recording of Band A" do + end + + it "should allow user to see invitation list of band A" do + end + end + + context "when logged in as musician who is not in band A" do + it "should not allow user to update attributes of band A" do + end + + it "should not allow user to create invitation for band A" do + end + + it "should not allow user to create recording for band A" do + end + + it "should not allow user to update recording of band A" do + end + + it "should not allow user to delete recording of Band A" do + end + + it "should allow user to view public recording of Band A" do + end + + it "should not allow user to view private recording of Band A" do + end + + it "should not allow user to see invitation list of band A" do + end + end + + context "when logged in as band invitation recipient" do + it "should allow acceptance of invitation" do + # ensure recipient is now member of band + end + + it "should allow declining of invitation" do + end + end + + context "when logged in as fan" do + it "should not allow band creation" do + end + + it "should not allow band invitation creation" do + end end end end \ No newline at end of file diff --git a/spec/requests/users_api_spec.rb b/spec/requests/users_api_spec.rb index d40a98e72..e2603accc 100644 --- a/spec/requests/users_api_spec.rb +++ b/spec/requests/users_api_spec.rb @@ -9,6 +9,7 @@ describe "User API", :type => :api do describe "profile" do let(:user) { FactoryGirl.create(:user) } let(:fan) { FactoryGirl.create(:fan) } + let(:band) { FactoryGirl.create(:band) } before(:each) do UserMailer.deliveries.clear @@ -21,7 +22,99 @@ describe "User API", :type => :api do JSON.parse(last_response.body).should == { "success" => success } end - context "unauthenticated user" do + ########################## FOLLOWINGS / FOLLOWERS ######################### + def create_user_following(authenticated_user, source_user, target_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + post "/api/users/#{source_user.id}/followings.json", { :user_id => target_user.id }.to_json, "CONTENT_TYPE" => 'application/json' + return last_response + end + + def get_user_followings(authenticated_user, source_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/users/#{source_user.id}/followings.json" + return last_response + end + + def get_user_followers(authenticated_user, source_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/users/#{source_user.id}/followers.json" + return last_response + end + + def delete_user_following(authenticated_user, source_user, target_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + delete "/api/users/#{source_user.id}/followings/#{target_user.id}.json" + return last_response + end + + def create_band_following(authenticated_user, source_user, target_band) + login(authenticated_user.email, authenticated_user.password, 200, true) + post "/api/users/#{source_user.id}/followings.json", { :band_id => target_band.id }.to_json, "CONTENT_TYPE" => 'application/json' + return last_response + end + + def get_band_followings(authenticated_user, source_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/users/#{source_user.id}/band_followings.json" + return last_response + end + + def get_band_followers(authenticated_user, source_band) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/bands/#{source_band.id}/followers.json" + return last_response + end + + ########################## RECORDINGS ######################### + def create_user_recording(authenticated_user, source_user, description, public) + login(authenticated_user.email, authenticated_user.password, 200, true) + post "/api/users/#{source_user.id}/recordings.json", { :description => description, :public => public }.to_json, "CONTENT_TYPE" => 'application/json' + return last_response + end + + def update_user_recording(authenticated_user, source_user, recording_id, description, public) + login(authenticated_user.email, authenticated_user.password, 200, true) + post "/api/users/#{source_user.id}/recordings/#{recording_id}.json", { :description => description, :public => public }.to_json, "CONTENT_TYPE" => 'application/json' + return last_response + end + + def get_user_recordings(authenticated_user, source_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/users/#{source_user.id}/recordings.json" + return last_response + end + + def get_user_recording(authenticated_user, source_user, recording_id) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/users/#{source_user.id}/recordings/#{recording_id}.json" + return last_response + end + + def delete_user_recording(authenticated_user, source_user, recording_id) + login(authenticated_user.email, authenticated_user.password, 200, true) + delete "/api/users/#{source_user.id}/recordings/#{recording_id}.json" + return last_response + end + + ########################## FAVORITES ######################### + def create_favorite(authenticated_user, source_user, recording_id) + login(authenticated_user.email, authenticated_user.password, 200, true) + post "/api/users/#{source_user.id}/favorites.json", { :recording_id => recording_id }.to_json, "CONTENT_TYPE" => 'application/json' + return last_response + end + + def get_favorites(authenticated_user, source_user) + login(authenticated_user.email, authenticated_user.password, 200, true) + get "/api/users/#{source_user.id}/favorites.json" + return last_response + end + + def delete_favorite(authenticated_user, source_user, recording_id) + login(authenticated_user.email, authenticated_user.password, 200, true) + delete "/api/users/#{source_user.id}/favorites/#{recording_id}.json" + end + + context "when accessing as unauthenticated user" do it "should allow successful login" do # can't access most apis; not logged in yet!' @@ -54,16 +147,8 @@ describe "User API", :type => :api do end end - context "authenticated user" do + context "when accessing as authenticated user" do # log in a valid user -=begin - before do - puts "logging in" - post '/sessions', "session[email]" => fan.email, "session[password]" => fan.password - rack_mock_session.cookie_jar["remember_token"].should == fan.remember_token - end -=end - it "should allow user updates" do # login as fan @@ -86,21 +171,18 @@ describe "User API", :type => :api do it "should allow user to follow user" do # create user following - login(user.email, user.password, 200, true) - post "/api/users/#{user.id}/followings.json", { :user_id => fan.id }.to_json, "CONTENT_TYPE" => 'application/json' + last_response = create_user_following(user, user, fan) last_response.status.should == 201 - # get following - login(user.email, user.password, 200, true) - get "/api/users/#{user.id}/followings.json" + # get followings + last_response = get_user_followings(user, user) last_response.status.should == 200 followings = JSON.parse(last_response.body) followings.size.should == 1 followings[0]["user_id"].should == fan.id # get followers for other side of above following (fan) - login(fan.email, fan.password, 200, true) - get "/api/users/#{fan.id}/followers.json" + last_response = get_user_followers(fan, fan) last_response.status.should == 200 followers = JSON.parse(last_response.body) followers.size.should == 1 @@ -109,93 +191,188 @@ describe "User API", :type => :api do it "should allow user to follow band" do # create band following - login(user.email, user.password, 200, true) - band = FactoryGirl.create(:band) - post "/api/users/#{user.id}/followings.json", { :band_id => band.id }.to_json, "CONTENT_TYPE" => 'application/json' + last_response = create_band_following(user, user, band) + last_response.status.should == 201 - # get following - login(user.email, user.password, 200, true) - get "/api/users/#{user.id}/band_followings.json" + # get band followings + last_response = get_band_followings(user, user) last_response.status.should == 200 followings = JSON.parse(last_response.body) followings.size.should == 1 followings[0]["band_id"].should == band.id # get followers for band - login(user.email, user.password, 200, true) - get "/api/bands/#{band.id}/followers.json" + last_response = get_band_followers(user, band) last_response.status.should == 200 followers = JSON.parse(last_response.body) followers.size.should == 1 followers[0]["user_id"].should == user.id - - # delete followings end it "should not allow user to create following for another user" do - login(user.email, user.password, 200, true) - post "/api/users/10/followings.json", { :user_id => fan.id }.to_json, "CONTENT_TYPE" => 'application/json' + dummy_user = FactoryGirl.create(:user) + last_response = create_user_following(user, dummy_user, fan) last_response.status.should == 403 end + it "should allow user to delete following" do + last_response = create_user_following(user, user, fan) + last_response.status.should == 201 + + # get followings + last_response = get_user_followings(user, user) + last_response.status.should == 200 + followings = JSON.parse(last_response.body) + followings.size.should == 1 + followings[0]["user_id"].should == fan.id + + # delete following + last_response = delete_user_following(user, user, fan) + last_response.status.should == 204 + + # get followings + last_response = get_user_followings(user, user) + last_response.status.should == 200 + followings = JSON.parse(last_response.body) + followings.size.should == 0 + end + it "should not allow user to delete following of another user" do + # create user following + last_response = create_user_following(user, user, fan) + last_response.status.should == 201 + + # get followings + last_response = get_user_followings(user, user) + last_response.status.should == 200 + followings = JSON.parse(last_response.body) + followings.size.should == 1 + followings[0]["user_id"].should == fan.id + + # attempt to delete following of another user + last_response = delete_user_following(fan, user, fan) + last_response.status.should == 403 + + # get followings + last_response = get_user_followings(user, user) + last_response.status.should == 200 + followings = JSON.parse(last_response.body) + followings.size.should == 1 end it "should allow musician to create recordings" do - # create public recording - login(user.email, user.password, 200, true) - post "/api/users/#{user.id}/recordings.json", { :description => "My Recording", :public => true }.to_json, "CONTENT_TYPE" => 'application/json' + public_description = "My Public Recording" + last_response = create_user_recording(user, user, public_description, true) last_response.status.should == 201 recording = JSON.parse(last_response.body) - recording["description"].should == "My Recording" + recording["description"].should == public_description + recording["public"].should == true # create private recording - login(user.email, user.password, 200, true) - post "/api/users/#{user.id}/recordings.json", { :description => "My Recording 2", :public => false }.to_json, "CONTENT_TYPE" => 'application/json' + private_description = "My Private Recording" + last_response = create_user_recording(user, user, private_description, false) last_response.status.should == 201 private_recording = JSON.parse(last_response.body) + private_recording["description"].should == private_description + private_recording["public"].should == false - # get all recordings as creator - login(user.email, user.password, 200, true) - get "/api/users/#{user.id}/recordings.json" + # update the second recording's description and public flag + last_response = update_user_recording(user, user, private_recording["id"], "My Recording 3", true) last_response.status.should == 200 - recordings = JSON.parse(last_response.body) - recordings.size.should == 2 + recording = JSON.parse(last_response.body) + recording["description"].should == "My Recording 3" + recording["public"].should == true - # get all recordings as non-creator - login(fan.email, fan.password, 200, true) - get "/api/users/#{user.id}/recordings.json" + # retrieve the recording details again to ensure the change took effect + last_response = get_user_recording(user, user, recording["id"]) last_response.status.should == 200 - recordings = JSON.parse(last_response.body) - recordings.size.should == 1 - - # attempt to get the private recording - login(fan.email, fan.password, 200, true) - get "/api/users/#{user.id}/recordings/#{private_recording["id"]}.json" - last_response.status.should == 403 + recording = JSON.parse(last_response.body) + recording["description"].should == "My Recording 3" + recording["public"].should == true end it "should not allow fan to create recordings" do - login(fan.email, fan.password, 200, true) - post "/api/users/#{fan.id}/recordings.json", { :description => "My Recording", :public => true }.to_json, "CONTENT_TYPE" => 'application/json' + last_response = create_user_recording(fan, fan, "Fan Recording", true) + last_response.status.should == 403 + end + + it "should allow creator to see public and private recordings in list" do + # create public recording + public_description = "My Public Recording" + last_response = create_user_recording(user, user, public_description, true) + last_response.status.should == 201 + + # create private recording + private_description = "My Private Recording" + last_response = create_user_recording(user, user, private_description, false) + last_response.status.should == 201 + + # get all recordings as creator + last_response = get_user_recordings(user, user) + recordings = JSON.parse(last_response.body) + recordings.size.should == 2 + end + + it "should allow creator to see private recording details" do + # create private recording + private_description = "My Private Recording" + last_response = create_user_recording(user, user, private_description, false) + last_response.status.should == 201 + private_recording = JSON.parse(last_response.body) + private_recording["description"].should == private_description + private_recording["public"].should == false + + # attempt to get the private recording as non-creator + last_response = get_user_recording(user, user, private_recording["id"]) + last_response.status.should == 200 + end + + it "should not allow non-creator to see private recordings in list" do + # create public recording + public_description = "My Public Recording" + last_response = create_user_recording(user, user, public_description, true) + last_response.status.should == 201 + + # create private recording + private_description = "My Private Recording" + last_response = create_user_recording(user, user, private_description, false) + last_response.status.should == 201 + + # get all recordings as non-creator + last_response = get_user_recordings(fan, user) + recordings = JSON.parse(last_response.body) + recordings.size.should == 1 + recordings[0]["description"].should == public_description + recordings[0]["public"].should == true + end + + it "should not allow non-creator to see private recording details" do + # create private recording + private_description = "My Private Recording" + last_response = create_user_recording(user, user, private_description, false) + last_response.status.should == 201 + private_recording = JSON.parse(last_response.body) + private_recording["description"].should == private_description + private_recording["public"].should == false + + # attempt to get the private recording as non-creator + last_response = get_user_recording(fan, user, private_recording["id"]) last_response.status.should == 403 end it "should allow user to create favorites" do # create recording first - login(user.email, user.password, 200, true) - post "/api/users/#{user.id}/recordings.json", { :description => "My Recording", :public => true }.to_json, "CONTENT_TYPE" => 'application/json' + last_response = create_user_recording(user, user, "My Recording", true) last_response.status.should == 201 recording = JSON.parse(last_response.body) # add favorite - login(user.email, user.password, 200, true) - post "/api/users/#{user.id}/favorites.json", { :recording_id => recording["id"] }.to_json, "CONTENT_TYPE" => 'application/json' + last_response = create_favorite(fan, fan, recording["id"]) last_response.status.should == 201 - login(user.email, user.password, 200, true) - get "/api/users/#{user.id}/favorites.json" + # get favorites + last_response = get_favorites(fan, fan) last_response.status.should == 200 favorites = JSON.parse(last_response.body) favorites.size.should == 1 @@ -205,9 +382,42 @@ describe "User API", :type => :api do end it "should not allow user to create favorite for another user" do + # create recording first + last_response = create_user_recording(user, user, "My Recording", true) + last_response.status.should == 201 + recording = JSON.parse(last_response.body) + + # attempt to add favorite for another user + last_response = create_favorite(fan, user, recording["id"]) + last_response.status.should == 403 end it "should allow user to delete favorites" do + # create recording first + last_response = create_user_recording(user, user, "My Recording", true) + last_response.status.should == 201 + recording = JSON.parse(last_response.body) + + # delete favorite + last_response = delete_favorite(user, user, recording["id"]) + last_response.status.should == 204 + + # get favorites + last_response = get_favorites(user, user) + last_response.status.should == 200 + favorites = JSON.parse(last_response.body) + favorites.size.should == 0 + end + + it "should not allow user to delete another user's favorites" do + # create recording first + last_response = create_user_recording(user, user, "My Recording", true) + last_response.status.should == 201 + recording = JSON.parse(last_response.body) + + # attempt to delete favorite as non-creator + last_response = delete_favorite(fan, user, recording["id"]) + last_response.status.should == 403 end it "should allow user to send friend request" do