BigW Consortium Gitlab

projects.rb 16.4 KB
Newer Older
1
module API
2 3
  # Projects API
  class Projects < Grape::API
4 5
    include PaginationParams

6
    before { authenticate_non_get! }
7

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
    helpers do
      params :optional_params do
        optional :description, type: String, desc: 'The description of the project'
        optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
        optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
        optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
        optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled'
        optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
        optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
        optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
        optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
        optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
        optional :visibility_level, type: Integer, values: [
          Gitlab::VisibilityLevel::PRIVATE,
          Gitlab::VisibilityLevel::INTERNAL,
          Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.'
        optional :public_builds, type: Boolean, desc: 'Perform public builds'
        optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
        optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
        optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
      end

      def map_public_to_visibility_level(attrs)
        publik = attrs.delete(:public)
        if !publik.nil? && !attrs[:visibility_level].present?
          # Since setting the public attribute to private could mean either
          # private or internal, use the more conservative option, private.
          attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
        end
        attrs
      end
    end

    resource :projects do
42
      helpers do
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
        params :sort_params do
          optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
                              default: 'created_at', desc: 'Return projects ordered by field'
          optional :sort, type: String, values: %w[asc desc], default: 'desc',
                          desc: 'Return projects sorted in ascending and descending order'
        end

        params :filter_params do
          optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
          optional :visibility, type: String, values: %w[public internal private],
                                desc: 'Limit by visibility'
          optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
          use :sort_params
        end

        params :create_params do
          optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
          optional :import_url, type: String, desc: 'URL from which the project is imported'
61
        end
62 63
      end

64
      desc 'Get a list of visible projects for authenticated user' do
65 66 67 68 69 70 71 72
        success Entities::BasicProjectDetails
      end
      params do
        optional :simple, type: Boolean, default: false,
                          desc: 'Return only the ID, URL, name, and path of each project'
        use :filter_params
        use :pagination
      end
73 74
      get '/visible' do
        projects = ProjectsFinder.new.execute(current_user)
75
        projects = filter_projects(projects)
76
        entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
77

78
        present paginate(projects), with: entity, user: current_user
79 80
      end

81
      desc 'Get a projects list for authenticated user' do
82 83 84 85 86 87 88 89
        success Entities::BasicProjectDetails
      end
      params do
        optional :simple, type: Boolean, default: false,
                          desc: 'Return only the ID, URL, name, and path of each project'
        use :filter_params
        use :pagination
      end
90 91 92 93
      get do
        authenticate!

        projects = current_user.authorized_projects
94 95 96
        projects = filter_projects(projects)
        entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess

97
        present paginate(projects), with: entity, user: current_user
98 99
      end

100 101 102 103 104 105 106
      desc 'Get an owned projects list for authenticated user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :filter_params
        use :pagination
      end
107
      get '/owned' do
108 109
        authenticate!

110 111
        projects = current_user.owned_projects
        projects = filter_projects(projects)
112 113

        present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
114 115
      end

116 117 118 119 120 121 122
      desc 'Gets starred project for the authenticated user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :filter_params
        use :pagination
      end
123
      get '/starred' do
124 125
        authenticate!

126 127
        projects = current_user.viewable_starred_projects
        projects = filter_projects(projects)
128 129

        present paginate(projects), with: Entities::Project, user: current_user
130 131
      end

132 133 134 135 136 137 138
      desc 'Get all projects for admin user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :filter_params
        use :pagination
      end
139 140
      get '/all' do
        authenticated_as_admin!
141

142 143
        projects = Project.all
        projects = filter_projects(projects)
144 145

        present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
146 147
      end

148 149 150 151 152 153 154
      desc 'Search for projects the current user has access to' do
        success Entities::Project
      end
      params do
        requires :query, type: String, desc: 'The project name to be searched'
        use :sort_params
        use :pagination
155
      end
156 157 158 159
      get "/search/:query" do
        search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
        projects = search_service.objects('projects', params[:page])
        projects = projects.reorder(params[:order_by] => params[:sort])
160

161 162 163 164 165 166 167 168 169 170 171 172
        present paginate(projects), with: Entities::Project
      end

      desc 'Create new project' do
        success Entities::Project
      end
      params do
        requires :name, type: String, desc: 'The name of the project'
        optional :path, type: String, desc: 'The path of the repository'
        use :optional_params
        use :create_params
      end
173
      post do
174 175 176 177 178 179
        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
        project = ::Projects::CreateService.new(current_user, attrs).execute

        if project.saved?
          present project, with: Entities::Project,
                           user_can_admin_project: can?(current_user, :admin_project, project)
180
        else
181 182
          if project.errors[:limit_reached].present?
            error!(project.errors[:limit_reached], 403)
183
          end
184
          render_validation_error!(project)
185 186 187
        end
      end

188 189 190 191 192 193 194 195 196 197
      desc 'Create new project for a specified user. Only available to admin users.' do
        success Entities::Project
      end
      params do
        requires :name, type: String, desc: 'The name of the project'
        requires :user_id, type: Integer, desc: 'The ID of a user'
        optional :default_branch, type: String, desc: 'The default branch of the project'
        use :optional_params
        use :create_params
      end
Angus MacArthur committed
198 199
      post "user/:user_id" do
        authenticated_as_admin!
200 201 202 203 204 205 206 207 208
        user = User.find_by(id: params.delete(:user_id))
        not_found!('User') unless user

        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
        project = ::Projects::CreateService.new(user, attrs).execute

        if project.saved?
          present project, with: Entities::Project,
                           user_can_admin_project: can?(current_user, :admin_project, project)
Angus MacArthur committed
209
        else
210
          render_validation_error!(project)
Angus MacArthur committed
211 212
        end
      end
213 214 215 216 217 218 219 220 221 222
    end

    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
    resource :projects, requirements: { id: /[^\/]+/ } do
      desc 'Get a single project' do
        success Entities::ProjectWithAccess
      end
      get ":id" do
223 224
        entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
        present user_project, with: entity, user: current_user,
225 226 227 228 229 230 231 232 233 234 235 236
                              user_can_admin_project: can?(current_user, :admin_project, user_project)
      end

      desc 'Get events for a single project' do
        success Entities::Event
      end
      params do
        use :pagination
      end
      get ":id/events" do
        present paginate(user_project.events.recent), with: Entities::Event
      end
Angus MacArthur committed
237

238 239 240 241 242 243
      desc 'Fork new project for the current user or provided namespace.' do
        success Entities::Project
      end
      params do
        optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
      end
244
      post 'fork/:id' do
245 246
        fork_params = declared_params(include_missing: false)
        namespace_id = fork_params[:namespace]
247

248
        if namespace_id.present?
249 250 251 252 253
          fork_params[:namespace] = if namespace_id =~ /^\d+$/
                                      Namespace.find_by(id: namespace_id)
                                    else
                                      Namespace.find_by_path_or_name(namespace_id)
                                    end
254

255
          unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
256 257
            not_found!('Target Namespace')
          end
258
        end
259

260
        forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
261

262 263
        if forked_project.errors.any?
          conflict!(forked_project.errors.messages)
264
        else
265 266
          present forked_project, with: Entities::Project,
                                  user_can_admin_project: can?(current_user, :admin_project, forked_project)
267
        end
268 269
      end

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
      desc 'Update an existing project' do
        success Entities::Project
      end
      params do
        optional :name, type: String, desc: 'The name of the project'
        optional :default_branch, type: String, desc: 'The default branch of the project'
        optional :path, type: String, desc: 'The path of the repository'
        use :optional_params
        at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
                        :wiki_enabled, :builds_enabled, :snippets_enabled,
                        :shared_runners_enabled, :container_registry_enabled,
                        :lfs_enabled, :public, :visibility_level, :public_builds,
                        :request_access_enabled, :only_allow_merge_if_build_succeeds,
                        :only_allow_merge_if_all_discussions_are_resolved, :path,
                        :default_branch
      end
286 287
      put ':id' do
        authorize_admin_project
288
        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
289
        authorize! :rename_project, user_project if attrs[:name].present?
290
        authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
291

292
        ::Projects::UpdateService.new(user_project, current_user, attrs).execute
293

294
        if user_project.errors.any?
295
          render_validation_error!(user_project)
296
        else
297 298
          present user_project, with: Entities::Project,
                                user_can_admin_project: can?(current_user, :admin_project, user_project)
299 300 301
        end
      end

302 303 304
      desc 'Archive a project' do
        success Entities::Project
      end
305
      post ':id/archive' do
306 307 308 309
        authorize!(:archive_project, user_project)

        user_project.archive!

310
        present user_project, with: Entities::Project
311 312
      end

313 314 315
      desc 'Unarchive a project' do
        success Entities::Project
      end
316
      post ':id/unarchive' do
317 318 319 320
        authorize!(:archive_project, user_project)

        user_project.unarchive!

321
        present user_project, with: Entities::Project
322 323
      end

324 325 326
      desc 'Star a project' do
        success Entities::Project
      end
327
      post ':id/star' do
328 329 330
        if current_user.starred?(user_project)
          not_modified!
        else
331 332
          current_user.toggle_star(user_project)
          user_project.reload
333

334 335 336 337
          present user_project, with: Entities::Project
        end
      end

338 339 340
      desc 'Unstar a project' do
        success Entities::Project
      end
341
      delete ':id/star' do
342 343 344
        if current_user.starred?(user_project)
          current_user.toggle_star(user_project)
          user_project.reload
345

346 347 348 349 350 351
          present user_project, with: Entities::Project
        else
          not_modified!
        end
      end

352
      desc 'Remove a project'
353 354
      delete ":id" do
        authorize! :remove_project, user_project
Stan Hu committed
355
        ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
356
      end
Angus MacArthur committed
357

358 359 360 361
      desc 'Mark this project as forked from another'
      params do
        requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from'
      end
362 363
      post ":id/fork/:forked_from_id" do
        authenticated_as_admin!
364

365
        forked_from_project = find_project!(params[:forked_from_id])
366 367 368 369
        not_found!("Source Project") unless forked_from_project

        if user_project.forked_from_project.nil?
          user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
370
        else
371
          render_api_error!("Project already forked", 409)
372 373 374
        end
      end

375
      desc 'Remove a forked_from relationship'
376
      delete ":id/fork" do
377
        authorize! :remove_fork_project, user_project
378

379
        if user_project.forked?
380
          user_project.forked_project_link.destroy
381 382
        else
          not_modified!
383 384
        end
      end
385

386 387 388 389 390 391 392 393
      desc 'Share the project with a group' do
        success Entities::ProjectGroupLink
      end
      params do
        requires :group_id, type: Integer, desc: 'The ID of a group'
        requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level'
        optional :expires_at, type: Date, desc: 'Share expiration date'
      end
394 395
      post ":id/share" do
        authorize! :admin_project, user_project
396
        group = Group.find_by_id(params[:group_id])
397 398 399 400 401

        unless group && can?(current_user, :read_group, group)
          not_found!('Group')
        end

402 403 404 405
        unless user_project.allowed_to_share_with_group?
          return render_api_error!("The project sharing with group is disabled", 400)
        end

406
        link = user_project.project_group_links.new(declared_params(include_missing: false))
407

408 409 410 411 412 413 414
        if link.save
          present link, with: Entities::ProjectGroupLink
        else
          render_api_error!(link.errors.full_messages.first, 409)
        end
      end

415 416 417 418 419 420 421 422 423 424 425 426 427
      params do
        requires :group_id, type: Integer, desc: 'The ID of the group'
      end
      delete ":id/share/:group_id" do
        authorize! :admin_project, user_project

        link = user_project.project_group_links.find_by(group_id: params[:group_id])
        not_found!('Group Link') unless link

        link.destroy
        no_content!
      end

428 429 430 431
      desc 'Upload a file'
      params do
        requires :file, type: File, desc: 'The file to be uploaded'
      end
432 433 434 435
      post ":id/uploads" do
        ::Projects::UploadService.new(user_project, params[:file]).execute
      end

436 437 438 439 440 441
      desc 'Get the users list of a project' do
        success Entities::UserBasic
      end
      params do
        optional :search, type: String, desc: 'Return list of users matching the search criteria'
        use :pagination
442
      end
443
      get ':id/users' do
444
        users = user_project.team.users
445 446 447
        users = users.search(params[:search]) if params[:search].present?

        present paginate(users), with: Entities::UserBasic
448
      end
449 450 451
    end
  end
end