summaryrefslogtreecommitdiff
path: root/lib/api/projects.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api/projects.rb')
-rw-r--r--lib/api/projects.rb649
1 files changed, 297 insertions, 352 deletions
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 291e7b689bf..20707e97e53 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,311 +1,262 @@
module API
# Projects API
class Projects < Grape::API
- before { authenticate! }
+ include PaginationParams
+
+ before { authenticate_non_get! }
+
+ helpers do
+ params :optional_params_ce do
+ optional :description, type: String, desc: 'The description of the project'
+ optional :ci_config_file, type: String, desc: 'The path to CI config file. Default to `.gitlab-ci.yml`'
+ 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'
+ optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
+ optional :avatar, type: File, desc: 'Avatar image for project'
+ optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
+ 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, requirements: { id: /[^\/]+/ } do
+ resource :projects do
helpers do
- def map_public_to_visibility_level(attrs)
- publik = attrs.delete(:public)
- if publik.present? && !attrs[:visibility_level].present?
- publik = to_boolean(publik)
- # 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
+ 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'
+ optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
+ optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
+ 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(options = {})
+ projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
+ projects = projects.with_statistics if params[:statistics]
+ projects = projects.with_issues_enabled if params[:with_issues_enabled]
+ projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+
+ options = options.reverse_merge(
+ with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
+ statistics: params[:statistics],
+ current_user: current_user
+ )
+ options[:with] = Entities::BasicProjectDetails if params[:simple]
+
+ present paginate(projects), options
end
end
- # Get a projects list for authenticated user
- #
- # Example Request:
- # GET /projects
+ 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
- projects = current_user.authorized_projects
- projects = filter_projects(projects)
- projects = paginate projects
- entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
-
- present projects, with: entity, user: current_user
- end
-
- # Get a list of visible projects for authenticated user
- #
- # Example Request:
- # GET /projects/visible
- get '/visible' do
- projects = ProjectsFinder.new.execute(current_user)
- projects = filter_projects(projects)
- projects = paginate projects
- entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
-
- present projects, with: entity, user: current_user
- end
-
- # Get an owned projects list for authenticated user
- #
- # Example Request:
- # GET /projects/owned
- get '/owned' do
- projects = current_user.owned_projects
- projects = filter_projects(projects)
- projects = paginate projects
- present projects, with: Entities::ProjectWithAccess, user: current_user
- end
-
- # Gets starred project for the authenticated user
- #
- # Example Request:
- # GET /projects/starred
- get '/starred' do
- projects = current_user.viewable_starred_projects
- projects = filter_projects(projects)
- projects = paginate projects
- present projects, with: Entities::Project, user: current_user
- end
-
- # Get all projects for admin user
- #
- # Example Request:
- # GET /projects/all
- get '/all' do
- authenticated_as_admin!
- projects = Project.all
- projects = filter_projects(projects)
- projects = paginate projects
- present projects, with: Entities::ProjectWithAccess, user: current_user
- end
-
- # Get a single project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id
- get ":id" do
- present user_project, with: Entities::ProjectWithAccess, user: current_user,
- user_can_admin_project: can?(current_user, :admin_project, user_project)
- end
-
- # Get events for a single project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/events
- get ":id/events" do
- events = paginate user_project.events.recent
- present events, with: Entities::Event
- end
-
- # Create new project
- #
- # Parameters:
- # name (required) - name for new project
- # description (optional) - short project description
- # issues_enabled (optional)
- # merge_requests_enabled (optional)
- # builds_enabled (optional)
- # wiki_enabled (optional)
- # snippets_enabled (optional)
- # container_registry_enabled (optional)
- # shared_runners_enabled (optional)
- # namespace_id (optional) - defaults to user namespace
- # public (optional) - if true same as setting visibility_level = 20
- # visibility_level (optional) - 0 by default
- # import_url (optional)
- # public_builds (optional)
- # lfs_enabled (optional)
- # request_access_enabled (optional) - Allow users to request member access
- # ci_config_file (optional)
- # Example Request
- # POST /projects
+ present_projects
+ 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
- required_attributes! [:name]
- attrs = attributes_for_keys [:builds_enabled,
- :container_registry_enabled,
- :ci_config_file,
- :description,
- :import_url,
- :issues_enabled,
- :lfs_enabled,
- :merge_requests_enabled,
- :name,
- :namespace_id,
- :only_allow_merge_if_build_succeeds,
- :path,
- :public,
- :public_builds,
- :request_access_enabled,
- :shared_runners_enabled,
- :snippets_enabled,
- :visibility_level,
- :wiki_enabled]
- attrs = map_public_to_visibility_level(attrs)
- @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)
+ attrs = declared_params(include_missing: false)
+ attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.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)
+ if project.errors[:limit_reached].present?
+ error!(project.errors[:limit_reached], 403)
end
- render_validation_error!(@project)
+ render_validation_error!(project)
end
end
- # Create new project for a specified user. Only available to admin users.
- #
- # Parameters:
- # user_id (required) - The ID of a user
- # name (required) - name for new project
- # description (optional) - short project description
- # default_branch (optional) - 'master' by default
- # issues_enabled (optional)
- # merge_requests_enabled (optional)
- # builds_enabled (optional)
- # wiki_enabled (optional)
- # snippets_enabled (optional)
- # container_registry_enabled (optional)
- # shared_runners_enabled (optional)
- # public (optional) - if true same as setting visibility_level = 20
- # visibility_level (optional)
- # import_url (optional)
- # public_builds (optional)
- # lfs_enabled (optional)
- # request_access_enabled (optional) - Allow users to request member access
- # ci_config_file (optional)
- # Example Request
- # POST /projects/user/:user_id
+ 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 :path, type: String, desc: 'The path of the repository'
+ 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(params[:user_id])
- attrs = attributes_for_keys [:builds_enabled,
- :ci_config_file,
- :default_branch,
- :description,
- :import_url,
- :issues_enabled,
- :lfs_enabled,
- :merge_requests_enabled,
- :name,
- :only_allow_merge_if_build_succeeds,
- :public,
- :public_builds,
- :request_access_enabled,
- :shared_runners_enabled,
- :snippets_enabled,
- :visibility_level,
- :wiki_enabled]
- attrs = map_public_to_visibility_level(attrs)
- @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)
+ 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)
+ render_validation_error!(project)
end
end
+ end
- # Fork new project for the current user or provided namespace.
- #
- # Parameters:
- # id (required) - The ID of a project
- # namespace (optional) - The ID or name of the namespace that the project will be forked into.
- # Example Request
- # POST /projects/fork/:id
- post 'fork/:id' do
- attrs = {}
- namespace_id = params[:namespace]
+ 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 '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?
- namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id)
+ fork_params[:namespace] = if namespace_id =~ /^\d+$/
+ Namespace.find_by(id: namespace_id)
+ else
+ Namespace.find_by_path_or_name(namespace_id)
+ end
- unless namespace && can?(current_user, :create_projects, namespace)
+ unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
not_found!('Target Namespace')
end
-
- attrs[:namespace] = namespace
end
- @forked_project =
- ::Projects::ForkService.new(user_project,
- current_user,
- attrs).execute
+ forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
- if @forked_project.errors.any?
- conflict!(@forked_project.errors.messages)
+ 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)
+ present forked_project, with: Entities::Project,
+ user_can_admin_project: can?(current_user, :admin_project, forked_project)
end
end
- # Update an existing project
- #
- # Parameters:
- # id (required) - the id of a project
- # name (optional) - name of a project
- # path (optional) - path of a project
- # description (optional) - short project description
- # issues_enabled (optional)
- # merge_requests_enabled (optional)
- # builds_enabled (optional)
- # wiki_enabled (optional)
- # snippets_enabled (optional)
- # container_registry_enabled (optional)
- # shared_runners_enabled (optional)
- # public (optional) - if true same as setting visibility_level = 20
- # visibility_level (optional) - visibility level of a project
- # public_builds (optional)
- # lfs_enabled (optional)
- # ci_config_file (optional)
- # Example Request
- # PUT /projects/:id
+ 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,
+ :printing_merge_request_link_enabled,
+ :public_builds,
+ :request_access_enabled,
+ :shared_runners_enabled,
+ :snippets_enabled,
+ :tag_list,
+ :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
- attrs = attributes_for_keys [:builds_enabled,
- :container_registry_enabled,
- :ci_config_file,
- :default_branch,
- :description,
- :issues_enabled,
- :lfs_enabled,
- :merge_requests_enabled,
- :name,
- :only_allow_merge_if_build_succeeds,
- :path,
- :public,
- :public_builds,
- :request_access_enabled,
- :shared_runners_enabled,
- :snippets_enabled,
- :visibility_level,
- :wiki_enabled]
- attrs = map_public_to_visibility_level(attrs)
authorize_admin_project
+ attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
- if attrs[:visibility_level].present?
- authorize! :change_visibility_level, user_project
- end
+ authorize! :change_visibility_level, user_project if attrs[:visibility].present?
- ::Projects::UpdateService.new(user_project,
- current_user, attrs).execute
+ attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled)
- if user_project.errors.any?
- render_validation_error!(user_project)
- else
+ 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
- # Archive project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # PUT /projects/:id/archive
+ desc 'Archive a project' do
+ success Entities::Project
+ end
post ':id/archive' do
authorize!(:archive_project, user_project)
@@ -314,12 +265,9 @@ module API
present user_project, with: Entities::Project
end
- # Unarchive project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # PUT /projects/:id/unarchive
+ desc 'Unarchive a project' do
+ success Entities::Project
+ end
post ':id/unarchive' do
authorize!(:archive_project, user_project)
@@ -328,12 +276,9 @@ module API
present user_project, with: Entities::Project
end
- # Star project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # POST /projects/:id/star
+ desc 'Star a project' do
+ success Entities::Project
+ end
post ':id/star' do
if current_user.starred?(user_project)
not_modified!
@@ -345,13 +290,10 @@ module API
end
end
- # Unstar project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # DELETE /projects/:id/star
- delete ':id/star' do
+ 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
@@ -362,71 +304,63 @@ module API
end
end
- # Remove project
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # DELETE /projects/:id
+ desc 'Remove a project'
delete ":id" do
authorize! :remove_project, user_project
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
+
+ accepted!
end
- # Mark this project as forked from another
- #
- # Parameters:
- # id: (required) - The ID of the project being marked as a fork
- # forked_from_id: (required) - The ID of the project it was forked from
- # Example Request:
- # POST /projects/:id/fork/:forked_from_id
+ 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])
- unless forked_from_project.nil?
- 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
+
+ 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
- not_found!("Source Project")
+ render_api_error!("Project already forked", 409)
end
end
- # Remove a forked_from relationship
- #
- # Parameters:
- # id: (required) - The ID of the project being marked as a fork
- # Example Request:
- # DELETE /projects/:id/fork
+ 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
- # Share project with group
- #
- # Parameters:
- # id (required) - The ID of a project
- # group_id (required) - The ID of a group
- # group_access (required) - Level of permissions for sharing
- # expires_at (optional) - Share expiration date
- #
- # Example Request:
- # POST /projects/:id/share
+ 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
- required_attributes! [:group_id, :group_access]
- attrs = attributes_for_keys [:group_id, :group_access, :expires_at]
+ 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(attrs)
+ link = user_project.project_group_links.new(declared_params(include_missing: false))
if link.save
present link, with: Entities::ProjectGroupLink
@@ -435,40 +369,51 @@ module API
end
end
- # Upload a file
- #
- # Parameters:
- # id: (required) - The ID of the project
- # file: (required) - The file to be uploaded
- post ":id/uploads" do
- ::Projects::UploadService.new(user_project, params[:file]).execute
+ 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
- # search for projects current_user has access to
- #
- # Parameters:
- # query (required) - A string contained in the project name
- # per_page (optional) - number of projects to return per page
- # page (optional) - the page to retrieve
- # Example Request:
- # GET /projects/search/:query
- 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(project_order_by => project_sort)
+ link.destroy
+ end
- present paginate(projects), with: Entities::Project
+ 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
- # Get a users list
- #
- # Example Request:
- # GET /users
+ 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.where(id: user_project.team.users.map(&:id))
- @users = @users.search(params[:search]) if params[:search].present?
- @users = paginate @users
- present @users, with: Entities::UserBasic
+ 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