BigW Consortium Gitlab

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

6 7
    before { authenticate! }

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 65 66 67 68 69 70 71 72
      desc 'Get a projects list for authenticated user' do
        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
      get do
74 75 76 77
        projects = current_user.authorized_projects
        projects = filter_projects(projects)
        entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess

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

81 82 83 84 85 86 87 88 89
      desc 'Get a list of visible projects for authenticated user' do
        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
      get '/visible' do
91 92 93 94
        projects = ProjectsFinder.new.execute(current_user)
        projects = filter_projects(projects)
        entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess

95
        present paginate(projects), with: entity, user: current_user
96 97
      end

98 99 100 101 102 103 104
      desc 'Get an owned projects list for authenticated user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :filter_params
        use :pagination
      end
105
      get '/owned' do
106 107
        projects = current_user.owned_projects
        projects = filter_projects(projects)
108 109

        present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
110 111
      end

112 113 114 115 116 117 118
      desc 'Gets starred project for the authenticated user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :filter_params
        use :pagination
      end
119
      get '/starred' do
120 121
        projects = current_user.viewable_starred_projects
        projects = filter_projects(projects)
122 123

        present paginate(projects), with: Entities::Project, user: current_user
124 125
      end

126 127 128 129 130 131 132
      desc 'Get all projects for admin user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :filter_params
        use :pagination
      end
133 134
      get '/all' do
        authenticated_as_admin!
135 136
        projects = Project.all
        projects = filter_projects(projects)
137 138

        present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
139 140
      end

141 142 143 144 145 146 147
      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
148
      end
149 150 151 152
      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])
153

154 155 156 157 158 159 160 161 162 163 164 165
        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
166
      post do
167 168 169 170 171 172
        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)
173
        else
174 175
          if project.errors[:limit_reached].present?
            error!(project.errors[:limit_reached], 403)
176
          end
177
          render_validation_error!(project)
178 179 180
        end
      end

181 182 183 184 185 186 187 188 189 190
      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
191 192
      post "user/:user_id" do
        authenticated_as_admin!
193 194 195 196 197 198 199 200 201
        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
202
        else
203
          render_validation_error!(project)
Angus MacArthur committed
204 205
        end
      end
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    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
        present user_project, with: Entities::ProjectWithAccess, user: current_user,
                              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
229

230 231 232 233 234 235
      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
236
      post 'fork/:id' do
237 238
        fork_params = declared_params(include_missing: false)
        namespace_id = fork_params[:namespace]
239

240
        if namespace_id.present?
241 242 243 244 245
          fork_params[:namespace] = if namespace_id =~ /^\d+$/
                                      Namespace.find_by(id: namespace_id)
                                    else
                                      Namespace.find_by_path_or_name(namespace_id)
                                    end
246

247
          unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
248 249
            not_found!('Target Namespace')
          end
250
        end
251

252
        forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
253

254 255
        if forked_project.errors.any?
          conflict!(forked_project.errors.messages)
256
        else
257 258
          present forked_project, with: Entities::Project,
                                  user_can_admin_project: can?(current_user, :admin_project, forked_project)
259
        end
260 261
      end

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
      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
278 279
      put ':id' do
        authorize_admin_project
280
        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
281
        authorize! :rename_project, user_project if attrs[:name].present?
282
        authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
283

284
        ::Projects::UpdateService.new(user_project, current_user, attrs).execute
285

286
        if user_project.errors.any?
287
          render_validation_error!(user_project)
288
        else
289 290
          present user_project, with: Entities::Project,
                                user_can_admin_project: can?(current_user, :admin_project, user_project)
291 292 293
        end
      end

294 295 296
      desc 'Archive a project' do
        success Entities::Project
      end
297
      post ':id/archive' do
298 299 300 301
        authorize!(:archive_project, user_project)

        user_project.archive!

302
        present user_project, with: Entities::Project
303 304
      end

305 306 307
      desc 'Unarchive a project' do
        success Entities::Project
      end
308
      post ':id/unarchive' do
309 310 311 312
        authorize!(:archive_project, user_project)

        user_project.unarchive!

313
        present user_project, with: Entities::Project
314 315
      end

316 317 318
      desc 'Star a project' do
        success Entities::Project
      end
319
      post ':id/star' do
320 321 322
        if current_user.starred?(user_project)
          not_modified!
        else
323 324
          current_user.toggle_star(user_project)
          user_project.reload
325

326 327 328 329
          present user_project, with: Entities::Project
        end
      end

330 331 332
      desc 'Unstar a project' do
        success Entities::Project
      end
333
      delete ':id/star' do
334 335 336
        if current_user.starred?(user_project)
          current_user.toggle_star(user_project)
          user_project.reload
337

338 339 340 341 342 343
          present user_project, with: Entities::Project
        else
          not_modified!
        end
      end

344
      desc 'Remove a project'
345 346
      delete ":id" do
        authorize! :remove_project, user_project
Stan Hu committed
347
        ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
348
      end
Angus MacArthur committed
349

350 351 352 353
      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
354 355
      post ":id/fork/:forked_from_id" do
        authenticated_as_admin!
356

357
        forked_from_project = find_project!(params[:forked_from_id])
358 359 360 361
        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)
362
        else
363
          render_api_error!("Project already forked", 409)
364 365 366
        end
      end

367
      desc 'Remove a forked_from relationship'
368
      delete ":id/fork" do
369
        authorize! :remove_fork_project, user_project
370

371
        if user_project.forked?
372
          user_project.forked_project_link.destroy
373 374
        else
          not_modified!
375 376
        end
      end
377

378 379 380 381 382 383 384 385
      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
386 387
      post ":id/share" do
        authorize! :admin_project, user_project
388
        group = Group.find_by_id(params[:group_id])
389 390 391 392 393

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

394 395 396 397
        unless user_project.allowed_to_share_with_group?
          return render_api_error!("The project sharing with group is disabled", 400)
        end

398
        link = user_project.project_group_links.new(declared_params(include_missing: false))
399

400 401 402 403 404 405 406
        if link.save
          present link, with: Entities::ProjectGroupLink
        else
          render_api_error!(link.errors.full_messages.first, 409)
        end
      end

407 408 409 410 411 412 413 414 415 416 417 418 419
      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

420 421 422 423
      desc 'Upload a file'
      params do
        requires :file, type: File, desc: 'The file to be uploaded'
      end
424 425 426 427
      post ":id/uploads" do
        ::Projects::UploadService.new(user_project, params[:file]).execute
      end

428 429 430 431 432 433
      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
434
      end
435
      get ':id/users' do
436 437 438 439
        users = User.where(id: user_project.team.users.map(&:id))
        users = users.search(params[:search]) if params[:search].present?

        present paginate(users), with: Entities::UserBasic
440
      end
441 442 443
    end
  end
end