diff options
author | Kamil Trzciński <ayufan@ayufan.eu> | 2018-02-28 20:03:02 +0100 |
---|---|---|
committer | Kamil Trzciński <ayufan@ayufan.eu> | 2018-02-28 20:03:02 +0100 |
commit | b1f8d8a1739ff48412c8205f0007a2af8399d097 (patch) | |
tree | f7d35d158e7c9bdda6c282f916e02fe9a0d4df90 /lib/api | |
parent | 52c3b8f31264230814d2ffa79d0987c1491676b3 (diff) | |
parent | 5b08d59f07fc53c1e34819fac20352119d5343e6 (diff) | |
download | gitlab-ce-b1f8d8a1739ff48412c8205f0007a2af8399d097.tar.gz |
Merge commit '5b08d59f07fc53c1e34819fac20352119d5343e6' into object-storage-ee-to-ce-backport
Diffstat (limited to 'lib/api')
45 files changed, 940 insertions, 381 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index c9b5f58c557..cdacf9839e5 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -68,6 +68,7 @@ module API delete ":id/access_requests/:user_id" do source = find_source(source_type, params[:id]) + status 204 ::Members::DestroyService.new(source, current_user, params) .execute(:requesters) end diff --git a/lib/api/api.rb b/lib/api/api.rb index efcf0976a81..94df543853b 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -3,6 +3,7 @@ module API include APIGuard allow_access_with_scope :api + prefix :api version %w(v3 v4), using: :path @@ -47,8 +48,8 @@ module API end before { header['X-Frame-Options'] = 'SAMEORIGIN' } - before { Gitlab::I18n.locale = current_user&.preferred_language } + # The locale is set to the current user's locale when `current_user` is loaded after { Gitlab::I18n.use_default_locale } rescue_from Gitlab::Access::AccessDeniedError do @@ -85,12 +86,16 @@ module API helpers ::API::Helpers helpers ::API::Helpers::CommonHelpers + NO_SLASH_URL_PART_REGEX = %r{[^/]+} + PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze + # Keep in alphabetical order mount ::API::AccessRequests mount ::API::AwardEmoji mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages + mount ::API::CircuitBreakers mount ::API::Commits mount ::API::CommitStatuses mount ::API::DeployKeys @@ -109,7 +114,8 @@ module API mount ::API::Members mount ::API::MergeRequestDiffs mount ::API::MergeRequests - mount ::API::Milestones + mount ::API::ProjectMilestones + mount ::API::GroupMilestones mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings @@ -118,6 +124,7 @@ module API mount ::API::ProjectHooks mount ::API::Projects mount ::API::ProjectSnippets + mount ::API::ProtectedBranches mount ::API::Repositories mount ::API::Runner mount ::API::Runners @@ -134,6 +141,7 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::GroupVariables mount ::API::Version route :any, '*path' do diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index 56f19f89642..5a028fc9d0b 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -88,6 +88,7 @@ module API unauthorized! unless award.user == current_user || current_user.admin? + status 204 award.destroy end end diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 3d816f8771d..d3dbf941298 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -4,19 +4,21 @@ module API class Branches < Grape::API include PaginationParams + BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX) + before { authorize! :download_code, user_project } params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a project repository branches' do success Entities::RepoBranch end params do use :pagination end - get ":id/repository/branches" do + get ':id/repository/branches' do branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name)) present paginate(branches), with: Entities::RepoBranch, project: user_project @@ -28,13 +30,14 @@ module API params do requires :branch, type: String, desc: 'The name of the branch' end - get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do + get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do branch = user_project.repository.find_branch(params[:branch]) not_found!("Branch") unless branch present branch, with: Entities::RepoBranch, project: user_project end + # Note: This API will be deprecated in favor of the protected branches API. # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}` # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility), # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`. @@ -46,7 +49,7 @@ module API optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch' optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch' end - put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do + put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do authorize_admin_project branch = user_project.repository.find_branch(params[:branch]) @@ -63,9 +66,9 @@ module API service_args = [user_project, current_user, protected_branch_params] protected_branch = if protected_branch - ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch) + ::ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch) else - ProtectedBranches::ApiCreateService.new(*service_args).execute + ::ProtectedBranches::ApiCreateService.new(*service_args).execute end if protected_branch.valid? @@ -75,13 +78,14 @@ module API end end + # Note: This API will be deprecated in favor of the protected branches API. desc 'Unprotect a single branch' do success Entities::RepoBranch end params do requires :branch, type: String, desc: 'The name of the branch' end - put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do + put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do authorize_admin_project branch = user_project.repository.find_branch(params[:branch]) @@ -99,7 +103,7 @@ module API requires :branch, type: String, desc: 'The name of the branch' requires :ref, type: String, desc: 'Create branch from commit sha or existing branch' end - post ":id/repository/branches" do + post ':id/repository/branches' do authorize_push_project result = CreateBranchService.new(user_project, current_user) @@ -118,7 +122,7 @@ module API params do requires :branch, type: String, desc: 'The name of the branch' end - delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do + delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do authorize_push_project result = DeleteBranchService.new(user_project, current_user) @@ -130,7 +134,7 @@ module API end desc 'Delete all merged branches' - delete ":id/repository/merged_branches" do + delete ':id/repository/merged_branches' do DeleteMergedBranchesService.new(user_project, current_user).async_execute accepted! diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index 395c401203c..9980aec4752 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -91,6 +91,7 @@ module API delete ':id' do message = find_message + status 204 message.destroy end end diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb new file mode 100644 index 00000000000..118883f5ea5 --- /dev/null +++ b/lib/api/circuit_breakers.rb @@ -0,0 +1,50 @@ +module API + class CircuitBreakers < Grape::API + before { authenticated_as_admin! } + + resource :circuit_breakers do + params do + requires :type, + type: String, + desc: "The type of circuitbreaker", + values: ['repository_storage'] + end + resource ':type' do + namespace '', requirements: { type: 'repository_storage' } do + helpers do + def failing_storage_health + @failing_storage_health ||= Gitlab::Git::Storage::Health.for_failing_storages + end + + def storage_health + @failing_storage_health ||= Gitlab::Git::Storage::Health.for_all_storages + end + end + + desc 'Get all failing git storages' do + detail 'This feature was introduced in GitLab 9.5' + success Entities::RepositoryStorageHealth + end + get do + present storage_health, with: Entities::RepositoryStorageHealth + end + + desc 'Get all failing git storages' do + detail 'This feature was introduced in GitLab 9.5' + success Entities::RepositoryStorageHealth + end + get 'failing' do + present failing_storage_health, with: Entities::RepositoryStorageHealth + end + + desc 'Reset all storage failures and open circuitbreaker' do + detail 'This feature was introduced in GitLab 9.5' + end + delete do + Gitlab::Git::Storage::CircuitBreaker.reset_all! + end + end + end + end + end +end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index bcb842b9211..ea78737288a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -4,13 +4,14 @@ module API class Commits < Grape::API include PaginationParams - before { authenticate! } + COMMIT_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(sha: API::NO_SLASH_URL_PART_REGEX) + before { authorize! :download_code, user_project } params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a project repository commits' do success Entities::RepoCommit end @@ -21,7 +22,7 @@ module API optional :path, type: String, desc: 'The file path' use :pagination end - get ":id/repository/commits" do + get ':id/repository/commits' do path = params[:path] before = params[:until] after = params[:since] @@ -53,16 +54,19 @@ module API detail 'This feature was introduced in GitLab 8.13' end params do - requires :branch, type: String, desc: 'The name of branch' + requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.' requires :commit_message, type: String, desc: 'Commit message' requires :actions, type: Array[Hash], desc: 'Actions to perform in commit' + optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' end - post ":id/repository/commits" do + post ':id/repository/commits' do authorize! :push_code, user_project - attrs = declared_params.merge(start_branch: declared_params[:branch], branch_name: declared_params[:branch]) + attrs = declared_params + attrs[:branch_name] = attrs.delete(:branch) + attrs[:start_branch] ||= attrs[:branch_name] result = ::Files::MultiService.new(user_project, current_user, attrs).execute @@ -76,42 +80,42 @@ module API desc 'Get a specific commit of a project' do success Entities::RepoCommitDetail - failure [[404, 'Not Found']] + failure [[404, 'Commit Not Found']] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' end - get ":id/repository/commits/:sha" do + get ':id/repository/commits/:sha', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) - not_found! "Commit" unless commit + not_found! 'Commit' unless commit present commit, with: Entities::RepoCommitDetail end desc 'Get the diff for a specific commit of a project' do - failure [[404, 'Not Found']] + failure [[404, 'Commit Not Found']] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' end - get ":id/repository/commits/:sha/diff" do + get ':id/repository/commits/:sha/diff', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) - not_found! "Commit" unless commit + not_found! 'Commit' unless commit commit.raw_diffs.to_a end desc "Get a commit's comments" do success Entities::CommitNote - failure [[404, 'Not Found']] + failure [[404, 'Commit Not Found']] end params do use :pagination requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' end - get ':id/repository/commits/:sha/comments' do + get ':id/repository/commits/:sha/comments', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -125,10 +129,10 @@ module API success Entities::RepoCommit end params do - requires :sha, type: String, desc: 'A commit sha to be cherry picked' + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked' requires :branch, type: String, desc: 'The name of the branch' end - post ':id/repository/commits/:sha/cherry_pick' do + post ':id/repository/commits/:sha/cherry_pick', requirements: COMMIT_ENDPOINT_REQUIREMENTS do authorize! :push_code, user_project commit = user_project.commit(params[:sha]) @@ -157,7 +161,7 @@ module API success Entities::CommitNote end params do - requires :sha, type: String, regexp: /\A\h{6,40}\z/, desc: "The commit's SHA" + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment' requires :note, type: String, desc: 'The text of the comment' optional :path, type: String, desc: 'The file path' given :path do @@ -165,7 +169,7 @@ module API requires :line_type, type: String, values: %w(new old), default: 'new', desc: 'The type of the line' end end - post ':id/repository/commits/:sha/comments' do + post ':id/repository/commits/:sha/comments', requirements: COMMIT_ENDPOINT_REQUIREMENTS do commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index d5c2f3d5094..42e7c1486b0 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -125,6 +125,7 @@ module API key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id]) not_found!('Deploy Key') unless key + status 204 key.destroy end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f4796f311a5..6ba4005dd0b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -66,13 +66,6 @@ module API expose :job_events end - class BasicProjectDetails < Grape::Entity - expose :id - expose :http_url_to_repo, :web_url - expose :name, :name_with_namespace - expose :path, :path_with_namespace - end - class SharedGroup < Grape::Entity expose :group_id expose :group_name do |group_link, options| @@ -81,13 +74,51 @@ module API expose :group_access, as: :group_access_level end - class Project < Grape::Entity + class BasicProjectDetails < Grape::Entity expose :id, :description, :default_branch, :tag_list - expose :archived?, as: :archived - expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url - expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } + expose :ssh_url_to_repo, :http_url_to_repo, :web_url expose :name, :name_with_namespace expose :path, :path_with_namespace + expose :star_count, :forks_count + expose :created_at, :last_activity_at + end + + class Project < BasicProjectDetails + include ::API::Helpers::RelatedResourcesHelpers + + expose :_links do + expose :self do |project| + expose_url(api_v4_projects_path(id: project.id)) + end + + expose :issues, if: -> (*args) { issues_available?(*args) } do |project| + expose_url(api_v4_projects_issues_path(id: project.id)) + end + + expose :merge_requests, if: -> (*args) { mrs_available?(*args) } do |project| + expose_url(api_v4_projects_merge_requests_path(id: project.id)) + end + + expose :repo_branches do |project| + expose_url(api_v4_projects_repository_branches_path(id: project.id)) + end + + expose :labels do |project| + expose_url(api_v4_projects_labels_path(id: project.id)) + end + + expose :events do |project| + expose_url(api_v4_projects_events_path(id: project.id)) + end + + expose :members do |project| + expose_url(api_v4_projects_members_path(id: project.id)) + end + end + + expose :archived?, as: :archived + expose :visibility + expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :container_registry_enabled # Expose old field names with the new permissions methods to keep API compatible @@ -97,7 +128,6 @@ module API expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) } expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) } - expose :created_at, :last_activity_at expose :shared_runners_enabled expose :lfs_enabled?, as: :lfs_enabled expose :creator_id @@ -108,8 +138,7 @@ module API expose :avatar_url do |user, options| user.avatar_url(only_path: false) end - expose :star_count, :forks_count - expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? } + expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds, as: :public_jobs expose :ci_config_path @@ -208,7 +237,7 @@ module API end expose :protected do |repo_branch, options| - ProtectedBranch.protected?(options[:project], repo_branch.name) + ::ProtectedBranch.protected?(options[:project], repo_branch.name) end expose :developers_can_push do |repo_branch, options| @@ -267,10 +296,23 @@ module API expose :deleted_file?, as: :deleted_file end + class ProtectedRefAccess < Grape::Entity + expose :access_level + expose :access_level_description do |protected_ref_access| + protected_ref_access.humanize + end + end + + class ProtectedBranch < Grape::Entity + expose :name + expose :push_access_levels, using: Entities::ProtectedRefAccess + expose :merge_access_levels, using: Entities::ProtectedRefAccess + end + class Milestone < Grape::Entity expose :id, :iid - expose(:project_id) { |entity| entity&.project_id } - expose(:group_id) { |entity| entity&.group_id } + expose :project_id, if: -> (entity, options) { entity&.project_id } + expose :group_id, if: -> (entity, options) { entity&.group_id } expose :title, :description expose :state, :created_at, :updated_at expose :due_date @@ -297,6 +339,26 @@ module API end class Issue < IssueBasic + include ::API::Helpers::RelatedResourcesHelpers + + expose :_links do + expose :self do |issue| + expose_url(api_v4_project_issue_path(id: issue.project_id, issue_iid: issue.iid)) + end + + expose :notes do |issue| + expose_url(api_v4_projects_issues_notes_path(id: issue.project_id, noteable_id: issue.iid)) + end + + expose :award_emoji do |issue| + expose_url(api_v4_projects_issues_award_emoji_path(id: issue.project_id, issue_iid: issue.iid)) + end + + expose :project do |issue| + expose_url(api_v4_projects_path(id: issue.project_id)) + end + end + expose :subscribed do |issue, options| issue.subscribed?(options[:current_user], options[:project] || issue.project) end @@ -314,12 +376,35 @@ module API expose :id end + class MergeRequestSimple < ProjectEntity + expose :title + expose :web_url do |merge_request, options| + Gitlab::UrlBuilder.build(merge_request) + end + end + class MergeRequestBasic < ProjectEntity expose :target_branch, :source_branch - expose :upvotes, :downvotes + expose :upvotes do |merge_request, options| + if options[:issuable_metadata] + options[:issuable_metadata][merge_request.id].upvotes + else + merge_request.upvotes + end + end + expose :downvotes do |merge_request, options| + if options[:issuable_metadata] + options[:issuable_metadata][merge_request.id].downvotes + else + merge_request.downvotes + end + end expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id - expose :label_names, as: :labels + expose :labels do |merge_request, options| + # Avoids an N+1 query since labels are preloaded + merge_request.labels.map(&:title).sort + end expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone expose :merge_when_pipeline_succeeds @@ -369,6 +454,9 @@ module API end class Note < Grape::Entity + # Only Issue and MergeRequest have iid + NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze + expose :id expose :note, as: :body expose :attachment_identifier, as: :attachment @@ -376,6 +464,9 @@ module API expose :created_at, :updated_at expose :system?, as: :system expose :noteable_id, :noteable_type + + # Avoid N+1 queries as much as possible + expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) } end class AwardEmoji < Grape::Entity @@ -408,7 +499,7 @@ module API class Event < Grape::Entity expose :title, :project_id, :action_name - expose :target_id, :target_type, :author_id + expose :target_id, :target_iid, :target_type, :author_id expose :data, :target_title expose :created_at expose :note, using: Entities::Note, if: ->(event, options) { event.note? } @@ -596,42 +687,14 @@ module API class ApplicationSetting < Grape::Entity expose :id - expose :default_projects_limit - expose :signup_enabled - expose :signin_enabled - expose :gravatar_enabled - expose :sign_in_text - expose :after_sign_up_text - expose :created_at - expose :updated_at - expose :home_page_url - expose :default_branch_protection + expose(*::ApplicationSettingsHelper.visible_attributes) expose(:restricted_visibility_levels) do |setting, _options| setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) } end - expose :max_attachment_size - expose :session_expire_delay expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) } expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) } expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) } - expose :default_artifacts_expire_in - expose :domain_whitelist - expose :domain_blacklist_enabled - expose :domain_blacklist - expose :user_oauth_applications - expose :after_sign_out_path - expose :container_registry_token_expire_delay - expose :repository_storage - expose :repository_storages - expose :koding_enabled - expose :koding_url - expose :plantuml_enabled - expose :plantuml_url - expose :terminal_max_session_time - expose :polling_interval_multiplier - expose :help_page_hide_commercial_content - expose :help_page_text - expose :help_page_support_url + expose :password_authentication_enabled, as: :signin_enabled end class Release < Grape::Entity @@ -642,7 +705,7 @@ module API class RepoTag < Grape::Entity expose :name, :message - expose :commit do |repo_tag, options| + expose :commit, using: Entities::RepoCommit do |repo_tag, options| options[:project].repository.commit(repo_tag.dereferenced_target) end @@ -894,5 +957,11 @@ module API expose :ip_address expose :submitted, as: :akismet_submitted end + + class RepositoryStorageHealth < Grape::Entity + expose :storage_name + expose :failing_on_hosts + expose :total_failures + end end end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 945771d46f3..c774a5c6685 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -79,6 +79,7 @@ module API environment = user_project.environments.find(params[:environment_id]) + status 204 environment.destroy end diff --git a/lib/api/files.rb b/lib/api/files.rb index 521287ee2b4..450334fee84 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -4,7 +4,7 @@ module API def commit_params(attrs) { file_path: attrs[:file_path], - start_branch: attrs[:branch], + start_branch: attrs[:start_branch] || attrs[:branch], branch_name: attrs[:branch], commit_message: attrs[:commit_message], file_content: attrs[:content], @@ -37,8 +37,9 @@ module API params :simple_file_params do requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :branch, type: String, desc: 'The name of branch' - requires :commit_message, type: String, desc: 'Commit Message' + requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.' + requires :commit_message, type: String, desc: 'Commit message' + optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' optional :author_email, type: String, desc: 'The email of the author' optional :author_name, type: String, desc: 'The name of the author' end diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb new file mode 100644 index 00000000000..b85eb59dc0a --- /dev/null +++ b/lib/api/group_milestones.rb @@ -0,0 +1,85 @@ +module API + class GroupMilestones < Grape::API + include MilestoneResponses + include PaginationParams + + before do + authenticate! + end + + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups, requirements: { id: %r{[^/]+} } do + desc 'Get a list of group milestones' do + success Entities::Milestone + end + params do + use :list_params + end + get ":id/milestones" do + list_milestones_for(user_group) + end + + desc 'Get a single group milestone' do + success Entities::Milestone + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a group milestone' + end + get ":id/milestones/:milestone_id" do + authorize! :read_group, user_group + + get_milestone_for(user_group) + end + + desc 'Create a new group milestone' do + success Entities::Milestone + end + params do + requires :title, type: String, desc: 'The title of the milestone' + use :optional_params + end + post ":id/milestones" do + authorize! :admin_milestones, user_group + + create_milestone_for(user_group) + end + + desc 'Update an existing group milestone' do + success Entities::Milestone + end + params do + use :update_params + end + put ":id/milestones/:milestone_id" do + authorize! :admin_milestones, user_group + + update_milestone_for(user_group) + end + + desc 'Get all issues for a single group milestone' do + success Entities::IssueBasic + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a group milestone' + use :pagination + end + get ":id/milestones/:milestone_id/issues" do + milestone_issuables_for(user_group, :issue) + end + + desc 'Get all merge requests for a single group milestone' do + detail 'This feature was introduced in GitLab 9.' + success Entities::MergeRequestBasic + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a group milestone' + use :pagination + end + get ':id/milestones/:milestone_id/merge_requests' do + milestone_issuables_for(user_group, :merge_request) + end + end + end +end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb new file mode 100644 index 00000000000..f64da4ab77b --- /dev/null +++ b/lib/api/group_variables.rb @@ -0,0 +1,96 @@ +module API + class GroupVariables < Grape::API + include PaginationParams + + before { authenticate! } + before { authorize! :admin_build, user_group } + + params do + requires :id, type: String, desc: 'The ID of a group' + end + + resource :groups, requirements: { id: %r{[^/]+} } do + desc 'Get group-level variables' do + success Entities::Variable + end + params do + use :pagination + end + get ':id/variables' do + variables = user_group.variables + present paginate(variables), with: Entities::Variable + end + + desc 'Get a specific variable from a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end + get ':id/variables/:key' do + key = params[:key] + variable = user_group.variables.find_by(key: key) + + return not_found!('GroupVariable') unless variable + + present variable, with: Entities::Variable + end + + desc 'Create a new variable in a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + requires :value, type: String, desc: 'The value of the variable' + optional :protected, type: String, desc: 'Whether the variable is protected' + end + post ':id/variables' do + variable_params = declared_params(include_missing: false) + + variable = user_group.variables.create(variable_params) + + if variable.valid? + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Update an existing variable from a group' do + success Entities::Variable + end + params do + optional :key, type: String, desc: 'The key of the variable' + optional :value, type: String, desc: 'The value of the variable' + optional :protected, type: String, desc: 'Whether the variable is protected' + end + put ':id/variables/:key' do + variable = user_group.variables.find_by(key: params[:key]) + + return not_found!('GroupVariable') unless variable + + variable_params = declared_params(include_missing: false).except(:key) + + if variable.update(variable_params) + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Delete an existing variable from a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end + delete ':id/variables/:key' do + variable = user_group.variables.find_by(key: params[:key]) + not_found!('GroupVariable') unless variable + + status 204 + variable.destroy + end + end + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index ebbaed0cbb7..49c3b2278c7 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -125,6 +125,8 @@ module API delete ":id" do group = find_group!(params[:id]) authorize! :admin_group, group + + status 204 ::Groups::DestroyService.new(group, current_user).execute end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7003390113b..4056d06bcc7 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -16,6 +16,8 @@ module API @current_user = initial_current_user + Gitlab::I18n.locale = @current_user&.preferred_language + sudo! @current_user @@ -25,6 +27,10 @@ module API initial_current_user != current_user end + def user_group + @group ||= find_group!(params[:id]) + end + def user_project @project ||= find_project!(params[:id]) end @@ -332,12 +338,14 @@ module API env['warden'] end + # Check if the request is GET/HEAD, or if CSRF token is valid. + def verified_request? + Gitlab::RequestForgeryProtection.verified?(env) + end + # Check the Rails session for valid authentication details - # - # Until CSRF protection is added to the API, disallow this method for - # state-changing endpoints def find_user_from_warden - warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) + warden.try(:authenticate) if verified_request? end def initial_current_user diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb new file mode 100644 index 00000000000..1f677529b07 --- /dev/null +++ b/lib/api/helpers/related_resources_helpers.rb @@ -0,0 +1,28 @@ +module API + module Helpers + module RelatedResourcesHelpers + include GrapeRouteHelpers::NamedRouteMatcher + + def issues_available?(project, options) + available?(:issues, project, options[:current_user]) + end + + def mrs_available?(project, options) + available?(:merge_requests, project, options[:current_user]) + end + + def expose_url(path) + url_options = Gitlab::Application.routes.default_url_options + protocol, host, port = url_options.slice(:protocol, :host, :port).values + + URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s + end + + private + + def available?(feature, project, current_user) + project.feature_available?(feature, current_user) + end + end + end +end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ef2c08e902c..8b007869dc3 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -101,7 +101,7 @@ module API end get "/broadcast_message" do - if message = BroadcastMessage.current.last + if message = BroadcastMessage.current&.last present message, with: Entities::BroadcastMessage else {} @@ -150,7 +150,7 @@ module API # # begin # repository = wiki? ? project.wiki.repository : project.repository - # Gitlab::GitalyClient::Notifications.new(repository.raw_repository).post_receive + # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive # rescue GRPC::Unavailable => e # render_api_error!(e, 500) # end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 64be08094ed..4cec1145f3a 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -29,6 +29,10 @@ module API optional :search, type: String, desc: 'Search issues for text present in the title or description' optional :created_after, type: DateTime, desc: 'Return issues created after the specified time' optional :created_before, type: DateTime, desc: 'Return issues created before the specified time' + optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' + optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID' + optional :scope, type: String, values: %w[created-by-me assigned-to-me all], + desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' use :pagination end @@ -55,9 +59,11 @@ module API optional :state, type: String, values: %w[opened closed all], default: 'all', desc: 'Return opened, closed, or all issues' use :issues_params + optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me', + desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' end get do - issues = find_issues(scope: 'authored') + issues = find_issues present paginate(issues), with: Entities::IssueBasic, current_user: current_user end @@ -112,7 +118,7 @@ module API params do requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue' end - get ":id/issues/:issue_iid" do + get ":id/issues/:issue_iid", as: :api_v4_project_issue do issue = find_project_issue(params[:issue_iid]) present issue, with: Entities::Issue, current_user: current_user, project: user_project end @@ -224,6 +230,7 @@ module API not_found!('Issue') unless issue authorize!(:destroy_issue, issue) + status 204 issue.destroy end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 20b25529d0c..4520c98d951 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -56,6 +56,7 @@ module API label = user_project.labels.find_by(title: params[:name]) not_found!('Label') unless label + status 204 label.destroy end diff --git a/lib/api/members.rb b/lib/api/members.rb index c200e46a328..bb970b7cd54 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -96,6 +96,7 @@ module API # Ensure that memeber exists source.members.find_by!(user_id: params[:user_id]) + status 204 ::Members::DestroyService.new(source, current_user, declared_params).execute end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d419d345ec5..8810d4e441d 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -4,6 +4,71 @@ module API before { authenticate! } + helpers ::Gitlab::IssuableMetadata + + helpers do + def find_merge_requests(args = {}) + args = params.merge(args) + + args[:milestone_title] = args.delete(:milestone) + args[:label_name] = args.delete(:labels) + + merge_requests = MergeRequestsFinder.new(current_user, args).execute + .reorder(args[:order_by] => args[:sort]) + merge_requests = paginate(merge_requests) + .preload(:target_project) + + return merge_requests if args[:view] == 'simple' + + merge_requests + .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels) + end + + params :merge_requests_params do + optional :state, type: String, values: %w[opened closed merged all], default: 'all', + desc: 'Return opened, closed, merged, or all merge requests' + optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', + desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return merge requests sorted in `asc` or `desc` order.' + optional :milestone, type: String, desc: 'Return merge requests for a specific milestone' + optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time' + optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time' + optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' + optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' + optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID' + optional :scope, type: String, values: %w[created-by-me assigned-to-me all], + desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + use :pagination + end + end + + resource :merge_requests do + desc 'List merge requests' do + success Entities::MergeRequestBasic + end + params do + use :merge_requests_params + optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me', + desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + end + get do + merge_requests = find_merge_requests + + options = { with: Entities::MergeRequestBasic, + current_user: current_user } + + if params[:view] == 'simple' + options[:with] = Entities::MergeRequestSimple + else + options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest') + end + + present merge_requests, options + end + end + params do requires :id, type: String, desc: 'The ID of a project' end @@ -27,27 +92,6 @@ module API render_api_error!(errors, 400) end - def issue_entity(project) - if project.has_external_issue_tracker? - Entities::ExternalIssue - else - Entities::IssueBasic - end - end - - def find_merge_requests(args = {}) - args = params.merge(args) - - args[:milestone_title] = args.delete(:milestone) - args[:label_name] = args.delete(:labels) - - merge_requests = MergeRequestsFinder.new(current_user, args).execute - .inc_notes_with_associations - .preload(:target_project, :author, :assignee, :milestone, :merge_request_diff) - - merge_requests.reorder(args[:order_by] => args[:sort]) - end - params :optional_params_ce do optional :description, type: String, desc: 'The description of the merge request' optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' @@ -65,25 +109,25 @@ module API success Entities::MergeRequestBasic end params do - optional :state, type: String, values: %w[opened closed merged all], default: 'all', - desc: 'Return opened, closed, merged, or all merge requests' - optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', - desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' - optional :sort, type: String, values: %w[asc desc], default: 'desc', - desc: 'Return merge requests sorted in `asc` or `desc` order.' + use :merge_requests_params optional :iids, type: Array[Integer], desc: 'The IID array of merge requests' - optional :milestone, type: String, desc: 'Return merge requests for a specific milestone' - optional :labels, type: String, desc: 'Comma-separated list of label names' - optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time' - optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time' - use :pagination end get ":id/merge_requests" do authorize! :read_merge_request, user_project merge_requests = find_merge_requests(project_id: user_project.id) - present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project + options = { with: Entities::MergeRequestBasic, + current_user: current_user, + project: user_project } + + if params[:view] == 'simple' + options[:with] = Entities::MergeRequestSimple + else + options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest') + end + + present merge_requests, options end desc 'Create a merge request' do @@ -120,6 +164,7 @@ module API merge_request = find_project_merge_request(params[:merge_request_iid]) authorize!(:destroy_merge_request, merge_request) + status 204 merge_request.destroy end @@ -260,7 +305,14 @@ module API get ':id/merge_requests/:merge_request_iid/closes_issues' do merge_request = find_merge_request_with_access(params[:merge_request_iid]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) - present paginate(issues), with: issue_entity(user_project), current_user: current_user + issues = paginate(issues) + + external_issues, internal_issues = issues.partition { |issue| issue.is_a?(ExternalIssue) } + + data = Entities::IssueBasic.represent(internal_issues, current_user: current_user) + data += Entities::ExternalIssue.represent(external_issues, current_user: current_user) + + data.as_json end end end diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb new file mode 100644 index 00000000000..ef09d9505d2 --- /dev/null +++ b/lib/api/milestone_responses.rb @@ -0,0 +1,98 @@ +module API + module MilestoneResponses + extend ActiveSupport::Concern + + included do + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the milestone' + optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)' + optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)' + end + + params :list_params do + optional :state, type: String, values: %w[active closed all], default: 'all', + desc: 'Return "active", "closed", or "all" milestones' + optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones' + optional :search, type: String, desc: 'The search criteria for the title or description of the milestone' + use :pagination + end + + params :update_params do + requires :milestone_id, type: Integer, desc: 'The milestone ID number' + optional :title, type: String, desc: 'The title of the milestone' + optional :state_event, type: String, values: %w[close activate], + desc: 'The state event of the milestone ' + use :optional_params + at_least_one_of :title, :description, :due_date, :state_event + end + + def list_milestones_for(parent) + milestones = parent.milestones + milestones = Milestone.filter_by_state(milestones, params[:state]) + milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present? + milestones = filter_by_search(milestones, params[:search]) if params[:search] + + present paginate(milestones), with: Entities::Milestone + end + + def get_milestone_for(parent) + milestone = parent.milestones.find(params[:milestone_id]) + present milestone, with: Entities::Milestone + end + + def create_milestone_for(parent) + milestone = ::Milestones::CreateService.new(parent, current_user, declared_params).execute + + if milestone.valid? + present milestone, with: Entities::Milestone + else + render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400) + end + end + + def update_milestone_for(parent) + milestone = parent.milestones.find(params.delete(:milestone_id)) + + milestone_params = declared_params(include_missing: false) + milestone = ::Milestones::UpdateService.new(parent, current_user, milestone_params).execute(milestone) + + if milestone.valid? + present milestone, with: Entities::Milestone + else + render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400) + end + end + + def milestone_issuables_for(parent, type) + milestone = parent.milestones.find(params[:milestone_id]) + + finder_klass, entity = get_finder_and_entity(type) + + params = build_finder_params(milestone, parent) + + issuables = finder_klass.new(current_user, params).execute + present paginate(issuables), with: entity, current_user: current_user + end + + def build_finder_params(milestone, parent) + finder_params = { milestone_title: milestone.title, sort: 'label_priority' } + + if parent.is_a?(Group) + finder_params.merge(group_id: parent.id) + else + finder_params.merge(project_id: parent.id) + end + end + + def get_finder_and_entity(type) + if type == :issue + [IssuesFinder, Entities::IssueBasic] + else + [MergeRequestsFinder, Entities::MergeRequestBasic] + end + end + end + end + end +end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb deleted file mode 100644 index 3541d3c95fb..00000000000 --- a/lib/api/milestones.rb +++ /dev/null @@ -1,154 +0,0 @@ -module API - class Milestones < Grape::API - include PaginationParams - - before { authenticate! } - - helpers do - def filter_milestones_state(milestones, state) - case state - when 'active' then milestones.active - when 'closed' then milestones.closed - else milestones - end - end - - params :optional_params do - optional :description, type: String, desc: 'The description of the milestone' - optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)' - optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)' - end - end - - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: { id: %r{[^/]+} } do - desc 'Get a list of project milestones' do - success Entities::Milestone - end - params do - optional :state, type: String, values: %w[active closed all], default: 'all', - desc: 'Return "active", "closed", or "all" milestones' - optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones' - optional :search, type: String, desc: 'The search criteria for the title or description of the milestone' - use :pagination - end - get ":id/milestones" do - authorize! :read_milestone, user_project - - milestones = user_project.milestones - milestones = filter_milestones_state(milestones, params[:state]) - milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present? - milestones = filter_by_search(milestones, params[:search]) if params[:search] - - present paginate(milestones), with: Entities::Milestone - end - - desc 'Get a single project milestone' do - success Entities::Milestone - end - params do - requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' - end - get ":id/milestones/:milestone_id" do - authorize! :read_milestone, user_project - - milestone = user_project.milestones.find(params[:milestone_id]) - present milestone, with: Entities::Milestone - end - - desc 'Create a new project milestone' do - success Entities::Milestone - end - params do - requires :title, type: String, desc: 'The title of the milestone' - use :optional_params - end - post ":id/milestones" do - authorize! :admin_milestone, user_project - - milestone = ::Milestones::CreateService.new(user_project, current_user, declared_params).execute - - if milestone.valid? - present milestone, with: Entities::Milestone - else - render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400) - end - end - - desc 'Update an existing project milestone' do - success Entities::Milestone - end - params do - requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' - optional :title, type: String, desc: 'The title of the milestone' - optional :state_event, type: String, values: %w[close activate], - desc: 'The state event of the milestone ' - use :optional_params - at_least_one_of :title, :description, :due_date, :state_event - end - put ":id/milestones/:milestone_id" do - authorize! :admin_milestone, user_project - milestone = user_project.milestones.find(params.delete(:milestone_id)) - - milestone_params = declared_params(include_missing: false) - milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone) - - if milestone.valid? - present milestone, with: Entities::Milestone - else - render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400) - end - end - - desc 'Get all issues for a single project milestone' do - success Entities::IssueBasic - end - params do - requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' - use :pagination - end - get ":id/milestones/:milestone_id/issues" do - authorize! :read_milestone, user_project - - milestone = user_project.milestones.find(params[:milestone_id]) - - finder_params = { - project_id: user_project.id, - milestone_title: milestone.title, - sort: 'label_priority' - } - - issues = IssuesFinder.new(current_user, finder_params).execute - present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project - end - - desc 'Get all merge requests for a single project milestone' do - detail 'This feature was introduced in GitLab 9.' - success Entities::MergeRequestBasic - end - params do - requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' - use :pagination - end - get ':id/milestones/:milestone_id/merge_requests' do - authorize! :read_milestone, user_project - - milestone = user_project.milestones.find(params[:milestone_id]) - - finder_params = { - project_id: user_project.id, - milestone_title: milestone.title, - sort: 'label_priority' - } - - merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute - present paginate(merge_requests), - with: Entities::MergeRequestBasic, - current_user: current_user, - project: user_project - end - end - end -end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 01ca62b593f..65ff89edf65 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -131,6 +131,7 @@ module API note = user_project.notes.find(params[:note_id]) authorize! :admin_note, note + status 204 ::Notes::DestroyService.new(user_project, current_user).execute(note) end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 7a345289617..649dd891f56 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -96,6 +96,7 @@ module API delete ":id/hooks/:hook_id" do hook = user_project.hooks.find(params.delete(:hook_id)) + status 204 hook.destroy end end diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb new file mode 100644 index 00000000000..451998c726a --- /dev/null +++ b/lib/api/project_milestones.rb @@ -0,0 +1,91 @@ +module API + class ProjectMilestones < Grape::API + include PaginationParams + include MilestoneResponses + + before do + authenticate! + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: %r{[^/]+} } do + desc 'Get a list of project milestones' do + success Entities::Milestone + end + params do + use :list_params + end + get ":id/milestones" do + authorize! :read_milestone, user_project + + list_milestones_for(user_project) + end + + desc 'Get a single project milestone' do + success Entities::Milestone + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + end + get ":id/milestones/:milestone_id" do + authorize! :read_milestone, user_project + + get_milestone_for(user_project) + end + + desc 'Create a new project milestone' do + success Entities::Milestone + end + params do + requires :title, type: String, desc: 'The title of the milestone' + use :optional_params + end + post ":id/milestones" do + authorize! :admin_milestone, user_project + + create_milestone_for(user_project) + end + + desc 'Update an existing project milestone' do + success Entities::Milestone + end + params do + use :update_params + end + put ":id/milestones/:milestone_id" do + authorize! :admin_milestone, user_project + + update_milestone_for(user_project) + end + + desc 'Get all issues for a single project milestone' do + success Entities::IssueBasic + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + use :pagination + end + get ":id/milestones/:milestone_id/issues" do + authorize! :read_milestone, user_project + + milestone_issuables_for(user_project, :issue) + end + + desc 'Get all merge requests for a single project milestone' do + detail 'This feature was introduced in GitLab 9.' + success Entities::MergeRequestBasic + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + use :pagination + end + get ':id/milestones/:milestone_id/merge_requests' do + authorize! :read_milestone, user_project + + milestone_issuables_for(user_project, :merge_request) + end + end + end +end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 3320eadff0d..f3d905b0068 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -116,6 +116,7 @@ module API not_found!('Snippet') unless snippet authorize! :admin_project_snippet, snippet + status 204 snippet.destroy end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c459257158d..89dda88d3f5 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -361,6 +361,7 @@ module API authorize! :remove_fork_project, user_project if user_project.forked? + status 204 user_project.forked_project_link.destroy else not_modified! @@ -405,6 +406,7 @@ module API link = user_project.project_group_links.find_by(group_id: params[:group_id]) not_found!('Group Link') unless link + status 204 link.destroy end diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb new file mode 100644 index 00000000000..d742f2e18d0 --- /dev/null +++ b/lib/api/protected_branches.rb @@ -0,0 +1,85 @@ +module API + class ProtectedBranches < Grape::API + include PaginationParams + + BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX) + + before { authorize_admin_project } + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + desc "Get a project's protected branches" do + success Entities::ProtectedBranch + end + params do + use :pagination + end + get ':id/protected_branches' do + protected_branches = user_project.protected_branches.preload(:push_access_levels, :merge_access_levels) + + present paginate(protected_branches), with: Entities::ProtectedBranch, project: user_project + end + + desc 'Get a single protected branch' do + success Entities::ProtectedBranch + end + params do + requires :name, type: String, desc: 'The name of the branch or wildcard' + end + get ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + protected_branch = user_project.protected_branches.find_by!(name: params[:name]) + + present protected_branch, with: Entities::ProtectedBranch, project: user_project + end + + desc 'Protect a single branch or wildcard' do + success Entities::ProtectedBranch + end + params do + requires :name, type: String, desc: 'The name of the protected branch' + optional :push_access_level, type: Integer, default: Gitlab::Access::MASTER, + values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS, + desc: 'Access levels allowed to push (defaults: `40`, master access level)' + optional :merge_access_level, type: Integer, default: Gitlab::Access::MASTER, + values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS, + desc: 'Access levels allowed to merge (defaults: `40`, master access level)' + end + post ':id/protected_branches' do + protected_branch = user_project.protected_branches.find_by(name: params[:name]) + if protected_branch + conflict!("Protected branch '#{params[:name]}' already exists") + end + + protected_branch_params = { + name: params[:name], + push_access_levels_attributes: [{ access_level: params[:push_access_level] }], + merge_access_levels_attributes: [{ access_level: params[:merge_access_level] }] + } + + service_args = [user_project, current_user, protected_branch_params] + + protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute + + if protected_branch.persisted? + present protected_branch, with: Entities::ProtectedBranch, project: user_project + else + render_api_error!(protected_branch.errors.full_messages, 422) + end + end + + desc 'Unprotect a single branch' + params do + requires :name, type: String, desc: 'The name of the protected branch' + end + delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + protected_branch = user_project.protected_branches.find_by!(name: params[:name]) + + protected_branch.destroy + + status 204 + end + end + end +end diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 4552115b3e2..88fc62d33df 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -45,6 +45,7 @@ module API end delete '/' do authenticate_runner! + status 204 Ci::Runner.find_by_token(params[:token]).destroy end @@ -89,7 +90,7 @@ module API if result.valid? if result.build Gitlab::Metrics.add_event(:build_found, - project: result.build.project.path_with_namespace) + project: result.build.project.full_path) present result.build, with: Entities::JobRequest::Response else Gitlab::Metrics.add_event(:build_not_found) @@ -118,7 +119,7 @@ module API job.trace.set(params[:trace]) if params[:trace] Gitlab::Metrics.add_event(:update_build, - project: job.project.path_with_namespace) + project: job.project.full_path) case params[:state].to_s when 'success' diff --git a/lib/api/runners.rb b/lib/api/runners.rb index db6c7c59092..5bf5a18e42f 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -79,6 +79,7 @@ module API runner = get_runner(params[:id]) authenticate_delete_runner!(runner) + status 204 runner.destroy! end end @@ -134,6 +135,7 @@ module API runner = runner_project.runner forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 + status 204 runner_project.destroy end end diff --git a/lib/api/services.rb b/lib/api/services.rb index 7488f95a9b7..843c05ae32e 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -313,12 +313,6 @@ module API desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com' }, { - required: true, - name: :project_key, - type: String, - desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ' - }, - { required: false, name: :username, type: String, diff --git a/lib/api/settings.rb b/lib/api/settings.rb index d598f9a62a2..d55a61fa638 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -20,58 +20,6 @@ module API success Entities::ApplicationSetting end params do - # CE - at_least_one_of_ce = [ - :admin_notification_email, - :after_sign_out_path, - :after_sign_up_text, - :akismet_enabled, - :container_registry_token_expire_delay, - :default_artifacts_expire_in, - :default_branch_protection, - :default_group_visibility, - :default_project_visibility, - :default_projects_limit, - :default_snippet_visibility, - :disabled_oauth_sign_in_sources, - :domain_blacklist_enabled, - :domain_whitelist, - :email_author_in_body, - :enabled_git_access_protocol, - :gravatar_enabled, - :help_page_hide_commercial_content, - :help_page_text, - :help_page_support_url, - :home_page_url, - :housekeeping_enabled, - :html_emails_enabled, - :import_sources, - :koding_enabled, - :max_artifacts_size, - :max_attachment_size, - :max_pages_size, - :metrics_enabled, - :plantuml_enabled, - :polling_interval_multiplier, - :recaptcha_enabled, - :repository_checks_enabled, - :repository_storage, - :require_two_factor_authentication, - :restricted_visibility_levels, - :send_user_confirmation_email, - :sentry_enabled, - :clientside_sentry_enabled, - :session_expire_delay, - :shared_runners_enabled, - :sidekiq_throttling_enabled, - :sign_in_text, - :signin_enabled, - :signup_enabled, - :terminal_max_session_time, - :user_default_external, - :user_oauth_applications, - :version_check_enabled - ] optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master' optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility' optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' @@ -95,7 +43,9 @@ module API requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' end optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' - optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + mutually_exclusive :password_authentication_enabled, :signin_enabled optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -148,7 +98,7 @@ module API given clientside_sentry_enabled: ->(val) { val } do requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name' end - optional :repository_storage, type: String, desc: 'Storage paths for new projects' + optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects' optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues." optional :koding_enabled, type: Boolean, desc: 'Enable Koding' given koding_enabled: ->(val) { val } do @@ -171,11 +121,16 @@ module API optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.' - at_least_one_of(*at_least_one_of_ce) + optional(*::ApplicationSettingsHelper.visible_attributes) + at_least_one_of(*::ApplicationSettingsHelper.visible_attributes) end put "application/settings" do attrs = declared_params(include_missing: false) + if attrs.has_key?(:signin_enabled) + attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled) + end + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index fd634037a77..35ece56c65c 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -123,6 +123,7 @@ module API authorize! :destroy_personal_snippet, snippet + status 204 snippet.destroy end diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index ed7b23b474a..c0179037440 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -66,6 +66,7 @@ module API hook = SystemHook.find_by(id: params[:id]) not_found!('System hook') unless hook + status 204 hook.destroy end end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 633a858f8c7..1333747cced 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -2,19 +2,21 @@ module API class Tags < Grape::API include PaginationParams + TAG_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(tag_name: API::NO_SLASH_URL_PART_REGEX) + before { authorize! :download_code, user_project } params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: { id: %r{[^/]+} } do + resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do desc 'Get a project repository tags' do success Entities::RepoTag end params do use :pagination end - get ":id/repository/tags" do + get ':id/repository/tags' do tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse) present paginate(tags), with: Entities::RepoTag, project: user_project end @@ -25,7 +27,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag' end - get ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do + get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do tag = user_project.repository.find_tag(params[:tag_name]) not_found!('Tag') unless tag @@ -60,7 +62,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag' end - delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do + delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize_push_project result = ::Tags::DestroyService.new(user_project, current_user) @@ -78,7 +80,7 @@ module API requires :tag_name, type: String, desc: 'The name of the tag' requires :description, type: String, desc: 'Release notes with markdown support' end - post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do + post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize_push_project result = CreateReleaseService.new(user_project, current_user) @@ -98,7 +100,7 @@ module API requires :tag_name, type: String, desc: 'The name of the tag' requires :description, type: String, desc: 'Release notes with markdown support' end - put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do + put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize_push_project result = UpdateReleaseService.new(user_project, current_user) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index d1f7e364029..55191169dd4 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -59,10 +59,10 @@ module API requires :id, type: Integer, desc: 'The ID of the todo being marked as done' end post ':id/mark_as_done' do - todo = current_user.todos.find(params[:id]) - TodoService.new.mark_todos_as_done([todo], current_user) + TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) + todo = Todo.find(params[:id]) - present todo.reload, with: Entities::Todo, current_user: current_user + present todo, with: Entities::Todo, current_user: current_user end desc 'Mark all todos as done' diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index a9f2ca2608e..edfdb63d183 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -15,24 +15,22 @@ module API optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do - project = find_project(params[:id]) - trigger = Ci::Trigger.find_by_token(params[:token].to_s) - not_found! unless project && trigger - unauthorized! unless trigger.project == project - # validate variables - variables = params[:variables].to_h - unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) } + params[:variables] = params[:variables].to_h + unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) } render_api_error!('variables needs to be a map of key-valued strings', 400) end - # create request and trigger builds - trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables) - if trigger_request - present trigger_request.pipeline, with: Entities::Pipeline + project = find_project(params[:id]) + not_found! unless project + + result = Ci::PipelineTriggerService.new(project, nil, params).execute + not_found! unless result + + if result[:http_status] + render_api_error!(result[:message], result[:http_status]) else - errors = 'No pipeline created' - render_api_error!(errors, 400) + present result[:pipeline], with: Entities::Pipeline end end @@ -142,6 +140,7 @@ module API trigger = user_project.triggers.find(params.delete(:trigger_id)) return not_found!('Trigger') unless trigger + status 204 trigger.destroy end end diff --git a/lib/api/users.rb b/lib/api/users.rb index c469751c31c..a590f2692a2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -235,6 +235,7 @@ module API key = user.keys.find_by(id: params[:key_id]) not_found!('Key') unless key + status 204 key.destroy end @@ -306,6 +307,7 @@ module API user = User.find_by(id: params[:id]) not_found!('User') unless user + status 204 user.delete_async(deleted_by: current_user, params: params) end @@ -406,6 +408,7 @@ module API requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token' end delete ':impersonation_token_id' do + status 204 find_impersonation_token.revoke! end end @@ -421,7 +424,16 @@ module API success Entities::UserPublic end get do - present current_user, with: sudo? ? Entities::UserWithPrivateDetails : Entities::UserPublic + entity = + if sudo? + Entities::UserWithPrivateDetails + elsif current_user.admin? + Entities::UserWithAdmin + else + Entities::UserPublic + end + + present current_user, with: entity end desc "Get the currently authenticated user's SSH keys" do @@ -474,6 +486,7 @@ module API key = current_user.keys.find_by(id: params[:key_id]) not_found!('Key') unless key + status 204 key.destroy end @@ -525,6 +538,7 @@ module API email = current_user.emails.find_by(id: params[:email_id]) not_found!('Email') unless email + status 204 Emails::DestroyService.new(current_user, email: email.email).execute end diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index c848f52723b..773f667abe0 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -161,7 +161,8 @@ module API expose :id expose :default_projects_limit expose :signup_enabled - expose :signin_enabled + expose :password_authentication_enabled + expose :password_authentication_enabled, as: :signin_enabled expose :gravatar_enabled expose :sign_in_text expose :after_sign_up_text @@ -258,11 +259,40 @@ module API expose :job_events, as: :build_events end - class Issue < ::API::Entities::Issue + class ProjectEntity < Grape::Entity + expose :id, :iid + expose(:project_id) { |entity| entity&.project.try(:id) } + expose :title, :description + expose :state, :created_at, :updated_at + end + + class IssueBasic < ProjectEntity + expose :label_names, as: :labels + expose :milestone, using: ::API::Entities::Milestone + expose :assignees, :author, using: ::API::Entities::UserBasic + + expose :assignee, using: ::API::Entities::UserBasic do |issue, options| + issue.assignees.first + end + + expose :user_notes_count + expose :upvotes, :downvotes + expose :due_date + expose :confidential + + expose :web_url do |issue, options| + Gitlab::UrlBuilder.build(issue) + end + end + + class Issue < IssueBasic unexpose :assignees expose :assignee do |issue, options| ::API::Entities::UserBasic.represent(issue.assignees.first, options) end + expose :subscribed do |issue, options| + issue.subscribed?(options[:current_user], options[:project] || issue.project) + end end end end diff --git a/lib/api/v3/project_hooks.rb b/lib/api/v3/project_hooks.rb index 94614bfc8b6..51014591a93 100644 --- a/lib/api/v3/project_hooks.rb +++ b/lib/api/v3/project_hooks.rb @@ -56,7 +56,9 @@ module API use :project_hook_properties end post ":id/hooks" do - hook = user_project.hooks.new(declared_params(include_missing: false)) + attrs = declared_params(include_missing: false) + attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events) + hook = user_project.hooks.new(attrs) if hook.save present hook, with: ::API::V3::Entities::ProjectHook @@ -77,7 +79,9 @@ module API put ":id/hooks/:hook_id" do hook = user_project.hooks.find(params.delete(:hook_id)) - if hook.update_attributes(declared_params(include_missing: false)) + attrs = declared_params(include_missing: false) + attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events) + if hook.update_attributes(attrs) present hook, with: ::API::V3::Entities::ProjectHook else error!("Invalid url given", 422) if hook.errors[:url].present? diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb index 748d6b97d4f..202011cfcbe 100644 --- a/lib/api/v3/settings.rb +++ b/lib/api/v3/settings.rb @@ -44,7 +44,9 @@ module API requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' end optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' - optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + mutually_exclusive :password_authentication_enabled, :signin_enabled optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -116,7 +118,7 @@ module API :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources, :user_oauth_applications, :user_default_external, :signup_enabled, :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, - :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, + :after_sign_up_text, :password_authentication_enabled, :signin_enabled, :require_two_factor_authentication, :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay, :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, @@ -126,7 +128,13 @@ module API :housekeeping_enabled, :terminal_max_session_time end put "application/settings" do - if current_settings.update_attributes(declared_params(include_missing: false)) + attrs = declared_params(include_missing: false) + + if attrs.has_key?(:signin_enabled) + attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled) + end + + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else render_validation_error!(current_settings) diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb index e3b311d61cd..2f2cf259987 100644 --- a/lib/api/v3/todos.rb +++ b/lib/api/v3/todos.rb @@ -11,10 +11,10 @@ module API requires :id, type: Integer, desc: 'The ID of the todo being marked as done' end delete ':id' do - todo = current_user.todos.find(params[:id]) - TodoService.new.mark_todos_as_done([todo], current_user) + TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) + todo = Todo.find(params[:id]) - present todo.reload, with: ::API::Entities::Todo, current_user: current_user + present todo, with: ::API::Entities::Todo, current_user: current_user end desc 'Mark all todos as done' diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb index a23d6b6b48c..e9d4c35307b 100644 --- a/lib/api/v3/triggers.rb +++ b/lib/api/v3/triggers.rb @@ -28,12 +28,13 @@ module API end # create request and trigger builds - trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables) - if trigger_request - present trigger_request, with: ::API::V3::Entities::TriggerRequest + result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables) + pipeline = result.pipeline + + if pipeline.persisted? + present result.trigger_request, with: ::API::V3::Entities::TriggerRequest else - errors = 'No builds created' - render_api_error!(errors, 400) + render_validation_error!(pipeline) end end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 7fa528fb2d3..7c0fdd3d1be 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -88,6 +88,7 @@ module API variable = user_project.variables.find_by(key: params[:key]) not_found!('Variable') unless variable + status 204 variable.destroy end end |