Project

General

Profile

Feature #44141 » 0001-Add-REST-API-for-boards.patch

Go MAEDA, 2026-06-04 12:45

View differences:

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
(1-1/2)