1# frozen_string_literal: true
2
3module Boards
4  class IssuesController < Boards::ApplicationController
5    # This is the maximum amount of issues which can be moved by one request to
6    # bulk_move for now. This is temporary and might be removed in future by
7    # introducing an alternative (async?) approach.
8    # (related: https://gitlab.com/groups/gitlab-org/-/epics/382)
9    MAX_MOVE_ISSUES_COUNT = 50
10
11    include BoardsResponses
12    include ControllerWithCrossProjectAccessCheck
13
14    requires_cross_project_access if: -> { board&.group_board? }
15
16    before_action :disable_query_limiting, only: [:bulk_move]
17    before_action :authorize_read_issue, only: [:index]
18    before_action :authorize_create_issue, only: [:create]
19    before_action :authorize_update_issue, only: [:update]
20    skip_before_action :authenticate_user!, only: [:index]
21    before_action :validate_id_list, only: [:bulk_move]
22    before_action :can_move_issues?, only: [:bulk_move]
23
24    feature_category :team_planning
25
26    def index
27      list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
28      issues = issues_from(list_service)
29
30      ::Boards::Issues::ListService.initialize_relative_positions(board, current_user, issues)
31
32      render_issues(issues, list_service.metadata)
33    end
34
35    def create
36      service = Boards::Issues::CreateService.new(board_parent, project, current_user, issue_params)
37      issue = service.execute
38
39      if issue.valid?
40        render json: serialize_as_json(issue)
41      else
42        render json: issue.errors, status: :unprocessable_entity
43      end
44    end
45
46    def bulk_move
47      service = Boards::Issues::MoveService.new(board_parent, current_user, move_params(true))
48
49      issues = Issue.find(params[:ids])
50
51      render json: service.execute_multiple(issues)
52    end
53
54    def update
55      service = Boards::Issues::MoveService.new(board_parent, current_user, move_params)
56
57      if service.execute(issue)
58        head :ok
59      else
60        head :unprocessable_entity
61      end
62    end
63
64    private
65
66    def issues_from(list_service)
67      issues = list_service.execute
68      issues.page(params[:page]).per(params[:per] || 20)
69            .without_count
70            .preload(associations_to_preload) # rubocop: disable CodeReuse/ActiveRecord
71            .load
72    end
73
74    def associations_to_preload
75      [
76        :milestone,
77        :assignees,
78        project: [
79            :route,
80            {
81                namespace: [:route]
82            }
83        ],
84        labels: [:priorities],
85        notes: [:award_emoji, :author]
86      ]
87    end
88
89    def can_move_issues?
90      head(:forbidden) unless can?(current_user, :admin_issue, board)
91    end
92
93    def serializer_options(issues)
94      {}
95    end
96
97    def render_issues(issues, metadata)
98      data = { issues: serialize_as_json(issues, opts: serializer_options(issues)) }
99      data.merge!(metadata)
100
101      render json: data
102    end
103
104    def issue
105      @issue ||= issues_finder.find(params[:id])
106    end
107
108    def filter_params
109      params.permit(*Boards::Issues::ListService.valid_params).merge(board_id: params[:board_id], id: params[:list_id])
110        .reject { |_, value| value.nil? }
111    end
112
113    def issues_finder
114      if board.group_board?
115        IssuesFinder.new(current_user, group_id: board_parent.id)
116      else
117        IssuesFinder.new(current_user, project_id: board_parent.id)
118      end
119    end
120
121    def project
122      @project ||= if board.group_board?
123                     Project.find(issue_params[:project_id])
124                   else
125                     board_parent
126                   end
127    end
128
129    def move_params(multiple = false)
130      id_param = multiple ? :ids : :id
131      params.permit(id_param, :board_id, :from_list_id, :to_list_id, :move_before_id, :move_after_id)
132    end
133
134    def issue_params
135      params.require(:issue)
136        .permit(:title, :milestone_id, :project_id)
137        .merge(board_id: params[:board_id], list_id: params[:list_id])
138    end
139
140    def serializer
141      IssueSerializer.new(current_user: current_user)
142    end
143
144    def serialize_as_json(resource, opts: {})
145      opts.merge!(include_full_project_path: board.group_board?, serializer: 'board')
146
147      serializer.represent(resource, opts)
148    end
149
150    def disable_query_limiting
151      Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/issues/35174')
152    end
153
154    def validate_id_list
155      head(:bad_request) unless params[:ids].is_a?(Array)
156      head(:unprocessable_entity) if params[:ids].size > MAX_MOVE_ISSUES_COUNT
157    end
158  end
159end
160
161Boards::IssuesController.prepend_mod_with('Boards::IssuesController')
162