##// END OF EJS Templates
REST API for project memberships (#7420)....
Jean-Philippe Lang -
r8678:c5665276b7a4
parent child
Show More
@@ -0,0 +1,18
1 api.array :memberships, api_meta(:total_count => @member_count, :offset => @offset, :limit => @limit) do
2 @members.each do |membership|
3 api.membership do
4 api.id membership.id
5 api.project :id => membership.project.id, :name => membership.project.name
6 api.__send__ membership.principal.class.name.underscore, :id => membership.principal.id, :name => membership.principal.name
7 api.array :roles do
8 membership.member_roles.each do |member_role|
9 if member_role.role
10 attrs = {:id => member_role.role.id, :name => member_role.role.name}
11 attrs.merge!(:inherited => true) if member_role.inherited_from.present?
12 api.role attrs
13 end
14 end
15 end
16 end
17 end
18 end
@@ -0,0 +1,14
1 api.membership do
2 api.id @member.id
3 api.project :id => @member.project.id, :name => @member.project.name
4 api.__send__ @member.principal.class.name.underscore, :id => @member.principal.id, :name => @member.principal.name
5 api.array :roles do
6 @member.member_roles.each do |member_role|
7 if member_role.role
8 attrs = {:id => member_role.role.id, :name => member_role.role.name}
9 attrs.merge!(:inherited => true) if member_role.inherited_from.present?
10 api.role attrs
11 end
12 end
13 end
14 end
@@ -0,0 +1,190
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../test_helper', __FILE__)
19
20 class ApiTest::MembershipsTest < ActionController::IntegrationTest
21 fixtures :projects, :users, :roles, :members, :member_roles
22
23 def setup
24 Setting.rest_api_enabled = '1'
25 end
26
27 context "/projects/:project_id/memberships" do
28 context "GET" do
29 context "xml" do
30 should "return memberships" do
31 get '/projects/1/memberships.xml', {}, credentials('jsmith')
32
33 assert_response :success
34 assert_equal 'application/xml', @response.content_type
35 assert_tag :tag => 'memberships',
36 :attributes => {:type => 'array'},
37 :child => {
38 :tag => 'membership',
39 :child => {
40 :tag => 'id',
41 :content => '2',
42 :sibling => {
43 :tag => 'user',
44 :attributes => {:id => '3', :name => 'Dave Lopper'},
45 :sibling => {
46 :tag => 'roles',
47 :child => {
48 :tag => 'role',
49 :attributes => {:id => '2', :name => 'Developer'}
50 }
51 }
52 }
53 }
54 }
55 end
56 end
57
58 context "json" do
59 should "return memberships" do
60 get '/projects/1/memberships.json', {}, credentials('jsmith')
61
62 assert_response :success
63 assert_equal 'application/json', @response.content_type
64 json = ActiveSupport::JSON.decode(response.body)
65 assert_equal({
66 "memberships" =>
67 [{"id"=>1,
68 "project" => {"name"=>"eCookbook", "id"=>1},
69 "roles" => [{"name"=>"Manager", "id"=>1}],
70 "user" => {"name"=>"John Smith", "id"=>2}},
71 {"id"=>2,
72 "project" => {"name"=>"eCookbook", "id"=>1},
73 "roles" => [{"name"=>"Developer", "id"=>2}],
74 "user" => {"name"=>"Dave Lopper", "id"=>3}}],
75 "limit" => 25,
76 "total_count" => 2,
77 "offset" => 0},
78 json)
79 end
80 end
81 end
82
83 context "POST" do
84 context "xml" do
85 should "create membership" do
86 assert_difference 'Member.count' do
87 post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith')
88
89 assert_response :created
90 end
91 end
92
93 should "return errors on failure" do
94 assert_no_difference 'Member.count' do
95 post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith')
96
97 assert_response :unprocessable_entity
98 assert_equal 'application/xml', @response.content_type
99 assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"}
100 end
101 end
102 end
103 end
104 end
105
106 context "/memberships/:id" do
107 context "GET" do
108 context "xml" do
109 should "return the membership" do
110 get '/memberships/2.xml', {}, credentials('jsmith')
111
112 assert_response :success
113 assert_equal 'application/xml', @response.content_type
114 assert_tag :tag => 'membership',
115 :child => {
116 :tag => 'id',
117 :content => '2',
118 :sibling => {
119 :tag => 'user',
120 :attributes => {:id => '3', :name => 'Dave Lopper'},
121 :sibling => {
122 :tag => 'roles',
123 :child => {
124 :tag => 'role',
125 :attributes => {:id => '2', :name => 'Developer'}
126 }
127 }
128 }
129 }
130 end
131 end
132
133 context "json" do
134 should "return the membership" do
135 get '/memberships/2.json', {}, credentials('jsmith')
136
137 assert_response :success
138 assert_equal 'application/json', @response.content_type
139 json = ActiveSupport::JSON.decode(response.body)
140 assert_equal(
141 {"membership" => {
142 "id" => 2,
143 "project" => {"name"=>"eCookbook", "id"=>1},
144 "roles" => [{"name"=>"Developer", "id"=>2}],
145 "user" => {"name"=>"Dave Lopper", "id"=>3}}
146 },
147 json)
148 end
149 end
150 end
151
152 context "PUT" do
153 context "xml" do
154 should "update membership" do
155 assert_not_equal [1,2], Member.find(2).role_ids.sort
156 assert_no_difference 'Member.count' do
157 put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,266]}}, credentials('jsmith')
158
159 assert_response :ok
160 end
161 member = Member.find(2)
162 assert_equal [1,2], member.role_ids.sort
163 end
164 end
165 end
166
167 context "DELETE" do
168 context "xml" do
169 should "destroy membership" do
170 assert_difference 'Member.count', -1 do
171 delete '/memberships/2.xml', {}, credentials('jsmith')
172
173 assert_response :ok
174 end
175 assert_nil Member.find_by_id(2)
176 end
177
178 should "respond with 422 on failure" do
179 assert_no_difference 'Member.count' do
180 # A membership with an inherited role can't be deleted
181 Member.find(2).member_roles.first.update_attribute :inherited_from, 99
182 delete '/memberships/2.xml', {}, credentials('jsmith')
183
184 assert_response :unprocessable_entity
185 end
186 end
187 end
188 end
189 end
190 end
@@ -17,29 +17,52
17
17
18 class MembersController < ApplicationController
18 class MembersController < ApplicationController
19 model_object Member
19 model_object Member
20 before_filter :find_model_object, :except => [:create, :autocomplete]
20 before_filter :find_model_object, :except => [:index, :create, :autocomplete]
21 before_filter :find_project_from_association, :except => [:create, :autocomplete]
21 before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
22 before_filter :find_project_by_project_id, :only => [:create, :autocomplete]
22 before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
23 before_filter :authorize
23 before_filter :authorize
24 accept_api_auth :index, :show, :create, :update, :destroy
25
26 def index
27 @offset, @limit = api_offset_and_limit
28 @member_count = @project.member_principals.count
29 @member_pages = Paginator.new self, @member_count, @limit, params['page']
30 @offset ||= @member_pages.current.offset
31 @members = @project.member_principals.all(
32 :order => "#{Member.table_name}.id",
33 :limit => @limit,
34 :offset => @offset
35 )
36
37 respond_to do |format|
38 format.html { head 406 }
39 format.api
40 end
41 end
42
43 def show
44 respond_to do |format|
45 format.html { head 406 }
46 format.api
47 end
48 end
24
49
25 def create
50 def create
26 members = []
51 members = []
27 if params[:membership] && request.post?
52 if params[:membership] && params[:membership][:user_ids]
28 attrs = params[:membership].dup
53 attrs = params[:membership].dup
29 if (user_ids = attrs.delete(:user_ids))
54 user_ids = attrs.delete(:user_ids)
30 user_ids.each do |user_id|
55 user_ids.each do |user_id|
31 members << Member.new(attrs.merge(:user_id => user_id))
56 members << Member.new(attrs.merge(:user_id => user_id))
32 end
33 else
34 members << Member.new(attrs)
35 end
57 end
36 @project.members << members
58 else
59 members << Member.new(params[:membership])
37 end
60 end
61 @project.members << members
62
38 respond_to do |format|
63 respond_to do |format|
39 if members.present? && members.all? {|m| m.valid? }
64 if members.present? && members.all? {|m| m.valid? }
40
41 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
65 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
42
43 format.js {
66 format.js {
44 render(:update) {|page|
67 render(:update) {|page|
45 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
68 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
@@ -47,8 +70,11 class MembersController < ApplicationController
47 members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
70 members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
48 }
71 }
49 }
72 }
73 format.api {
74 @member = members.first
75 render :action => 'show', :status => :created, :location => membership_url(@member)
76 }
50 else
77 else
51
52 format.js {
78 format.js {
53 render(:update) {|page|
79 render(:update) {|page|
54 errors = members.collect {|m|
80 errors = members.collect {|m|
@@ -58,7 +84,7 class MembersController < ApplicationController
58 page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
84 page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
59 }
85 }
60 }
86 }
61
87 format.api { render_validation_errors(members.first) }
62 end
88 end
63 end
89 end
64 end
90 end
@@ -67,17 +93,23 class MembersController < ApplicationController
67 if params[:membership]
93 if params[:membership]
68 @member.role_ids = params[:membership][:role_ids]
94 @member.role_ids = params[:membership][:role_ids]
69 end
95 end
70 if request.put? && @member.save
96 saved = @member.save
71 respond_to do |format|
97 respond_to do |format|
72 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
98 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
73 format.js {
99 format.js {
74 render(:update) {|page|
100 render(:update) {|page|
75 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
101 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
76 page << 'hideOnLoad()'
102 page << 'hideOnLoad()'
77 page.visual_effect(:highlight, "member-#{@member.id}")
103 page.visual_effect(:highlight, "member-#{@member.id}")
78 }
79 }
104 }
80 end
105 }
106 format.api {
107 if saved
108 head :ok
109 else
110 render_validation_errors(@member)
111 end
112 }
81 end
113 end
82 end
114 end
83
115
@@ -92,6 +124,13 class MembersController < ApplicationController
92 page << 'hideOnLoad()'
124 page << 'hideOnLoad()'
93 }
125 }
94 }
126 }
127 format.api {
128 if @member.destroyed?
129 head :ok
130 else
131 head :unprocessable_entity
132 end
133 }
95 end
134 end
96 end
135 end
97
136
@@ -170,7 +170,7 ActionController::Routing::Routes.draw do |map|
170 project.resources :repositories, :shallow => true, :except => [:index, :show],
170 project.resources :repositories, :shallow => true, :except => [:index, :show],
171 :member => {:committers => [:get, :post]}
171 :member => {:committers => [:get, :post]}
172 project.resources :memberships, :shallow => true, :controller => 'members',
172 project.resources :memberships, :shallow => true, :controller => 'members',
173 :only => [:create, :update, :destroy],
173 :only => [:index, :show, :create, :update, :destroy],
174 :collection => {:autocomplete => :get}
174 :collection => {:autocomplete => :get}
175
175
176 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
176 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
@@ -52,7 +52,7 Redmine::AccessControl.map do |map|
52 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
52 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
53 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
53 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
54 map.permission :select_project_modules, {:projects => :modules}, :require => :member
54 map.permission :select_project_modules, {:projects => :modules}, :require => :member
55 map.permission :manage_members, {:projects => :settings, :members => [:create, :update, :destroy, :autocomplete]}, :require => :member
55 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member
56 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
56 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
57 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
57 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
58
58
@@ -20,18 +20,38 require File.expand_path('../../../test_helper', __FILE__)
20 class RoutingMembersTest < ActionController::IntegrationTest
20 class RoutingMembersTest < ActionController::IntegrationTest
21 def test_members
21 def test_members
22 assert_routing(
22 assert_routing(
23 { :method => 'get', :path => "/projects/5234/memberships.xml" },
24 { :controller => 'members', :action => 'index', :project_id => '5234', :format => 'xml' }
25 )
26 assert_routing(
27 { :method => 'get', :path => "/memberships/5234.xml" },
28 { :controller => 'members', :action => 'show', :id => '5234', :format => 'xml' }
29 )
30 assert_routing(
23 { :method => 'post', :path => "/projects/5234/memberships" },
31 { :method => 'post', :path => "/projects/5234/memberships" },
24 { :controller => 'members', :action => 'create', :project_id => '5234' }
32 { :controller => 'members', :action => 'create', :project_id => '5234' }
25 )
33 )
26 assert_routing(
34 assert_routing(
35 { :method => 'post', :path => "/projects/5234/memberships.xml" },
36 { :controller => 'members', :action => 'create', :project_id => '5234', :format => 'xml' }
37 )
38 assert_routing(
27 { :method => 'put', :path => "/memberships/5234" },
39 { :method => 'put', :path => "/memberships/5234" },
28 { :controller => 'members', :action => 'update', :id => '5234' }
40 { :controller => 'members', :action => 'update', :id => '5234' }
29 )
41 )
30 assert_routing(
42 assert_routing(
43 { :method => 'put', :path => "/memberships/5234.xml" },
44 { :controller => 'members', :action => 'update', :id => '5234', :format => 'xml' }
45 )
46 assert_routing(
31 { :method => 'delete', :path => "/memberships/5234" },
47 { :method => 'delete', :path => "/memberships/5234" },
32 { :controller => 'members', :action => 'destroy', :id => '5234' }
48 { :controller => 'members', :action => 'destroy', :id => '5234' }
33 )
49 )
34 assert_routing(
50 assert_routing(
51 { :method => 'delete', :path => "/memberships/5234.xml" },
52 { :controller => 'members', :action => 'destroy', :id => '5234', :format => 'xml' }
53 )
54 assert_routing(
35 { :method => 'get', :path => "/projects/5234/memberships/autocomplete" },
55 { :method => 'get', :path => "/projects/5234/memberships/autocomplete" },
36 { :controller => 'members', :action => 'autocomplete', :project_id => '5234' }
56 { :controller => 'members', :action => 'autocomplete', :project_id => '5234' }
37 )
57 )
General Comments 0
You need to be logged in to leave comments. Login now