diff options
Diffstat (limited to 'lib/api')
-rw-r--r-- | lib/api/api.rb | 7 | ||||
-rw-r--r-- | lib/api/boards.rb | 2 | ||||
-rw-r--r-- | lib/api/branches.rb | 7 | ||||
-rw-r--r-- | lib/api/builds.rb | 2 | ||||
-rw-r--r-- | lib/api/commits.rb | 4 | ||||
-rw-r--r-- | lib/api/entities.rb | 2 | ||||
-rw-r--r-- | lib/api/files.rb | 2 | ||||
-rw-r--r-- | lib/api/helpers.rb | 4 | ||||
-rw-r--r-- | lib/api/helpers/internal_helpers.rb | 4 | ||||
-rw-r--r-- | lib/api/project_snippets.rb | 2 | ||||
-rw-r--r-- | lib/api/projects.rb | 16 | ||||
-rw-r--r-- | lib/api/services.rb | 13 | ||||
-rw-r--r-- | lib/api/settings.rb | 3 | ||||
-rw-r--r-- | lib/api/snippets.rb | 2 | ||||
-rw-r--r-- | lib/api/users.rb | 2 | ||||
-rw-r--r-- | lib/api/v3/projects.rb | 458 |
16 files changed, 491 insertions, 39 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/boards.rb b/lib/api/boards.rb index 4ac491edc1b..13752eb4947 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -37,7 +37,7 @@ module API end desc 'Get the lists of a project board' do - detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13' + detail 'Does not include `done` list. This feature was introduced in 8.13' success Entities::List end get '/lists' do diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 0950c3d2e88..be659fa4a6a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -129,12 +129,7 @@ module API end end - # Delete all merged branches - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/repository/branches/delete_merged + desc 'Delete all merged branches' delete ":id/repository/merged_branches" do DeleteMergedBranchesService.new(user_project, current_user).async_execute diff --git a/lib/api/builds.rb b/lib/api/builds.rb index af61be343be..44fe0fc4a95 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -209,7 +209,7 @@ module API build = get_build!(params[:build_id]) - bad_request!("Unplayable Build") unless build.playable? + bad_request!("Unplayable Job") unless build.playable? build.play(current_user) diff --git a/lib/api/commits.rb b/lib/api/commits.rb index e6d707f3c3d..2fefe760d24 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -54,7 +54,7 @@ module API authorize! :push_code, user_project attrs = declared_params - attrs[:source_branch] = attrs[:branch_name] + attrs[:start_branch] = attrs[:branch_name] attrs[:target_branch] = attrs[:branch_name] attrs[:actions].map! do |action| action[:action] = action[:action].to_sym @@ -139,8 +139,6 @@ module API commit_params = { commit: commit, create_merge_request: false, - source_project: user_project, - source_branch: commit.cherry_pick_branch_name, target_branch: params[:branch] } diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9f59939e9ae..b1ead48caf7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -137,6 +137,7 @@ module API expose :avatar_url expose :web_url expose :request_access_enabled + expose :full_name, :full_path expose :statistics, if: :statistics do with_options format_with: -> (value) { value.to_i } do @@ -574,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/files.rb b/lib/api/files.rb index 2e79e22e649..c58472de578 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -5,7 +5,7 @@ module API def commit_params(attrs) { file_path: attrs[:file_path], - source_branch: attrs[:branch_name], + start_branch: attrs[:branch_name], target_branch: attrs[:branch_name], commit_message: attrs[:commit_message], file_content: attrs[:content], diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a1d7b323f4f..dfab60f7fa5 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -45,7 +45,7 @@ module API if id =~ /^\d+$/ Project.find_by(id: id) else - Project.find_with_namespace(id) + Project.find_by_full_path(id) end end @@ -304,7 +304,7 @@ module API header['X-Sendfile'] = path body else - path + file path end end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index e8975eb57e0..080a6274957 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -30,7 +30,7 @@ module API def wiki? @wiki ||= project_path.end_with?('.wiki') && - !Project.find_with_namespace(project_path) + !Project.find_by_full_path(project_path) end def project @@ -41,7 +41,7 @@ module API # the wiki repository as well. project_path.chomp!('.wiki') if wiki? - Project.find_with_namespace(project_path) + Project.find_by_full_path(project_path) end end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 9d8c5b63685..dcc0c82ee27 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -58,7 +58,7 @@ module API end post ":id/snippets" do authorize! :create_project_snippet, user_project - snippet_params = declared_params + snippet_params = declared_params.merge(request: request, api: true) snippet_params[:content] = snippet_params.delete(:code) snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute 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/services.rb b/lib/api/services.rb index a0abec49438..1456fe4688b 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -661,6 +661,14 @@ module API end trigger_services.each do |service_slug, settings| + helpers do + def chat_command_service(project, service_slug, params) + project.services.active.where(template: false).find do |service| + service.try(:token) == params[:token] && service.to_param == service_slug.underscore + end + end + end + params do requires :id, type: String, desc: 'The ID of a project' end @@ -679,9 +687,8 @@ module API # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project - service = project.find_or_initialize_service(service_slug.underscore) - - result = service.try(:active?) && service.try(:trigger, params) + service = chat_command_service(project, service_slug, params) + result = service.try(:trigger, params) if result status result[:status] || 200 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/snippets.rb b/lib/api/snippets.rb index e096e636806..eb9ece49e7f 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -64,7 +64,7 @@ module API desc: 'The visibility level of the snippet' end post do - attrs = declared_params(include_missing: false) + attrs = declared_params(include_missing: false).merge(request: request, api: true) snippet = CreateSnippetService.new(nil, current_user, attrs).execute if snippet.persisted? diff --git a/lib/api/users.rb b/lib/api/users.rb index 11a7368b4c0..0ed468626b7 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -160,6 +160,8 @@ module API end end + user_params.merge!(password_expires_at: Time.now) if user_params[:password].present? + if user.update_attributes(user_params.except(:extern_uid, :provider)) present user, with: Entities::UserPublic else 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 |