BigW Consortium Gitlab

projects.rb 15 KB
1 2 3 4 5 6 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
module API
  # Projects API
  class Projects < Grape::API
    include PaginationParams

    before { authenticate_non_get! }

    helpers do
      params :optional_params_ce 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 :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs 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 :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
        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_pipeline_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

      params :optional_params do
        use :optional_params_ce
      end

      params :statistics_params do
        optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
      end
    end

    resource :projects do
      helpers do
        params :collection_params do
          use :sort_params
          use :filter_params
          use :pagination

          optional :simple, type: Boolean, default: false,
                            desc: 'Return only the ID, URL, name, and path of each project'
        end

        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: Gitlab::VisibilityLevel.string_values,
                                desc: 'Limit by visibility'
          optional :search, type: String, desc: 'Return list of projects matching the search criteria'
          optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
          optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
          optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
        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'
        end

        def present_projects(projects, options = {})
          options = options.reverse_merge(
            with: Entities::Project,
            current_user: current_user,
            simple: params[:simple]
          )

          projects = filter_projects(projects)
          projects = projects.with_statistics if options[:statistics]
          options[:with] = Entities::BasicProjectDetails if options[:simple]

          present paginate(projects), options
        end
      end

      desc 'Get a list of visible projects for authenticated user' do
        success Entities::BasicProjectDetails
      end
      params do
        use :collection_params
        use :statistics_params
      end
      get do
        entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
        present_projects ProjectsFinder.new(current_user: current_user).execute, with: entity, statistics: params[:statistics]
      end

      desc 'Create new project' do
        success Entities::Project
      end
      params do
        optional :name, type: String, desc: 'The name of the project'
        optional :path, type: String, desc: 'The path of the repository'
        at_least_one_of :name, :path
        use :optional_params
        use :create_params
      end
      post do
        attrs = declared_params(include_missing: false)
        attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.has_key?(:jobs_enabled)
        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)
        else
          if project.errors[:limit_reached].present?
            error!(project.errors[:limit_reached], 403)
          end
          render_validation_error!(project)
        end
      end

      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
      post "user/:user_id" do
        authenticated_as_admin!
        user = User.find_by(id: params.delete(:user_id))
        not_found!('User') unless user

        attrs = 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)
        else
          render_validation_error!(project)
        end
      end
    end

    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
    resource :projects, requirements: { id: %r{[^/]+} } do
      desc 'Get a single project' do
        success Entities::ProjectWithAccess
      end
      params do
        use :statistics_params
      end
      get ":id" do
        entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
        present user_project, with: entity, current_user: current_user,
                              user_can_admin_project: can?(current_user, :admin_project, user_project), statistics: params[:statistics]
      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

      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
      post ':id/fork' do
        fork_params = declared_params(include_missing: false)
        namespace_id = fork_params[:namespace]

        if namespace_id.present?
          fork_params[:namespace] = if namespace_id =~ /^\d+$/
                                      Namespace.find_by(id: namespace_id)
                                    else
                                      Namespace.find_by_path_or_name(namespace_id)
                                    end

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

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

        if forked_project.errors.any?
          conflict!(forked_project.errors.messages)
        else
          present forked_project, with: Entities::Project,
                                  user_can_admin_project: can?(current_user, :admin_project, forked_project)
        end
      end

      desc 'Update an existing project' do
        success Entities::Project
      end
      params do
        # CE
        at_least_one_of_ce =
          [
            :jobs_enabled,
            :container_registry_enabled,
            :default_branch,
            :description,
            :issues_enabled,
            :lfs_enabled,
            :merge_requests_enabled,
            :name,
            :only_allow_merge_if_all_discussions_are_resolved,
            :only_allow_merge_if_pipeline_succeeds,
            :path,
            :public_builds,
            :request_access_enabled,
            :shared_runners_enabled,
            :snippets_enabled,
            :visibility,
            :wiki_enabled
          ]
        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(*at_least_one_of_ce)
      end
      put ':id' do
        authorize_admin_project
        attrs = declared_params(include_missing: false)
        authorize! :rename_project, user_project if attrs[:name].present?
        authorize! :change_visibility_level, user_project if attrs[:visibility].present?

        attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.has_key?(:jobs_enabled)

        result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute

        if result[:status] == :success
          present user_project, with: Entities::Project,
                                user_can_admin_project: can?(current_user, :admin_project, user_project)
        else
          render_validation_error!(user_project)
        end
      end

      desc 'Archive a project' do
        success Entities::Project
      end
      post ':id/archive' do
        authorize!(:archive_project, user_project)

        user_project.archive!

        present user_project, with: Entities::Project
      end

      desc 'Unarchive a project' do
        success Entities::Project
      end
      post ':id/unarchive' do
        authorize!(:archive_project, user_project)

        user_project.unarchive!

        present user_project, with: Entities::Project
      end

      desc 'Star a project' do
        success Entities::Project
      end
      post ':id/star' do
        if current_user.starred?(user_project)
          not_modified!
        else
          current_user.toggle_star(user_project)
          user_project.reload

          present user_project, with: Entities::Project
        end
      end

      desc 'Unstar a project' do
        success Entities::Project
      end
      post ':id/unstar' do
        if current_user.starred?(user_project)
          current_user.toggle_star(user_project)
          user_project.reload

          present user_project, with: Entities::Project
        else
          not_modified!
        end
      end

      desc 'Remove a project'
      delete ":id" do
        authorize! :remove_project, user_project
        ::Projects::DestroyService.new(user_project, current_user, {}).async_execute

        accepted!
      end

      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
      post ":id/fork/:forked_from_id" do
        authenticated_as_admin!

        forked_from_project = find_project!(params[:forked_from_id])
        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)
        else
          render_api_error!("Project already forked", 409)
        end
      end

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

        if user_project.forked?
          user_project.forked_project_link.destroy
        else
          not_modified!
        end
      end

      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
      post ":id/share" do
        authorize! :admin_project, user_project
        group = Group.find_by_id(params[:group_id])

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

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

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

        if link.save
          present link, with: Entities::ProjectGroupLink
        else
          render_api_error!(link.errors.full_messages.first, 409)
        end
      end

      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
      end

      desc 'Upload a file'
      params do
        requires :file, type: File, desc: 'The file to be uploaded'
      end
      post ":id/uploads" do
        UploadService.new(user_project, params[:file]).execute
      end

      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
      end
      get ':id/users' do
        users = user_project.team.users
        users = users.search(params[:search]) if params[:search].present?

        present paginate(users), with: Entities::UserBasic
      end

      desc 'Start the housekeeping task for a project' do
        detail 'This feature was introduced in GitLab 9.0.'
      end
      post ':id/housekeeping' do
        authorize_admin_project

        begin
          ::Projects::HousekeepingService.new(user_project).execute
        rescue ::Projects::HousekeepingService::LeaseTaken => error
          conflict!(error.message)
        end
      end
    end
  end
end