Project

General

Profile

« Previous | Next » 

Revision 8798

REST API for project memberships (#7420).

View differences:

trunk/app/controllers/members_controller.rb
17 17

  
18 18
class MembersController < ApplicationController
19 19
  model_object Member
20
  before_filter :find_model_object, :except => [:create, :autocomplete]
21
  before_filter :find_project_from_association, :except => [:create, :autocomplete]
22
  before_filter :find_project_by_project_id, :only => [:create, :autocomplete]
20
  before_filter :find_model_object, :except => [:index, :create, :autocomplete]
21
  before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
22
  before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
23 23
  before_filter :authorize
24
  accept_api_auth :index, :show, :create, :update, :destroy
24 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
49

  
25 50
  def create
26 51
    members = []
27
    if params[:membership] && request.post?
52
    if params[:membership] && params[:membership][:user_ids]
28 53
      attrs = params[:membership].dup
29
      if (user_ids = attrs.delete(:user_ids))
30
        user_ids.each do |user_id|
31
          members << Member.new(attrs.merge(:user_id => user_id))
32
        end
33
      else
34
        members << Member.new(attrs)
54
      user_ids = attrs.delete(:user_ids)
55
      user_ids.each do |user_id|
56
        members << Member.new(attrs.merge(:user_id => user_id))
35 57
      end
36
      @project.members << members
58
    else
59
      members << Member.new(params[:membership])
37 60
    end
61
    @project.members << members
62

  
38 63
    respond_to do |format|
39 64
      if members.present? && members.all? {|m| m.valid? }
40

  
41 65
        format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
42

  
43 66
        format.js {
44 67
          render(:update) {|page|
45 68
            page.replace_html "tab-content-members", :partial => 'projects/settings/members'
......
47 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 77
      else
51

  
52 78
        format.js {
53 79
          render(:update) {|page|
54 80
            errors = members.collect {|m|
......
58 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 88
      end
63 89
    end
64 90
  end
......
67 93
    if params[:membership]
68 94
      @member.role_ids = params[:membership][:role_ids]
69 95
    end
70
    if request.put? && @member.save
71
  	 respond_to do |format|
72
        format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
73
        format.js {
74
          render(:update) {|page|
75
            page.replace_html "tab-content-members", :partial => 'projects/settings/members'
76
            page << 'hideOnLoad()'
77
            page.visual_effect(:highlight, "member-#{@member.id}")
78
          }
96
    saved = @member.save
97
    respond_to do |format|
98
      format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
99
      format.js {
100
        render(:update) {|page|
101
          page.replace_html "tab-content-members", :partial => 'projects/settings/members'
102
          page << 'hideOnLoad()'
103
          page.visual_effect(:highlight, "member-#{@member.id}")
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 113
    end
82 114
  end
83 115

  
......
92 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 134
    end
96 135
  end
97 136

  
trunk/app/views/members/index.api.rsb
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 19

  
trunk/app/views/members/show.api.rsb
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 15

  
trunk/config/routes.rb
170 170
    project.resources :repositories, :shallow => true, :except => [:index, :show],
171 171
                      :member => {:committers => [:get, :post]}
172 172
    project.resources :memberships, :shallow => true, :controller => 'members',
173
                      :only => [:create, :update, :destroy],
173
                      :only => [:index, :show, :create, :update, :destroy],
174 174
                      :collection => {:autocomplete => :get}
175 175

  
176 176
    project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
trunk/lib/redmine.rb
52 52
  map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
53 53
  map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
54 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 56
  map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
57 57
  map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
58 58

  
trunk/test/integration/api_test/memberships_test.rb
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
0 191

  
trunk/test/integration/routing/members_test.rb
20 20
class RoutingMembersTest < ActionController::IntegrationTest
21 21
  def test_members
22 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 31
        { :method => 'post', :path => "/projects/5234/memberships" },
24 32
        { :controller => 'members', :action => 'create', :project_id => '5234' }
25 33
      )
26 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 39
        { :method => 'put', :path => "/memberships/5234" },
28 40
        { :controller => 'members', :action => 'update', :id => '5234' }
29 41
      )
30 42
    assert_routing(
43
        { :method => 'put', :path => "/memberships/5234.xml" },
44
        { :controller => 'members', :action => 'update', :id => '5234', :format => 'xml' }
45
      )
46
    assert_routing(
31 47
        { :method => 'delete', :path => "/memberships/5234" },
32 48
        { :controller => 'members', :action => 'destroy', :id => '5234' }
33 49
      )
34 50
    assert_routing(
51
        { :method => 'delete', :path => "/memberships/5234.xml" },
52
        { :controller => 'members', :action => 'destroy', :id => '5234', :format => 'xml' }
53
      )
54
    assert_routing(
35 55
        { :method => 'get', :path => "/projects/5234/memberships/autocomplete" },
36 56
        { :controller => 'members', :action => 'autocomplete', :project_id => '5234' }
37 57
      )

Also available in: Unified diff