Feature #44141 » 0001-Add-REST-API-for-boards.patch
| app/controllers/boards_controller.rb | ||
|---|---|---|
| 19 | 19 | |
| 20 | 20 |
class BoardsController < ApplicationController |
| 21 | 21 |
default_search_scope :messages |
| 22 |
before_action :find_project_by_project_id, :find_board_if_available, :authorize |
|
| 22 |
before_action :find_project_by_project_id, :only => [:index, :new, :create] |
|
| 23 |
before_action :find_board_if_available, :only => [:index, :show, :edit, :update, :destroy] |
|
| 24 |
before_action :authorize |
|
| 23 | 25 |
accept_atom_auth :index, :show |
| 26 |
accept_api_auth :index, :show, :create, :update, :destroy |
|
| 24 | 27 | |
| 25 | 28 |
helper :sort |
| 26 | 29 |
include SortHelper |
| 27 | 30 |
helper :watchers |
| 28 | 31 | |
| 29 | 32 |
def index |
| 30 |
@boards = @project.boards.preload(:last_message => :author).to_a
|
|
| 33 |
@boards = @project.boards.preload(:parent, {:last_message => :author}).to_a
|
|
| 31 | 34 |
# show the board if there is only one |
| 32 |
if @boards.size == 1 |
|
| 35 |
if !api_request? && @boards.size == 1
|
|
| 33 | 36 |
@board = @boards.first |
| 34 | 37 |
show |
| 38 |
else |
|
| 39 |
respond_to do |format| |
|
| 40 |
format.html |
|
| 41 |
format.api |
|
| 42 |
end |
|
| 35 | 43 |
end |
| 36 | 44 |
end |
| 37 | 45 | |
| ... | ... | |
| 63 | 71 |
to_a |
| 64 | 72 |
render_feed(messages, :title => "#{@project}: #{@board}")
|
| 65 | 73 |
end |
| 74 |
format.api |
|
| 66 | 75 |
end |
| 67 | 76 |
end |
| 68 | 77 | |
| ... | ... | |
| 75 | 84 |
@board = @project.boards.build |
| 76 | 85 |
@board.safe_attributes = params[:board] |
| 77 | 86 |
if @board.save |
| 78 |
flash[:notice] = l(:notice_successful_create) |
|
| 79 |
redirect_to_settings_in_projects |
|
| 87 |
respond_to do |format| |
|
| 88 |
format.html do |
|
| 89 |
flash[:notice] = l(:notice_successful_create) |
|
| 90 |
redirect_to_settings_in_projects |
|
| 91 |
end |
|
| 92 |
format.api do |
|
| 93 |
render( |
|
| 94 |
:action => 'show', |
|
| 95 |
:status => :created, |
|
| 96 |
:location => board_url(@board) |
|
| 97 |
) |
|
| 98 |
end |
|
| 99 |
end |
|
| 80 | 100 |
else |
| 81 |
render :action => 'new' |
|
| 101 |
respond_to do |format| |
|
| 102 |
format.html {render :action => 'new'}
|
|
| 103 |
format.api {render_validation_errors(@board)}
|
|
| 104 |
end |
|
| 82 | 105 |
end |
| 83 | 106 |
end |
| 84 | 107 | |
| ... | ... | |
| 94 | 117 |
redirect_to_settings_in_projects |
| 95 | 118 |
end |
| 96 | 119 |
format.js {head :ok}
|
| 120 |
format.api {render_api_ok}
|
|
| 97 | 121 |
end |
| 98 | 122 |
else |
| 99 | 123 |
respond_to do |format| |
| 100 | 124 |
format.html {render :action => 'edit'}
|
| 101 | 125 |
format.js {head :unprocessable_content}
|
| 126 |
format.api {render_validation_errors(@board)}
|
|
| 102 | 127 |
end |
| 103 | 128 |
end |
| 104 | 129 |
end |
| 105 | 130 | |
| 106 | 131 |
def destroy |
| 107 |
if @board.destroy |
|
| 108 |
flash[:notice] = l(:notice_successful_delete) |
|
| 132 |
destroyed = @board.destroy |
|
| 133 |
respond_to do |format| |
|
| 134 |
format.html do |
|
| 135 |
if destroyed |
|
| 136 |
flash[:notice] = l(:notice_successful_delete) |
|
| 137 |
else |
|
| 138 |
flash[:error] = @board.errors.full_messages.to_sentence |
|
| 139 |
end |
|
| 140 |
redirect_to_settings_in_projects |
|
| 141 |
end |
|
| 142 |
format.api do |
|
| 143 |
if destroyed |
|
| 144 |
render_api_ok |
|
| 145 |
else |
|
| 146 |
render_validation_errors(@board) |
|
| 147 |
end |
|
| 148 |
end |
|
| 109 | 149 |
end |
| 110 |
redirect_to_settings_in_projects |
|
| 111 | 150 |
end |
| 112 | 151 | |
| 113 | 152 |
private |
| ... | ... | |
| 117 | 156 |
end |
| 118 | 157 | |
| 119 | 158 |
def find_board_if_available |
| 120 |
@board = @project.boards.find(params[:id]) if params[:id] |
|
| 159 |
return if params[:id].blank? |
|
| 160 | ||
| 161 |
if params[:project_id] |
|
| 162 |
@project = Project.find(params[:project_id]) |
|
| 163 |
@board = @project.boards.find(params[:id]) |
|
| 164 |
else |
|
| 165 |
@board = Board.includes(:project).find(params[:id]) |
|
| 166 |
@project = @board.project |
|
| 167 |
end |
|
| 121 | 168 |
rescue ActiveRecord::RecordNotFound |
| 122 | 169 |
render_404 |
| 123 | 170 |
end |
| app/helpers/boards_helper.rb | ||
|---|---|---|
| 38 | 38 |
end |
| 39 | 39 |
options |
| 40 | 40 |
end |
| 41 | ||
| 42 |
def render_api_board(board, api) |
|
| 43 |
api.id board.id |
|
| 44 |
api.project(:id => board.project_id, :name => board.project.name) unless board.project.nil? |
|
| 45 |
api.name board.name |
|
| 46 |
api.description board.description |
|
| 47 |
api.parent(:id => board.parent_id, :name => board.parent.name) unless board.parent.nil? |
|
| 48 |
api.position board.position |
|
| 49 |
api.topics_count board.topics_count |
|
| 50 |
api.messages_count board.messages_count |
|
| 51 |
render_api_board_last_message(board.last_message, api) unless board.last_message.nil? |
|
| 52 |
end |
|
| 53 | ||
| 54 |
def render_api_board_last_message(message, api) |
|
| 55 |
api.last_message(:id => message.id) do |
|
| 56 |
api.author(:id => message.author_id, :name => message.author.name) unless message.author.nil? |
|
| 57 |
api.subject message.subject |
|
| 58 |
api.created_on message.created_on |
|
| 59 |
end |
|
| 60 |
end |
|
| 41 | 61 |
end |
| app/views/boards/index.api.rsb | ||
|---|---|---|
| 1 |
api.array :boards, api_meta(:total_count => @boards.size) do |
|
| 2 |
@boards.each do |board| |
|
| 3 |
api.board do |
|
| 4 |
render_api_board(board, api) |
|
| 5 |
end |
|
| 6 |
end |
|
| 7 |
end |
|
| app/views/boards/show.api.rsb | ||
|---|---|---|
| 1 |
api.board do |
|
| 2 |
render_api_board(@board, api) |
|
| 3 |
end |
|
| config/routes.rb | ||
|---|---|---|
| 52 | 52 |
post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' |
| 53 | 53 |
post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' |
| 54 | 54 |
post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' |
| 55 |
get 'boards/:id.:format', :to => 'boards#show', :as => 'board', :constraints => {:format => /xml|json/}
|
|
| 56 |
put 'boards/:id.:format', :to => 'boards#update', :constraints => {:format => /xml|json/}
|
|
| 57 |
patch 'boards/:id.:format', :to => 'boards#update', :constraints => {:format => /xml|json/}
|
|
| 58 |
delete 'boards/:id.:format', :to => 'boards#destroy', :constraints => {:format => /xml|json/}
|
|
| 55 | 59 | |
| 56 | 60 |
# Auto complete routes |
| 57 | 61 |
match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' |
| test/integration/api_test/api_routing_test.rb | ||
|---|---|---|
| 27 | 27 |
should_route 'POST /uploads' => 'attachments#upload' |
| 28 | 28 |
end |
| 29 | 29 | |
| 30 |
def test_boards |
|
| 31 |
should_route 'GET /projects/foo/boards' => 'boards#index', :project_id => 'foo' |
|
| 32 |
should_route 'POST /projects/foo/boards' => 'boards#create', :project_id => 'foo' |
|
| 33 | ||
| 34 |
should_route 'GET /boards/1' => 'boards#show', :id => '1' |
|
| 35 |
should_route 'PUT /boards/1' => 'boards#update', :id => '1' |
|
| 36 |
should_route 'DELETE /boards/1' => 'boards#destroy', :id => '1' |
|
| 37 |
end |
|
| 38 | ||
| 30 | 39 |
def test_custom_fields |
| 31 | 40 |
should_route 'GET /custom_fields' => 'custom_fields#index' |
| 32 | 41 |
end |
| test/integration/api_test/boards_test.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
require_relative '../../test_helper' |
|
| 21 | ||
| 22 |
class Redmine::ApiTest::BoardsTest < Redmine::ApiTest::Base |
|
| 23 |
test "GET /projects/:project_id/boards.xml should return the boards" do |
|
| 24 |
get '/projects/1/boards.xml', :headers => credentials('jsmith')
|
|
| 25 | ||
| 26 |
assert_response :success |
|
| 27 |
assert_equal 'application/xml', @response.media_type |
|
| 28 |
assert_select 'boards[type=array] board id', :text => '1' |
|
| 29 |
assert_select 'boards[type=array] board last_message[id="6"] subject', :text => 'RE: post 2' |
|
| 30 |
end |
|
| 31 | ||
| 32 |
test "GET /boards/:id.xml should return the board" do |
|
| 33 |
get '/boards/1.xml', :headers => credentials('jsmith')
|
|
| 34 | ||
| 35 |
assert_response :success |
|
| 36 |
assert_equal 'application/xml', @response.media_type |
|
| 37 |
assert_select 'board' do |
|
| 38 |
assert_select 'id', :text => '1' |
|
| 39 |
assert_select 'project[id="1"][name="eCookbook"]' |
|
| 40 |
assert_select 'name', :text => 'Help' |
|
| 41 |
assert_select 'description', :text => 'Help board' |
|
| 42 |
assert_select 'topics_count', :text => '2' |
|
| 43 |
assert_select 'messages_count', :text => '6' |
|
| 44 |
end |
|
| 45 |
end |
|
| 46 | ||
| 47 |
test "POST /projects/:project_id/boards.xml should create the board" do |
|
| 48 |
assert_difference 'Board.count' do |
|
| 49 |
post( |
|
| 50 |
'/projects/1/boards.xml', |
|
| 51 |
:params => {
|
|
| 52 |
:board => {
|
|
| 53 |
:name => 'API', |
|
| 54 |
:description => 'API board', |
|
| 55 |
:parent_id => 2 |
|
| 56 |
} |
|
| 57 |
}, |
|
| 58 |
:headers => credentials('jsmith'))
|
|
| 59 |
end |
|
| 60 | ||
| 61 |
board = Board.order(id: :desc).first |
|
| 62 |
assert_response :created |
|
| 63 |
assert_equal 'application/xml', @response.media_type |
|
| 64 |
assert_equal 'API', board.name |
|
| 65 |
assert_equal 'API board', board.description |
|
| 66 |
assert_equal Board.find(2), board.parent |
|
| 67 |
assert_select 'board id', :text => board.id.to_s |
|
| 68 |
end |
|
| 69 | ||
| 70 |
test "POST /projects/:project_id/boards.xml with invalid parameters should return errors" do |
|
| 71 |
assert_no_difference 'Board.count' do |
|
| 72 |
post( |
|
| 73 |
'/projects/1/boards.xml', |
|
| 74 |
:params => {:board => {:name => '', :description => 'API board'}},
|
|
| 75 |
:headers => credentials('jsmith'))
|
|
| 76 |
end |
|
| 77 | ||
| 78 |
assert_response :unprocessable_content |
|
| 79 |
assert_equal 'application/xml', @response.media_type |
|
| 80 |
assert_select 'errors error', :text => 'Name cannot be blank' |
|
| 81 |
end |
|
| 82 | ||
| 83 |
test "PUT /boards/:id.xml should update the board" do |
|
| 84 |
put( |
|
| 85 |
'/boards/2.xml', |
|
| 86 |
:params => {:board => {:name => 'API Update', :description => 'Updated board'}},
|
|
| 87 |
:headers => credentials('jsmith'))
|
|
| 88 | ||
| 89 |
assert_response :no_content |
|
| 90 |
assert_equal '', @response.body |
|
| 91 |
assert_equal 'API Update', Board.find(2).name |
|
| 92 |
assert_equal 'Updated board', Board.find(2).description |
|
| 93 |
end |
|
| 94 | ||
| 95 |
test "DELETE /boards/:id.xml should destroy the board" do |
|
| 96 |
assert_difference 'Board.count', -1 do |
|
| 97 |
delete '/boards/2.xml', :headers => credentials('jsmith')
|
|
| 98 |
end |
|
| 99 | ||
| 100 |
assert_response :no_content |
|
| 101 |
assert_equal '', @response.body |
|
| 102 |
assert_nil Board.find_by_id(2) |
|
| 103 |
end |
|
| 104 |
end |
|