Merge pull request #696 from warpc/692-rest-api-for-groups

[refs #692]: REST API for Groups
This commit is contained in:
Vladimir Sharshov 2012-10-16 10:39:21 -07:00
commit 3413a681af
16 changed files with 417 additions and 29 deletions

View File

@ -2,6 +2,8 @@
class Api::V1::BaseController < ApplicationController class Api::V1::BaseController < ApplicationController
#respond_to :json #respond_to :json
helper_method :member_path
rescue_from CanCan::AccessDenied do |exception| rescue_from CanCan::AccessDenied do |exception|
respond_to do |format| respond_to do |format|
format.json { render :json => {:message => t("flash.exception_message")}.to_json, :status => 403 } format.json { render :json => {:message => t("flash.exception_message")}.to_json, :status => 403 }
@ -14,9 +16,18 @@ class Api::V1::BaseController < ApplicationController
[message, subject.errors.full_messages].flatten.join('. ') [message, subject.errors.full_messages].flatten.join('. ')
end end
def add_member_to_subject(subject) def create_subject(subject)
class_name = subject.class.name
if subject.save
render_json_response subject, "#{class_name} has been created successfully"
else
render_validation_error subject, "#{class_name} has not been created"
end
end
def add_member_to_subject(subject, role = 'admin')
class_name = subject.class.name.downcase class_name = subject.class.name.downcase
if member.present? && subject.add_member(member) if member.present? && subject.add_member(member, role)
render_json_response subject, "#{member.class.to_s} '#{member.id}' has been added to #{class_name} successfully" render_json_response subject, "#{member.class.to_s} '#{member.id}' has been added to #{class_name} successfully"
else else
render_validation_error subject, "Member has not been added to #{class_name}" render_validation_error subject, "Member has not been added to #{class_name}"
@ -68,6 +79,14 @@ class Api::V1::BaseController < ApplicationController
render_json_response(subject, error_message(subject, message), 422) render_json_response(subject, error_message(subject, message), 422)
end end
def member_path(subject)
if subject.is_a?(User)
api_v1_user_path(subject.id, :format => :json)
else
api_v1_group_path(subject.id, :format => :json)
end
end
private private
def member def member

View File

@ -0,0 +1,56 @@
# -*- encoding : utf-8 -*-
class Api::V1::GroupsController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:show] if APP_CONFIG['anonymous_access']
load_and_authorize_resource
def index
# accessible_by(current_ability)
@groups = current_user.groups.paginate(paginate_params)
end
def show
end
def members
@members = @group.members.
where('actor_id != ?', @group.owner_id).
order('name').paginate(paginate_params)
end
def update
update_subject @group
end
def destroy
destroy_subject @group
end
def create
@group = current_user.own_groups.new params[:group]
create_subject @group
end
def add_member
params[:type] = 'User'
add_member_to_subject @group, (params[:role] || 'admin')
end
def remove_member
params[:type] = 'User'
remove_member_from_subject @group
end
def update_member
member_id, role = params[:member_id], params[:role]
if member_id.present? && role.present? && @group.owner_id != member_id.to_i &&
@group.actors.where(:actor_id => member_id, :actor_type => 'User').
update_all(:role => role)
render_json_response @group, "Role for user #{member_id} has been updated in group successfully"
else
render_validation_error @group, 'Role for user has not been updated in group'
end
end
end

View File

@ -22,11 +22,7 @@ class Api::V1::PlatformsController < Api::V1::BaseController
platform_params = params[:platform] || {} platform_params = params[:platform] || {}
owner = User.where(:id => platform_params[:owner_id]).first owner = User.where(:id => platform_params[:owner_id]).first
@platform.owner = owner || get_owner @platform.owner = owner || get_owner
if @platform.save create_subject @platform
render_json_response @platform, 'Platform has been created successfully'
else
render_validation_error @platform, 'Platform has not been created'
end
end end
def update def update

View File

@ -29,28 +29,22 @@ class Groups::MembersController < Groups::BaseController
def remove def remove
all_user_ids = [] all_user_ids = []
params['user_remove'].each do |user_id, remove| params['user_remove'].each do |user_id, remove|
all_user_ids << user_id if remove == ["1"] && parent.owner.id.to_s != user_id all_user_ids << user_id if remove == ["1"]
end if params['user_remove'] end if params['user_remove']
all_user_ids.each do |user_id| User.where(:id => all_user_ids).each do |user|
u = User.find(user_id) parent.remove_member(user)
Relation.by_actor(u).by_target(parent).each {|r| r.destroy}
end end
redirect_to group_members_path(parent) redirect_to group_members_path(parent)
end end
def add def add
if params['user_id'] and !params['user_id'].empty? if params['user_id'].present?
@user = User.find_by_uname(params['user_id']) @user = User.find_by_uname(params['user_id'])
unless parent.actors.exists? :actor_id => @user.id, :actor_type => 'User' if parent.add_member(@user, params[:role])
relation = parent.actors.build(:actor_id => @user.id, :actor_type => 'User', :role => params[:role])
if relation.save
flash[:notice] = t("flash.members.successfully_added") flash[:notice] = t("flash.members.successfully_added")
else else
flash[:error] = t("flash.members.error_in_adding") flash[:error] = t("flash.members.error_in_adding")
end end
else
flash[:error] = t("flash.members.already_added")
end
end end
redirect_to group_members_path(parent) redirect_to group_members_path(parent)
end end

View File

@ -22,8 +22,7 @@ class Groups::ProfileController < Groups::BaseController
end end
def create def create
@group = Group.new params[:group] @group = current_user.own_groups.new params[:group]
@group.owner = current_user
if @group.save if @group.save
flash[:notice] = t('flash.group.saved') flash[:notice] = t('flash.group.saved')
redirect_to group_path(@group) redirect_to group_path(@group)

View File

@ -52,7 +52,7 @@ class Ability
if user.user? if user.user?
can [:read, :create], Group can [:read, :create], Group
can [:update, :manage_members], Group do |group| can [:update, :manage_members, :members, :add_member, :remove_member, :update_member], Group do |group|
group.actors.exists?(:actor_type => 'User', :actor_id => user.id, :role => 'admin') # or group.owner_id = user.id group.actors.exists?(:actor_type => 'User', :actor_id => user.id, :role => 'admin') # or group.owner_id = user.id
end end
can :destroy, Group, :owner_id => user.id can :destroy, Group, :owner_id => user.id

View File

@ -39,6 +39,20 @@ class Group < Avatar
uname uname
end end
def add_member(member, role = 'admin')
if actors.exists?(:actor_id => member.id, :actor_type => member.class.to_s) || owner == member
true
else
rel = actors.build(:role => role)
rel.actor = member
rel.save
end
end
def remove_member(member)
Relation.remove_member(member, self)
end
protected protected
def add_owner_to_members def add_owner_to_members

View File

@ -23,7 +23,7 @@ class Relation < ActiveRecord::Base
end end
def self.add_member(member, target, role) def self.add_member(member, target, role)
if target.relations.exists?(:actor_id => member.id, :actor_type => member.class.to_s) || @platform.try(:owner) == member if target.relations.exists?(:actor_id => member.id, :actor_type => member.class.to_s) || target.try(:owner) == member
true true
else else
rel = target.relations.build(:role => role) rel = target.relations.build(:role => role)
@ -33,6 +33,7 @@ class Relation < ActiveRecord::Base
end end
def self.remove_member(member, target) def self.remove_member(member, target)
return false if target.try(:owner) == member
Relation.by_actor(member).by_target(target).each{|r| r.destroy} Relation.by_actor(member).by_target(target).each{|r| r.destroy}
end end

View File

@ -0,0 +1,15 @@
json.groups @groups do |json, group|
json.(group, :id, :uname, :own_projects_count, :description)
json.created_at group.created_at.to_i
json.updated_at group.updated_at.to_i
json.owner do |json_owner|
json_owner.(group.owner, :id, :name)
json_owner.type 'User'
json_owner.url api_v1_user_path(group.owner_id, :format => :json)
end
json.avatar_url avatar_url(group, :big)
json.url api_v1_group_path(group.id, :format => :json)
json.html_url group_path(group.uname)
end
json.url api_v1_groups_path(:format => :json)

View File

@ -0,0 +1,9 @@
json.group do |json|
json.(@group, :id)
json.members @members do |json_members, member|
json_members.(member, :id)
json_members.type member.class.name
json_members.url member_path(member)
end
end
json.url members_api_v1_group_path(@group.id, :format => :json)

View File

@ -0,0 +1,13 @@
json.group do |json|
json.(@group, :id, :uname, :own_projects_count, :description)
json.created_at @group.created_at.to_i
json.updated_at @group.updated_at.to_i
json.owner do |json_owner|
json_owner.(@group.owner, :id, :name)
json_owner.type 'User'
json_owner.url api_v1_user_path(@group.owner_id, :format => :json)
end
json.avatar_url avatar_url(@group, :big)
json.url api_v1_group_path(@group.id, :format => :json)
json.html_url group_path(@group.uname)
end

View File

@ -3,13 +3,13 @@ json.platforms @platforms do |json, platform|
json.owner do |json_owner| json.owner do |json_owner|
json_owner.(platform.owner, :id, :name) json_owner.(platform.owner, :id, :name)
json_owner.type platform.owner_type json_owner.type platform.owner_type
json_owner.url url_for(platform.owner) json_owner.url member_path(platform.owner)
end end
json.repositories platform.repositories do |json_repos, repo| json.repositories platform.repositories do |json_repos, repo|
json_repos.(repo, :id, :name) json_repos.(repo, :id, :name)
json_repos.url api_v1_repository_path(repo.id, :format => :json) json_repos.url api_v1_repository_path(repo.id, :format => :json)
end end
json.url api_v1_platform_path(platform.name, :format => :json) json.url api_v1_platform_path(platform.id, :format => :json)
end end
json.url api_v1_platforms_path(:format => :json) json.url api_v1_platforms_path(:format => :json)

View File

@ -3,6 +3,7 @@ json.platform do |json|
json.members @members do |json_members, member| json.members @members do |json_members, member|
json_members.(member, :id) json_members.(member, :id)
json_members.type member.class.name json_members.type member.class.name
json_members.url member_path(member)
end end
end end
json.url members_api_v1_platform_path(@platform, :format => :json) json.url members_api_v1_platform_path(@platform.id, :format => :json)

View File

@ -5,11 +5,11 @@ json.platform do |json|
json.owner do |json_owner| json.owner do |json_owner|
json_owner.(@platform.owner, :id, :name) json_owner.(@platform.owner, :id, :name)
json_owner.type @platform.owner_type json_owner.type @platform.owner_type
json_owner.url url_for(@platform.owner) json_owner.url member_path(@platform.owner)
end end
json.repositories @platform.repositories do |json_repos, repo| json.repositories @platform.repositories do |json_repos, repo|
json_repos.(repo, :id, :name) json_repos.(repo, :id, :name)
json_repos.url api_v1_repository_path(repo.id, :format => :json) json_repos.url api_v1_repository_path(repo.id, :format => :json)
end end
end end
json.url api_v1_platform_path(@platform, :format => :json) json.url api_v1_platform_path(@platform.id, :format => :json)

View File

@ -55,6 +55,14 @@ Rosa::Application.routes.draw do
put :notifiers put :notifiers
} }
end end
resources :groups, :only => [:index, :show, :update, :create, :destroy] do
member {
get :members
put :add_member
delete :remove_member
put :update_member
}
end
end end
end end

View File

@ -0,0 +1,263 @@
# -*- encoding : utf-8 -*-
require 'spec_helper'
shared_examples_for 'api group user with reader rights' do
it 'should be able to perform members action' do
get :members, :id => @group.id, :format => :json
response.should be_success
end
it_should_behave_like 'api group user with show rights'
end
shared_examples_for 'api group user with show rights' do
it 'should be able to perform show action' do
get :show, :id => @group.id, :format => :json
response.should be_success
end
it 'should be able to perform index action' do
get :index, :format => :json
response.should be_success
end
end
shared_examples_for 'api group user without reader rights' do
it 'should not be able to perform members action' do
get :members, :id => @group.id, :format => :json
response.should_not be_success
end
end
shared_examples_for 'api group user with admin rights' do
context 'api group user with update rights' do
before do
put :update, {:group => {:description => 'new description'}, :id => @group.id}, :format => :json
end
it 'should be able to perform update action' do
response.should be_success
end
it 'ensures that group has been updated' do
@group.reload
@group.description.should == 'new description'
end
end
context 'api group user with add_member rights' do
let(:member) { FactoryGirl.create(:user) }
before do
put :add_member, {:member_id => member.id, :id => @group.id}, :format => :json
end
it 'should be able to perform add_member action' do
response.should be_success
end
it 'ensures that new member has been added to group' do
@group.members.should include(member)
end
end
context 'api group user with remove_member rights' do
let(:member) { FactoryGirl.create(:user) }
before do
@group.add_member(member)
delete :remove_member, {:member_id => member.id, :id => @group.id}, :format => :json
end
it 'should be able to perform remove_member action' do
response.should be_success
end
it 'ensures that member has been removed from group' do
@group.members.should_not include(member)
end
end
context 'api group user with update_member rights' do
let(:member) { FactoryGirl.create(:user) }
before do
@group.add_member(member)
put :update_member, {:member_id => member.id, :role => 'reader', :id => @group.id}, :format => :json
end
it 'should be able to perform update_member action' do
response.should be_success
end
it 'ensures that member role has been updated in group' do
@group.actors.where(:actor_id => member, :actor_type => 'User').first.
role.should == 'reader'
end
end
end
shared_examples_for 'api group user with owner rights' do
context 'api group user with destroy rights' do
it 'should be able to perform destroy action' do
delete :destroy, :id => @group.id, :format => :json
response.should be_success
end
it 'ensures that group has been destroyed' do
lambda { delete :destroy, :id => @group.id, :format => :json }.should change{ Group.count }.by(-1)
end
end
end
shared_examples_for 'api group user without admin rights' do
context 'api group user without update_member rights' do
let(:member) { FactoryGirl.create(:user) }
before do
@group.add_member(member)
put :update_member, {:member_id => member.id, :role => 'reader', :id => @group.id}, :format => :json
end
it 'should not be able to perform update_member action' do
response.should_not be_success
end
it 'ensures that member role has not been updated in group' do
@group.actors.where(:actor_id => member, :actor_type => 'User').first.
role.should_not == 'reader'
end
end
context 'api group user without update rights' do
before do
put :update, {:group => {:description => 'new description'}, :id => @group.id}, :format => :json
end
it 'should not be able to perform update action' do
response.should_not be_success
end
it 'ensures that platform has not been updated' do
@group.reload
@group.description.should_not == 'new description'
end
end
context 'api group user without add_member rights' do
let(:member) { FactoryGirl.create(:user) }
before do
put :add_member, {:member_id => member.id, :id => @group.id}, :format => :json
end
it 'should not be able to perform add_member action' do
response.should_not be_success
end
it 'ensures that new member has not been added to group' do
@group.members.should_not include(member)
end
end
context 'api group user without remove_member rights' do
let(:member) { FactoryGirl.create(:user) }
before do
@group.add_member(member)
delete :remove_member, {:member_id => member.id, :id => @group.id}, :format => :json
end
it 'should be able to perform update action' do
response.should_not be_success
end
it 'ensures that member has not been removed from group' do
@group.members.should include(member)
end
end
end
shared_examples_for 'api group user without owner rights' do
context 'api group user without destroy rights' do
it 'should not be able to perform destroy action' do
delete :destroy, :id => @group.id, :format => :json
response.should_not be_success
end
it 'ensures that group has not been destroyed' do
lambda { delete :destroy, :id => @group.id, :format => :json }.should_not change{ Group.count }
end
end
end
describe Api::V1::GroupsController do
before do
stub_symlink_methods
@group = FactoryGirl.create(:group)
@user = FactoryGirl.create(:user)
end
context 'for guest' do
it "should not be able to perform index action" do
get :index, :format => :json
response.status.should == 401
end
it "should not be able to perform show action", :anonymous_access => false do
get :show, :id => @group.id, :format => :json
response.status.should == 401
end
it "should be able to perform show action", :anonymous_access => true do
get :show, :id => @group.id, :format => :json
response.should be_success
end
context 'api group user without create rights' do
let(:params) { {:group => {:uname => 'test_uname'}} }
it 'should not be able to perform create action' do
post :create, params, :format => :json
response.should_not be_success
end
it 'ensures that group has not been created' do
lambda { post :create, params, :format => :json }.should_not change{ Group.count }
end
end
it_should_behave_like 'api group user without reader rights'
it_should_behave_like 'api group user without admin rights'
it_should_behave_like 'api group user without owner rights'
end
context 'for global admin' do
before do
@admin = FactoryGirl.create(:admin)
http_login(@admin)
end
it_should_behave_like 'api group user with reader rights'
it_should_behave_like 'api group user with admin rights'
it_should_behave_like 'api group user with owner rights'
end
context 'for owner user' do
before do
@group = FactoryGirl.create(:group, :owner => @user)
http_login(@user)
end
it_should_behave_like 'api group user with reader rights'
it_should_behave_like 'api group user with admin rights'
it_should_behave_like 'api group user with owner rights'
end
context 'for admin user' do
before do
@group.add_member(@user)
http_login(@user)
end
it_should_behave_like 'api group user with reader rights'
it_should_behave_like 'api group user with admin rights'
it_should_behave_like 'api group user without owner rights'
end
context 'for simple user' do
before do
http_login(@user)
end
it_should_behave_like 'api group user with show rights'
it_should_behave_like 'api group user without reader rights'
it_should_behave_like 'api group user without admin rights'
it_should_behave_like 'api group user without owner rights'
end
end