diff options
Diffstat (limited to 'lib/api')
| -rw-r--r-- | lib/api/api.rb | 7 | ||||
| -rw-r--r-- | lib/api/entities.rb | 1 | ||||
| -rw-r--r-- | lib/api/helpers.rb | 2 | ||||
| -rw-r--r-- | lib/api/projects.rb | 16 | ||||
| -rw-r--r-- | lib/api/settings.rb | 3 | ||||
| -rw-r--r-- | lib/api/v3/projects.rb | 458 | 
6 files changed, 468 insertions, 19 deletions
| diff --git a/lib/api/api.rb b/lib/api/api.rb index 6cf6b501021..090109d5e6f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -1,7 +1,12 @@  module API    class API < Grape::API      include APIGuard -    version 'v3', using: :path + +    version %w(v3 v4), using: :path + +    version 'v3', using: :path do +      mount ::API::V3::Projects +    end      before { allow_access_with_scope :api } diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a07b2a9ca0f..b1ead48caf7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -575,6 +575,7 @@ module API        expose :koding_url        expose :plantuml_enabled        expose :plantuml_url +      expose :terminal_max_session_time      end      class Release < Grape::Entity diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index eb5b947172a..dfab60f7fa5 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -304,7 +304,7 @@ module API          header['X-Sendfile'] = path          body        else -        path +        file path        end      end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 941f47114a4..92a70faf1c2 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -151,22 +151,6 @@ module API          present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics]        end -      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 -      end -      get "/search/:query", requirements: { 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]) - -        present paginate(projects), with: Entities::Project -      end -        desc 'Create new project' do          success Entities::Project        end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index c5eff16a5de..a1d1c1432d3 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -107,6 +107,7 @@ module API          requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."          requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."        end +      optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'        at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,                        :default_group_visibility, :restricted_visibility_levels, :import_sources,                        :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit, @@ -120,7 +121,7 @@ module API                        :akismet_enabled, :admin_notification_email, :sentry_enabled,                        :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,                        :version_check_enabled, :email_author_in_body, :html_emails_enabled, -                      :housekeeping_enabled +                      :housekeeping_enabled, :terminal_max_session_time      end      put "application/settings" do        if current_settings.update_attributes(declared_params(include_missing: false)) diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb new file mode 100644 index 00000000000..bac7d485a22 --- /dev/null +++ b/lib/api/v3/projects.rb @@ -0,0 +1,458 @@ +module API +  module V3 +    class Projects < Grape::API +      include PaginationParams + +      before { authenticate_non_get! } + +      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 +        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: %w[public internal private], +                                  desc: 'Limit by visibility' +            optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' +          end + +          params :statistics_params do +            optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' +          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 +        end +        get '/visible' do +          entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails +          present_projects ProjectsFinder.new.execute(current_user), with: entity +        end + +        desc 'Get a projects list for authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +        end +        get do +          authenticate! + +          present_projects current_user.authorized_projects, +            with: Entities::ProjectWithAccess +        end + +        desc 'Get an owned projects list for authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +          use :statistics_params +        end +        get '/owned' do +          authenticate! + +          present_projects current_user.owned_projects, +            with: Entities::ProjectWithAccess, +            statistics: params[:statistics] +        end + +        desc 'Gets starred project for the authenticated user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +        end +        get '/starred' do +          authenticate! + +          present_projects current_user.viewable_starred_projects +        end + +        desc 'Get all projects for admin user' do +          success Entities::BasicProjectDetails +        end +        params do +          use :collection_params +          use :statistics_params +        end +        get '/all' do +          authenticated_as_admin! + +          present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics] +        end + +        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 +        end +        get "/search/:query", requirements: { 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]) + +          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 +        post do +          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) +          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 = 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) +          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: /[^\/]+/ } do +        desc 'Get a single project' do +          success Entities::ProjectWithAccess +        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) +        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 'fork/:id' 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 +          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 +        put ':id' do +          authorize_admin_project +          attrs = map_public_to_visibility_level(declared_params(include_missing: false)) +          authorize! :rename_project, user_project if attrs[:name].present? +          authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? + +          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 +        delete ':id/star' 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 +        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 +          no_content! +        end + +        desc 'Upload a file' +        params do +          requires :file, type: File, desc: 'The file to be uploaded' +        end +        post ":id/uploads" do +          ::Projects::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 +      end +    end +  end +end | 
