gitlab-org--gitlab-foss/lib/api/projects.rb

487 lines
18 KiB
Ruby
Raw Normal View History

require_dependency 'declarative_policy'
module API
2012-06-29 06:46:01 -04:00
class Projects < Grape::API
2016-11-09 09:51:27 -05:00
include PaginationParams
include Helpers::CustomAttributes
2016-11-09 09:51:27 -05:00
before { authenticate_non_get! }
2012-06-29 06:46:01 -04:00
2016-11-09 09:51:27 -05:00
helpers do
2017-04-05 12:31:15 -04:00
params :optional_params_ce do
2016-11-09 09:51:27 -05:00
optional :description, type: String, desc: 'The description of the project'
optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
2016-11-09 09:51:27 -05:00
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'
2016-11-09 09:51:27 -05:00
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 :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
2016-11-09 09:51:27 -05:00
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.'
2016-11-09 09:51:27 -05:00
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'
2016-11-09 09:51:27 -05:00
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
2017-05-30 13:41:43 -04:00
optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
2017-06-09 15:49:57 -04:00
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'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
2016-11-09 09:51:27 -05:00
end
2017-04-05 12:31:15 -04:00
params :optional_params do
use :optional_params_ce
end
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
2017-06-29 13:20:59 -04:00
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 load_projects
ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
end
def present_projects(projects, options = {})
2017-06-29 13:20:59 -04:00
projects = reorder_projects(projects)
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
2017-06-29 13:20:59 -04:00
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
projects = projects.with_statistics if params[:statistics]
projects = paginate(projects)
projects, options = with_custom_attributes(projects, options)
2017-06-29 13:20:59 -04:00
if current_user
project_members = current_user.project_members.preload(:source, user: [notification_settings: :source])
group_members = current_user.group_members.preload(:source, user: [notification_settings: :source])
end
2017-06-29 13:20:59 -04:00
options = options.reverse_merge(
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
statistics: params[:statistics],
2017-11-30 07:13:00 -05:00
project_members: project_members,
group_members: group_members,
2017-06-29 13:20:59 -04:00
current_user: current_user
)
options[:with] = Entities::BasicProjectDetails if params[:simple]
present options[:with].prepare_relation(projects, options), options
2017-06-29 13:20:59 -04:00
end
2016-11-09 09:51:27 -05:00
end
2012-06-29 06:46:01 -04:00
2017-08-31 07:44:49 -04:00
resource :users, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
2017-06-29 13:20:59 -04:00
desc 'Get a user projects' do
success Entities::BasicProjectDetails
end
params do
requires :user_id, type: String, desc: 'The ID or username of the user'
use :collection_params
use :statistics_params
use :with_custom_attributes
end
2017-06-29 13:20:59 -04:00
get ":user_id/projects" do
user = find_user(params[:user_id])
not_found!('User') unless user
2017-06-29 13:20:59 -04:00
params[:user] = user
present_projects load_projects
2017-06-29 13:20:59 -04:00
end
end
resource :projects do
2017-09-18 09:03:24 -04:00
include CustomAttributesEndpoints
desc 'Get a list of visible projects for authenticated user' do
2016-11-09 09:51:27 -05:00
success Entities::BasicProjectDetails
end
params do
use :collection_params
use :statistics_params
use :with_custom_attributes
2016-11-09 09:51:27 -05:00
end
2012-06-29 06:46:01 -04:00
get do
present_projects load_projects
end
2016-11-09 09:51:27 -05:00
desc 'Create new project' do
success Entities::Project
end
params do
optional :name, type: String, desc: 'The name of the project'
2016-11-09 09:51:27 -05:00
optional :path, type: String, desc: 'The path of the repository'
at_least_one_of :name, :path
2016-11-09 09:51:27 -05:00
use :optional_params
use :create_params
end
2012-08-31 03:15:37 -04:00
post do
attrs = declared_params(include_missing: false)
attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled)
2016-11-09 09:51:27 -05:00
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)
2012-08-31 03:15:37 -04:00
else
2016-11-09 09:51:27 -05:00
if project.errors[:limit_reached].present?
error!(project.errors[:limit_reached], 403)
end
2016-11-09 09:51:27 -05:00
render_validation_error!(project)
2012-08-31 03:15:37 -04:00
end
end
2016-11-09 09:51:27 -05:00
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'
2016-11-09 09:51:27 -05:00
optional :default_branch, type: String, desc: 'The default branch of the project'
use :optional_params
use :create_params
end
2012-11-14 15:37:52 -05:00
post "user/:user_id" do
authenticated_as_admin!
2016-11-09 09:51:27 -05:00
user = User.find_by(id: params.delete(:user_id))
not_found!('User') unless user
attrs = declared_params(include_missing: false)
2016-11-09 09:51:27 -05:00
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)
2012-11-14 15:37:52 -05:00
else
2016-11-09 09:51:27 -05:00
render_validation_error!(project)
2012-11-14 15:37:52 -05:00
end
end
2016-11-09 09:51:27 -05:00
end
2012-11-14 15:37:52 -05:00
2016-11-09 09:51:27 -05:00
params do
requires :id, type: String, desc: 'The ID of a project'
end
2017-08-31 07:44:49 -04:00
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
2016-11-09 09:51:27 -05:00
desc 'Get a single project' do
success Entities::ProjectWithAccess
end
params do
use :statistics_params
use :with_custom_attributes
end
2016-11-09 09:51:27 -05:00
get ":id" do
options = {
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user,
user_can_admin_project: can?(current_user, :admin_project, user_project),
statistics: params[:statistics]
}
project, options = with_custom_attributes(user_project, options)
present project, options
2016-11-09 09:51:27 -05:00
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
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
2016-11-09 09:51:27 -05:00
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
if namespace_id.present?
fork_params[:namespace] = find_namespace(namespace_id)
2016-11-09 09:51:27 -05:00
unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
not_found!('Target Namespace')
end
end
2016-11-09 09:51:27 -05:00
forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
2016-11-09 09:51:27 -05:00
if forked_project.errors.any?
conflict!(forked_project.errors.messages)
else
2016-11-09 09:51:27 -05:00
present forked_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, forked_project)
end
end
desc 'List forks of this project' do
success Entities::Project
end
params do
use :collection_params
use :with_custom_attributes
end
get ':id/forks' do
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
present_projects forks
end
2016-11-09 09:51:27 -05:00
desc 'Update an existing project' do
success Entities::Project
end
params do
2017-04-05 12:31:15 -04:00
# CE
at_least_one_of_ce =
[
:jobs_enabled,
:resolve_outdated_diff_discussions,
:ci_config_path,
2017-04-05 12:31:15 -04:00
:container_registry_enabled,
:default_branch,
:description,
:issues_enabled,
:lfs_enabled,
:merge_requests_enabled,
:merge_method,
2017-04-05 12:31:15 -04:00
:name,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds,
:path,
:printing_merge_request_link_enabled,
2017-04-05 12:31:15 -04:00
:public_builds,
:request_access_enabled,
:shared_runners_enabled,
:snippets_enabled,
2017-05-30 13:41:43 -04:00
:tag_list,
2017-04-05 12:31:15 -04:00
:visibility,
:wiki_enabled
2017-04-05 12:31:15 -04:00
]
2016-11-09 09:51:27 -05:00
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'
2017-04-05 12:31:15 -04:00
2016-11-09 09:51:27 -05:00
use :optional_params
2017-04-05 12:31:15 -04:00
at_least_one_of(*at_least_one_of_ce)
2016-11-09 09:51:27 -05:00
end
2014-10-15 02:57:35 -04:00
put ':id' do
authorize_admin_project
attrs = declared_params(include_missing: false)
2014-10-15 02:57:35 -04:00
authorize! :rename_project, user_project if attrs[:name].present?
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
2014-10-15 02:57:35 -04:00
attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled)
2014-10-15 02:57:35 -04:00
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
2014-10-15 02:57:35 -04:00
if result[:status] == :success
2016-01-11 09:27:20 -05:00
present user_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, user_project)
else
render_validation_error!(user_project)
2014-10-15 02:57:35 -04:00
end
end
2016-11-09 09:51:27 -05:00
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
2016-11-09 09:51:27 -05:00
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
2016-11-09 09:51:27 -05:00
desc 'Star a project' do
success Entities::Project
end
2016-04-06 09:52:16 -04:00
post ':id/star' do
2016-04-12 12:52:43 -04:00
if current_user.starred?(user_project)
not_modified!
else
2016-04-06 09:52:16 -04:00
current_user.toggle_star(user_project)
user_project.reload
2016-04-13 06:50:00 -04:00
2016-04-06 09:52:16 -04:00
present user_project, with: Entities::Project
end
end
2016-11-09 09:51:27 -05:00
desc 'Unstar a project' do
success Entities::Project
end
post ':id/unstar' do
2016-04-06 09:52:16 -04:00
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reload
2016-04-13 06:50:00 -04:00
2016-04-06 09:52:16 -04:00
present user_project, with: Entities::Project
else
not_modified!
end
end
2016-11-09 09:51:27 -05:00
desc 'Remove a project'
2013-10-09 07:41:41 -04:00
delete ":id" do
authorize! :remove_project, user_project
2017-03-02 07:14:13 -05:00
destroy_conditionally!(user_project) do
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
end
accepted!
2013-10-09 07:41:41 -04:00
end
2012-11-14 15:37:52 -05:00
2016-11-09 09:51:27 -05:00
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!
2016-11-09 09:51:27 -05:00
fork_from_project = find_project!(params[:forked_from_id])
2016-11-09 09:51:27 -05:00
not_found!("Source Project") unless fork_from_project
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
if result
present user_project.reload, with: Entities::Project
else
render_api_error!("Project already forked", 409) if user_project.forked?
end
end
2016-11-09 09:51:27 -05:00
desc 'Remove a forked_from relationship'
delete ":id/fork" do
authorize! :remove_fork_project, user_project
2016-11-09 09:51:27 -05:00
result = destroy_conditionally!(user_project) do
::Projects::UnlinkForkService.new(user_project, current_user).execute
end
result ? status(204) : not_modified!
end
2016-01-07 07:37:14 -05:00
2016-11-09 09:51:27 -05:00
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
2016-11-09 09:51:27 -05:00
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
2016-11-09 09:51:27 -05:00
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'
2016-01-07 07:37:14 -05:00
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
2016-01-07 07:37:14 -05:00
destroy_conditionally!(link)
end
2016-11-09 09:51:27 -05:00
desc 'Upload a file'
params do
requires :file, type: File, desc: 'The file to be uploaded'
end
2016-01-07 07:37:14 -05:00
post ":id/uploads" do
UploadService.new(user_project, params[:file]).execute
end
2016-11-09 09:51:27 -05:00
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 = DeclarativePolicy.subject_scope { user_project.team.users }
2016-11-09 09:51:27 -05:00
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
2012-06-29 06:46:01 -04:00
end
end
end