summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api.rb13
-rw-r--r--lib/api/award_emoji.rb4
-rw-r--r--lib/api/boards.rb13
-rw-r--r--lib/api/branches.rb16
-rw-r--r--lib/api/commits.rb14
-rw-r--r--lib/api/deploy_keys.rb36
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/files.rb9
-rw-r--r--lib/api/helpers.rb22
-rw-r--r--lib/api/helpers/pagination.rb2
-rw-r--r--lib/api/issues.rb20
-rw-r--r--lib/api/labels.rb8
-rw-r--r--lib/api/merge_request_diffs.rb6
-rw-r--r--lib/api/merge_requests.rb3
-rw-r--r--lib/api/milestones.rb22
-rw-r--r--lib/api/pagination_params.rb4
-rw-r--r--lib/api/pipelines.rb4
-rw-r--r--lib/api/project_hooks.rb4
-rw-r--r--lib/api/project_snippets.rb8
-rw-r--r--lib/api/projects.rb15
-rw-r--r--lib/api/repositories.rb14
-rw-r--r--lib/api/runners.rb3
-rw-r--r--lib/api/snippets.rb7
-rw-r--r--lib/api/subscriptions.rb4
-rw-r--r--lib/api/system_hooks.rb10
-rw-r--r--lib/api/tags.rb10
-rw-r--r--lib/api/templates.rb12
-rw-r--r--lib/api/todos.rb6
-rw-r--r--lib/api/users.rb20
-rw-r--r--lib/api/v3/boards.rb51
-rw-r--r--lib/api/v3/branches.rb24
-rw-r--r--lib/api/v3/commits.rb205
-rw-r--r--lib/api/v3/files.rb138
-rw-r--r--lib/api/v3/issues.rb13
-rw-r--r--lib/api/v3/labels.rb19
-rw-r--r--lib/api/v3/project_snippets.rb8
-rw-r--r--lib/api/v3/repositories.rb55
-rw-r--r--lib/api/v3/subscriptions.rb53
-rw-r--r--lib/api/v3/system_hooks.rb19
-rw-r--r--lib/api/v3/tags.rb20
-rw-r--r--lib/api/v3/todos.rb28
-rw-r--r--lib/api/v3/users.rb96
42 files changed, 917 insertions, 123 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 06346ae822a..a0282ff8deb 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,13 +5,26 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
+ mount ::API::V3::Boards
+ mount ::API::V3::Branches
+ mount ::API::V3::Commits
mount ::API::V3::DeployKeys
+ mount ::API::V3::Files
mount ::API::V3::Issues
+ mount ::API::V3::Labels
mount ::API::V3::Members
+ mount ::API::V3::MergeRequestDiffs
mount ::API::V3::MergeRequests
+ mount ::API::V3::ProjectHooks
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
+ mount ::API::V3::Repositories
+ mount ::API::V3::Subscriptions
+ mount ::API::V3::SystemHooks
+ mount ::API::V3::Tags
+ mount ::API::V3::Todos
mount ::API::V3::Templates
+ mount ::API::V3::Users
end
before { allow_access_with_scope :api }
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 58a4df54bea..2ef327217ea 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -28,8 +28,8 @@ module API
end
get endpoint do
if can_read_awardable?
- awards = paginate(awardable.award_emoji)
- present awards, with: Entities::AwardEmoji
+ awards = awardable.award_emoji
+ present paginate(awards), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 13752eb4947..f4226e5a89d 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -1,6 +1,7 @@
module API
- # Boards API
class Boards < Grape::API
+ include PaginationParams
+
before { authenticate! }
params do
@@ -11,9 +12,12 @@ module API
detail 'This feature was introduced in 8.13'
success Entities::Board
end
+ params do
+ use :pagination
+ end
get ':id/boards' do
authorize!(:read_board, user_project)
- present user_project.boards, with: Entities::Board
+ present paginate(user_project.boards), with: Entities::Board
end
params do
@@ -40,9 +44,12 @@ module API
detail 'Does not include `done` list. This feature was introduced in 8.13'
success Entities::List
end
+ params do
+ use :pagination
+ end
get '/lists' do
authorize!(:read_board, user_project)
- present board_lists, with: Entities::List
+ present paginate(board_lists), with: Entities::List
end
desc 'Get a list of a project board' do
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 9331be1f7de..c65de90cca2 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -1,8 +1,9 @@
require 'mime/types'
module API
- # Projects API
class Branches < Grape::API
+ include PaginationParams
+
before { authenticate! }
before { authorize! :download_code, user_project }
@@ -13,10 +14,13 @@ module API
desc 'Get a project repository branches' do
success Entities::RepoBranch
end
+ params do
+ use :pagination
+ end
get ":id/repository/branches" do
- branches = user_project.repository.branches.sort_by(&:name)
+ branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
- present branches, with: Entities::RepoBranch, project: user_project
+ present paginate(branches), with: Entities::RepoBranch, project: user_project
end
desc 'Get a single branch' do
@@ -93,13 +97,13 @@ module API
success Entities::RepoBranch
end
params do
- requires :branch_name, type: String, desc: 'The name of the branch'
+ 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
authorize_push_project
result = CreateBranchService.new(user_project, current_user).
- execute(params[:branch_name], params[:ref])
+ execute(params[:branch], params[:ref])
if result[:status] == :success
present result[:branch],
@@ -122,7 +126,7 @@ module API
if result[:status] == :success
{
- branch_name: params[:branch]
+ branch: params[:branch]
}
else
render_api_error!(result[:message], result[:return_code])
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 173083d0ade..0cd817f9352 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -16,16 +16,13 @@ module API
end
params do
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
- optional :since, type: String, desc: 'Only commits after or in this date will be returned'
- optional :until, type: String, desc: 'Only commits before or in this date will be returned'
+ optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
+ optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
optional :page, type: Integer, default: 0, desc: 'The page for pagination'
optional :per_page, type: Integer, default: 20, desc: 'The number of results per page'
optional :path, type: String, desc: 'The file path'
end
get ":id/repository/commits" do
- # TODO remove the next line for 9.0, use DateTime type in the params block
- datetime_attributes! :since, :until
-
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
offset = params[:page] * params[:per_page]
@@ -44,7 +41,7 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch_name, type: String, desc: 'The name of branch'
+ requires :branch, type: String, desc: 'The name of branch'
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
optional :author_email, type: String, desc: 'Author email for commit'
@@ -53,9 +50,8 @@ module API
post ":id/repository/commits" do
authorize! :push_code, user_project
- attrs = declared_params
- attrs[:start_branch] = attrs[:branch_name]
- attrs[:target_branch] = attrs[:branch_name]
+ attrs = declared_params.merge(start_branch: declared_params[:branch], target_branch: declared_params[:branch])
+
attrs[:actions].map! do |action|
action[:action] = action[:action].to_sym
action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 3f5183d46a2..69e85c27a65 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -1,12 +1,17 @@
module API
class DeployKeys < Grape::API
+ include PaginationParams
+
before { authenticate! }
+ desc 'Return all deploy keys'
+ params do
+ use :pagination
+ end
get "deploy_keys" do
authenticated_as_admin!
- keys = DeployKey.all
- present keys, with: Entities::SSHKey
+ present paginate(DeployKey.all), with: Entities::SSHKey
end
params do
@@ -18,8 +23,11 @@ module API
desc "Get a specific project's deploy keys" do
success Entities::SSHKey
end
+ params do
+ use :pagination
+ end
get ":id/deploy_keys" do
- present user_project.deploy_keys, with: Entities::SSHKey
+ present paginate(user_project.deploy_keys), with: Entities::SSHKey
end
desc 'Get single deploy key' do
@@ -85,20 +93,6 @@ module API
end
end
- desc 'Disable a deploy key for a project' do
- detail 'This feature was added in GitLab 8.11'
- success Entities::SSHKey
- end
- params do
- requires :key_id, type: Integer, desc: 'The ID of the deploy key'
- end
- delete ":id/deploy_keys/:key_id/disable" do
- key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
- key.destroy
-
- present key.deploy_key, with: Entities::SSHKey
- end
-
desc 'Delete deploy key for a project' do
success Key
end
@@ -107,11 +101,9 @@ module API
end
delete ":id/deploy_keys/:key_id" do
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
- if key
- key.destroy
- else
- not_found!('Deploy Key')
- end
+ not_found!('Deploy Key') unless key
+
+ key.destroy
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 232f231ddd2..400ee7c92aa 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -26,7 +26,7 @@ module API
expose :last_sign_in_at
expose :confirmed_at
expose :email
- expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
+ expose :color_scheme_id, :projects_limit, :current_sign_in_at
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 2ecdd747c8e..500f9d3c787 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,12 +1,11 @@
module API
- # Projects API
class Files < Grape::API
helpers do
def commit_params(attrs)
{
file_path: attrs[:file_path],
- start_branch: attrs[:branch_name],
- target_branch: attrs[:branch_name],
+ start_branch: attrs[:branch],
+ target_branch: attrs[:branch],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
file_content_encoding: attrs[:encoding],
@@ -18,13 +17,13 @@ module API
def commit_response(attrs)
{
file_path: attrs[:file_path],
- branch_name: attrs[:branch_name]
+ branch: attrs[:branch]
}
end
params :simple_file_params do
requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb'
- requires :branch_name, type: String, desc: 'The name of branch'
+ requires :branch, type: String, desc: 'The name of branch'
requires :commit_message, type: String, desc: 'Commit Message'
optional :author_email, type: String, desc: 'The email of the author'
optional :author_name, type: String, desc: 'The name of the author'
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 13896dd91b9..a1db2099693 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -153,29 +153,13 @@ module API
params_hash = custom_params || params
attrs = {}
keys.each do |key|
- if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
+ if params_hash[key].present? || (params_hash.has_key?(key) && params_hash[key] == false)
attrs[key] = params_hash[key]
end
end
ActionController::Parameters.new(attrs).permit!
end
- # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
- # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
- #
- # Parameters:
- # keys (required) - An array consisting of elements that must be parseable as dates from the params hash
- def datetime_attributes!(*keys)
- keys.each do |key|
- begin
- params[key] = Time.xmlschema(params[key]) if params[key].present?
- rescue ArgumentError
- message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
- render_api_error!(message, 400)
- end
- end
- end
-
def filter_by_iid(items, iid)
items.where(iid: iid)
end
@@ -231,6 +215,10 @@ module API
end
end
+ def render_spam_error!
+ render_api_error!({ error: 'Spam detected' }, 400)
+ end
+
def render_api_error!(message, status)
error!({ 'message' => message }, status, header)
end
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 2199eea7e5f..0764b58fb4c 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -2,7 +2,7 @@ module API
module Helpers
module Pagination
def paginate(relation)
- relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
+ relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data)
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 90fca20d4fa..6d30c5d81b1 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -10,17 +10,9 @@ module API
args.delete(:id)
args[:milestone_title] = args.delete(:milestone)
+ args[:label_name] = args.delete(:labels)
- match_all_labels = args.delete(:match_all_labels)
- labels = args.delete(:labels)
- args[:label_name] = labels if match_all_labels
-
- issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
-
- # TODO: Remove in 9.0 pass `label_name: args.delete(:labels)` to IssuesFinder
- if !match_all_labels && labels.present?
- issues = issues.includes(:labels).where('labels.title' => labels.split(','))
- end
+ issues = IssuesFinder.new(current_user, args).execute
issues.reorder(args[:order_by] => args[:sort])
end
@@ -77,7 +69,7 @@ module API
get ":id/issues" do
group = find_group!(params[:id])
- issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true)
+ issues = find_issues(group_id: group.id, state: params[:state] || 'opened')
present paginate(issues), with: Entities::Issue, current_user: current_user
end
@@ -177,9 +169,13 @@ module API
params.delete(:updated_at)
end
+ update_params = declared_params(include_missing: false).merge(request: request, api: true)
+
issue = ::Issues::UpdateService.new(user_project,
current_user,
- declared_params(include_missing: false)).execute(issue)
+ update_params).execute(issue)
+
+ render_spam_error! if issue.spam?
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 652786d4e3e..d2955af3f95 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,6 +1,7 @@
module API
- # Labels API
class Labels < Grape::API
+ include PaginationParams
+
before { authenticate! }
params do
@@ -10,8 +11,11 @@ module API
desc 'Get all labels of the project' do
success Entities::Label
end
+ params do
+ use :pagination
+ end
get ':id/labels' do
- present available_labels, with: Entities::Label, current_user: current_user, project: user_project
+ present paginate(available_labels), with: Entities::Label, current_user: current_user, project: user_project
end
desc 'Create a new label' do
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index bc3d69f6904..4901a7cfea6 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -1,6 +1,8 @@
module API
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
+ include PaginationParams
+
before { authenticate! }
resource :projects do
@@ -12,12 +14,12 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ use :pagination
end
-
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = find_merge_request_with_access(params[:merge_request_id])
- present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
+ present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8e09a6f7354..bdd764abfeb 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -119,8 +119,9 @@ module API
end
get ':id/merge_requests/:merge_request_id/commits' do
merge_request = find_merge_request_with_access(params[:merge_request_id])
+ commits = ::Kaminari.paginate_array(merge_request.commits)
- present merge_request.commits, with: Entities::RepoCommit
+ present paginate(commits), with: Entities::RepoCommit
end
desc 'Show the merge request changes' do
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 3c373a84ec5..0b4ed76b35c 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -120,6 +120,28 @@ module API
issues = IssuesFinder.new(current_user, finder_params).execute
present paginate(issues), with: Entities::Issue, 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::MergeRequest
+ 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_id: milestone.id
+ }
+
+ merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
+ present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ end
end
end
end
diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb
index 8c1e4381a74..f566eb3ed2b 100644
--- a/lib/api/pagination_params.rb
+++ b/lib/api/pagination_params.rb
@@ -15,8 +15,8 @@ module API
included do
helpers do
params :pagination do
- optional :page, type: Integer, desc: 'Current page number'
- optional :per_page, type: Integer, desc: 'Number of items per page'
+ optional :page, type: Integer, default: 1, desc: 'Current page number'
+ optional :per_page, type: Integer, default: 20, desc: 'Number of items per page'
end
end
end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index b634b1d0222..f59f7959173 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -23,7 +23,7 @@ module API
pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
present paginate(pipelines), with: Entities::Pipeline
end
-
+
desc 'Create a new pipeline' do
detail 'This feature was introduced in GitLab 8.14'
success Entities::Pipeline
@@ -58,7 +58,7 @@ module API
present pipeline, with: Entities::Pipeline
end
- desc 'Retry failed builds in the pipeline' do
+ desc 'Retry builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Pipeline
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index cb679e6658a..f7a28d7ad10 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -32,9 +32,7 @@ module API
use :pagination
end
get ":id/hooks" do
- hooks = paginate user_project.hooks
-
- present hooks, with: Entities::ProjectHook
+ present paginate(user_project.hooks), with: Entities::ProjectHook
end
desc 'Get a project hook' do
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index dcc0c82ee27..2a1cce73f3f 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -63,6 +63,8 @@ module API
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: Entities::ProjectSnippet
else
@@ -92,12 +94,16 @@ module API
authorize! :update_project_snippet, snippet
snippet_params = declared_params(include_missing: false)
+ .merge(request: request, api: true)
+
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
UpdateSnippetService.new(user_project, current_user, snippet,
snippet_params).execute
- if snippet.persisted?
+ render_spam_error! if snippet.spam?
+
+ if snippet.valid?
present snippet, with: Entities::ProjectSnippet
else
render_validation_error!(snippet)
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 68c2732ec80..f1cb1b22143 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -266,7 +266,7 @@ module API
desc 'Unstar a project' do
success Entities::Project
end
- delete ':id/star' do
+ post ':id/unstar' do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reload
@@ -374,6 +374,19 @@ module API
present paginate(users), with: Entities::UserBasic
end
+
+ desc 'Start the housekeeping task for a project' do
+ detail 'This feature was introduced in GitLab 9.0.'
+ end
+ post ':id/housekeeping' do
+ authorize_admin_project
+
+ begin
+ ::Projects::HousekeepingService.new(user_project).execute
+ rescue ::Projects::HousekeepingService::LeaseTaken => error
+ conflict!(error.message)
+ end
+ end
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 4ca6646a6f1..bfda6f45b0a 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -2,6 +2,8 @@ require 'mime/types'
module API
class Repositories < Grape::API
+ include PaginationParams
+
before { authorize! :download_code, user_project }
params do
@@ -24,6 +26,7 @@ module API
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :path, type: String, desc: 'The path of the tree'
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+ use :pagination
end
get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
@@ -33,8 +36,8 @@ module API
not_found!('Tree') unless commit
tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
-
- present tree.sorted_entries, with: Entities::RepoTreeObject
+ entries = ::Kaminari.paginate_array(tree.sorted_entries)
+ present paginate(entries), with: Entities::RepoTreeObject
end
desc 'Get a raw file contents'
@@ -100,10 +103,13 @@ module API
desc 'Get repository contributors' do
success Entities::Contributor
end
+ params do
+ use :pagination
+ end
get ':id/repository/contributors' do
begin
- present user_project.repository.contributors,
- with: Entities::Contributor
+ contributors = ::Kaminari.paginate_array(user_project.repository.contributors)
+ present paginate(contributors), with: Entities::Contributor
rescue
not_found!
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 4816b5ed1b7..4fbd4096533 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -60,8 +60,9 @@ module API
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
+ update_service = Ci::UpdateRunnerService.new(runner)
- if runner.update(declared_params(include_missing: false))
+ if update_service.update(declared_params(include_missing: false))
present runner, with: Entities::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index eb9ece49e7f..ac03fbd2a3d 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -67,6 +67,8 @@ module API
attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: Entities::PersonalSnippet
else
@@ -93,9 +95,12 @@ module API
return not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet
- attrs = declared_params(include_missing: false)
+ attrs = declared_params(include_missing: false).merge(request: request, api: true)
UpdateSnippetService.new(nil, current_user, snippet, attrs).execute
+
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: Entities::PersonalSnippet
else
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index e11d7537cc9..acf11dbdf26 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -21,7 +21,7 @@ module API
desc 'Subscribe to a resource' do
success entity_class
end
- post ":id/#{type}/:subscribable_id/subscription" do
+ post ":id/#{type}/:subscribable_id/subscribe" do
resource = instance_exec(params[:subscribable_id], &finder)
if resource.subscribed?(current_user, user_project)
@@ -35,7 +35,7 @@ module API
desc 'Unsubscribe from a resource' do
success entity_class
end
- delete ":id/#{type}/:subscribable_id/subscription" do
+ post ":id/#{type}/:subscribable_id/unsubscribe" do
resource = instance_exec(params[:subscribable_id], &finder)
if !resource.subscribed?(current_user, user_project)
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 708ec8cfe70..d038a3fa828 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,6 +1,7 @@
module API
- # Hooks API
class SystemHooks < Grape::API
+ include PaginationParams
+
before do
authenticate!
authenticated_as_admin!
@@ -10,10 +11,11 @@ module API
desc 'Get the list of system hooks' do
success Entities::Hook
end
+ params do
+ use :pagination
+ end
get do
- hooks = SystemHook.all
-
- present hooks, with: Entities::Hook
+ present paginate(SystemHook.all), with: Entities::Hook
end
desc 'Create a new system hook' do
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index b6fd8f569a9..86759ab882f 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -1,6 +1,7 @@
module API
- # Git Tags API
class Tags < Grape::API
+ include PaginationParams
+
before { authorize! :download_code, user_project }
params do
@@ -10,9 +11,12 @@ module API
desc 'Get a project repository tags' do
success Entities::RepoTag
end
+ params do
+ use :pagination
+ end
get ":id/repository/tags" do
- present user_project.repository.tags.sort_by(&:name).reverse,
- with: Entities::RepoTag, project: user_project
+ tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse)
+ present paginate(tags), with: Entities::RepoTag, project: user_project
end
desc 'Get a single repository tag' do
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 8a2d66efd89..0fc13b35d5b 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,5 +1,7 @@
module API
class Templates < Grape::API
+ include PaginationParams
+
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
klass: Gitlab::Template::GitignoreTemplate,
@@ -51,12 +53,14 @@ module API
end
params do
optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ use :pagination
end
get "templates/licenses" do
options = {
featured: declared(params).popular.present? ? true : nil
}
- present Licensee::License.all(options), with: ::API::Entities::RepoLicense
+ licences = ::Kaminari.paginate_array(Licensee::License.all(options))
+ present paginate(licences), with: Entities::RepoLicense
end
desc 'Get the text for a specific license' do
@@ -82,8 +86,12 @@ module API
detail "This feature was introduced in GitLab #{gitlab_version}."
success Entities::TemplatesList
end
+ params do
+ use :pagination
+ end
get "templates/#{template_type}" do
- present klass.all, with: Entities::TemplatesList
+ templates = ::Kaminari.paginate_array(klass.all)
+ present paginate(templates), with: Entities::TemplatesList
end
desc 'Get the text for a specific template present in local filesystem' do
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 9bd077263a7..0b9650b296c 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -58,7 +58,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
end
- delete ':id' do
+ post ':id/mark_as_done' do
todo = current_user.todos.find(params[:id])
TodoService.new.mark_todos_as_done([todo], current_user)
@@ -66,9 +66,11 @@ module API
end
desc 'Mark all todos as done'
- delete do
+ post '/mark_as_done' do
todos = find_todos
TodoService.new.mark_todos_as_done(todos, current_user)
+
+ no_content!
end
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 82ac3886ac3..fbc17953691 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -209,6 +209,7 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
end
get ':id/keys' do
authenticated_as_admin!
@@ -216,7 +217,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- present user.keys, with: Entities::SSHKey
+ present paginate(user.keys), with: Entities::SSHKey
end
desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
@@ -266,13 +267,14 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
end
get ':id/emails' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
- present user.emails, with: Entities::Email
+ present paginate(user.emails), with: Entities::Email
end
desc 'Delete an email address of a specified user. Available only for admins.' do
@@ -312,7 +314,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
- put ':id/block' do
+ post ':id/block' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -328,7 +330,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
- put ':id/unblock' do
+ post ':id/unblock' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -373,8 +375,11 @@ module API
desc "Get the currently authenticated user's SSH keys" do
success Entities::SSHKey
end
+ params do
+ use :pagination
+ end
get "keys" do
- present current_user.keys, with: Entities::SSHKey
+ present paginate(current_user.keys), with: Entities::SSHKey
end
desc 'Get a single key owned by currently authenticated user' do
@@ -423,8 +428,11 @@ module API
desc "Get the currently authenticated user's email addresses" do
success Entities::Email
end
+ params do
+ use :pagination
+ end
get "emails" do
- present current_user.emails, with: Entities::Email
+ present paginate(current_user.emails), with: Entities::Email
end
desc 'Get a single email address owned by the currently authenticated user' do
diff --git a/lib/api/v3/boards.rb b/lib/api/v3/boards.rb
new file mode 100644
index 00000000000..31d708bc2c8
--- /dev/null
+++ b/lib/api/v3/boards.rb
@@ -0,0 +1,51 @@
+module API
+ module V3
+ class Boards < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all project boards' do
+ detail 'This feature was introduced in 8.13'
+ success ::API::Entities::Board
+ end
+ get ':id/boards' do
+ authorize!(:read_board, user_project)
+ present user_project.boards, with: ::API::Entities::Board
+ end
+
+ params do
+ requires :board_id, type: Integer, desc: 'The ID of a board'
+ end
+ segment ':id/boards/:board_id' do
+ helpers do
+ def project_board
+ board = user_project.boards.first
+
+ if params[:board_id] == board.id
+ board
+ else
+ not_found!('Board')
+ end
+ end
+
+ def board_lists
+ project_board.lists.destroyable
+ end
+ end
+
+ desc 'Get the lists of a project board' do
+ detail 'Does not include `done` list. This feature was introduced in 8.13'
+ success ::API::Entities::List
+ end
+ get '/lists' do
+ authorize!(:read_board, user_project)
+ present board_lists, with: ::API::Entities::List
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
new file mode 100644
index 00000000000..733c6b21be5
--- /dev/null
+++ b/lib/api/v3/branches.rb
@@ -0,0 +1,24 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Branches < Grape::API
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository branches' do
+ success ::API::Entities::RepoBranch
+ end
+ get ":id/repository/branches" do
+ branches = user_project.repository.branches.sort_by(&:name)
+
+ present branches, with: ::API::Entities::RepoBranch, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
new file mode 100644
index 00000000000..477e22fd25e
--- /dev/null
+++ b/lib/api/v3/commits.rb
@@ -0,0 +1,205 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Commits < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository commits' do
+ success ::API::Entities::RepoCommit
+ end
+ params do
+ optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+ optional :since, type: DateTime, desc: 'Only commits after or in this date will be returned'
+ optional :until, type: DateTime, desc: 'Only commits before or in this date will be returned'
+ optional :page, type: Integer, default: 0, desc: 'The page for pagination'
+ optional :per_page, type: Integer, default: 20, desc: 'The number of results per page'
+ optional :path, type: String, desc: 'The file path'
+ end
+ get ":id/repository/commits" do
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ offset = params[:page] * params[:per_page]
+
+ commits = user_project.repository.commits(ref,
+ path: params[:path],
+ limit: params[:per_page],
+ offset: offset,
+ after: params[:since],
+ before: params[:until])
+
+ present commits, with: ::API::Entities::RepoCommit
+ end
+
+ desc 'Commit multiple file changes as one commit' do
+ success ::API::Entities::RepoCommitDetail
+ detail 'This feature was introduced in GitLab 8.13'
+ end
+ params do
+ requires :branch_name, type: String, desc: 'The name of branch'
+ requires :commit_message, type: String, desc: 'Commit message'
+ requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
+ 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
+ authorize! :push_code, user_project
+
+ attrs = declared_params.dup
+ branch = attrs.delete(:branch_name)
+ attrs.merge!(branch: branch, start_branch: branch, target_branch: branch)
+
+ attrs[:actions].map! do |action|
+ action[:action] = action[:action].to_sym
+ action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
+ action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
+ action
+ end
+
+ result = ::Files::MultiService.new(user_project, current_user, attrs).execute
+
+ if result[:status] == :success
+ commit_detail = user_project.repository.commits(result[:result], limit: 1).first
+ present commit_detail, with: ::API::Entities::RepoCommitDetail
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
+ desc 'Get a specific commit of a project' do
+ success ::API::Entities::RepoCommitDetail
+ failure [[404, '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
+ commit = user_project.commit(params[:sha])
+
+ not_found! "Commit" unless commit
+
+ present commit, with: ::API::Entities::RepoCommitDetail
+ end
+
+ desc 'Get the diff for a specific commit of a project' do
+ failure [[404, '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
+ commit = user_project.commit(params[:sha])
+
+ not_found! "Commit" unless commit
+
+ commit.raw_diffs.to_a
+ end
+
+ desc "Get a commit's comments" do
+ success ::API::Entities::CommitNote
+ failure [[404, '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
+ commit = user_project.commit(params[:sha])
+
+ not_found! 'Commit' unless commit
+ notes = Note.where(commit_id: commit.id).order(:created_at)
+
+ present paginate(notes), with: ::API::Entities::CommitNote
+ end
+
+ desc 'Cherry pick commit into a branch' do
+ detail 'This feature was introduced in GitLab 8.15'
+ success ::API::Entities::RepoCommit
+ end
+ params do
+ requires :sha, type: String, desc: 'A commit sha to be cherry picked'
+ requires :branch, type: String, desc: 'The name of the branch'
+ end
+ post ':id/repository/commits/:sha/cherry_pick' do
+ authorize! :push_code, user_project
+
+ commit = user_project.commit(params[:sha])
+ not_found!('Commit') unless commit
+
+ branch = user_project.repository.find_branch(params[:branch])
+ not_found!('Branch') unless branch
+
+ commit_params = {
+ commit: commit,
+ create_merge_request: false,
+ source_project: user_project,
+ source_branch: commit.cherry_pick_branch_name,
+ target_branch: params[:branch]
+ }
+
+ result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute
+
+ if result[:status] == :success
+ branch = user_project.repository.find_branch(params[:branch])
+ present user_project.repository.commit(branch.dereferenced_target), with: ::API::Entities::RepoCommit
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
+ desc 'Post comment to commit' do
+ success ::API::Entities::CommitNote
+ end
+ params do
+ requires :sha, type: String, regexp: /\A\h{6,40}\z/, desc: "The commit's SHA"
+ requires :note, type: String, desc: 'The text of the comment'
+ optional :path, type: String, desc: 'The file path'
+ given :path do
+ requires :line, type: Integer, desc: 'The line number'
+ requires :line_type, type: String, values: ['new', 'old'], default: 'new', desc: 'The type of the line'
+ end
+ end
+ post ':id/repository/commits/:sha/comments' do
+ commit = user_project.commit(params[:sha])
+ not_found! 'Commit' unless commit
+
+ opts = {
+ note: params[:note],
+ noteable_type: 'Commit',
+ commit_id: commit.id
+ }
+
+ if params[:path]
+ commit.raw_diffs(all_diffs: true).each do |diff|
+ next unless diff.new_path == params[:path]
+ lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
+
+ lines.each do |line|
+ next unless line.new_pos == params[:line] && line.type == params[:line_type]
+ break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+ end
+
+ break if opts[:line_code]
+ end
+
+ opts[:type] = LegacyDiffNote.name if opts[:line_code]
+ end
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+ if note.save
+ present note, with: ::API::Entities::CommitNote
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/files.rb b/lib/api/v3/files.rb
new file mode 100644
index 00000000000..4f8d58d37c8
--- /dev/null
+++ b/lib/api/v3/files.rb
@@ -0,0 +1,138 @@
+module API
+ module V3
+ class Files < Grape::API
+ helpers do
+ def commit_params(attrs)
+ {
+ file_path: attrs[:file_path],
+ start_branch: attrs[:branch],
+ target_branch: attrs[:branch],
+ commit_message: attrs[:commit_message],
+ file_content: attrs[:content],
+ file_content_encoding: attrs[:encoding],
+ author_email: attrs[:author_email],
+ author_name: attrs[:author_name]
+ }
+ end
+
+ def commit_response(attrs)
+ {
+ file_path: attrs[:file_path],
+ branch: attrs[:branch]
+ }
+ end
+
+ params :simple_file_params do
+ requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb'
+ requires :branch_name, type: String, desc: 'The name of branch'
+ requires :commit_message, type: String, desc: 'Commit Message'
+ optional :author_email, type: String, desc: 'The email of the author'
+ optional :author_name, type: String, desc: 'The name of the author'
+ end
+
+ params :extended_file_params do
+ use :simple_file_params
+ requires :content, type: String, desc: 'File content'
+ optional :encoding, type: String, values: %w[base64], desc: 'File encoding'
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Get a file from repository'
+ params do
+ requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb'
+ requires :ref, type: String, desc: 'The name of branch, tag, or commit'
+ end
+ get ":id/repository/files" do
+ authorize! :download_code, user_project
+
+ commit = user_project.commit(params[:ref])
+ not_found!('Commit') unless commit
+
+ repo = user_project.repository
+ blob = repo.blob_at(commit.sha, params[:file_path])
+ not_found!('File') unless blob
+
+ blob.load_all_data!(repo)
+ status(200)
+
+ {
+ file_name: blob.name,
+ file_path: blob.path,
+ size: blob.size,
+ encoding: "base64",
+ content: Base64.strict_encode64(blob.data),
+ ref: params[:ref],
+ blob_id: blob.id,
+ commit_id: commit.id,
+ last_commit_id: repo.last_commit_id_for_path(commit.sha, params[:file_path])
+ }
+ end
+
+ desc 'Create new file in repository'
+ params do
+ use :extended_file_params
+ end
+ post ":id/repository/files" do
+ authorize! :push_code, user_project
+
+ file_params = declared_params(include_missing: false)
+ file_params[:branch] = file_params.delete(:branch_name)
+
+ result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute
+
+ if result[:status] == :success
+ status(201)
+ commit_response(file_params)
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
+ desc 'Update existing file in repository'
+ params do
+ use :extended_file_params
+ end
+ put ":id/repository/files" do
+ authorize! :push_code, user_project
+
+ file_params = declared_params(include_missing: false)
+ file_params[:branch] = file_params.delete(:branch_name)
+
+ result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute
+
+ if result[:status] == :success
+ status(200)
+ commit_response(file_params)
+ else
+ http_status = result[:http_status] || 400
+ render_api_error!(result[:message], http_status)
+ end
+ end
+
+ desc 'Delete an existing file in repository'
+ params do
+ use :simple_file_params
+ end
+ delete ":id/repository/files" do
+ authorize! :push_code, user_project
+
+ file_params = declared_params(include_missing: false)
+ file_params[:branch] = file_params.delete(:branch_name)
+
+ result = ::Files::DestroyService.new(user_project, current_user, commit_params(file_params)).execute
+
+ if result[:status] == :success
+ status(200)
+ commit_response(file_params)
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index 081d45165e8..d0af09f0e1e 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -16,7 +16,8 @@ module API
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
- args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
+ # IssuesFinder expects iids
+ args[:iids] = args.delete(:iid) if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
@@ -148,9 +149,7 @@ module API
issue = ::Issues::CreateService.new(user_project,
current_user,
issue_params.merge(request: request, api: true)).execute
- if issue.spam?
- render_api_error!({ error: 'Spam detected' }, 400)
- end
+ render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::Entities::Issue, current_user: current_user, project: user_project
@@ -181,9 +180,13 @@ module API
params.delete(:updated_at)
end
+ update_params = declared_params(include_missing: false).merge(request: request, api: true)
+
issue = ::Issues::UpdateService.new(user_project,
current_user,
- declared_params(include_missing: false)).execute(issue)
+ update_params).execute(issue)
+
+ render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::Entities::Issue, current_user: current_user, project: user_project
diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb
new file mode 100644
index 00000000000..5c3261311bf
--- /dev/null
+++ b/lib/api/v3/labels.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ class Labels < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all labels of the project' do
+ success ::API::Entities::Label
+ end
+ get ':id/labels' do
+ present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/project_snippets.rb b/lib/api/v3/project_snippets.rb
index 9f95d4395fa..e03e941d30b 100644
--- a/lib/api/v3/project_snippets.rb
+++ b/lib/api/v3/project_snippets.rb
@@ -64,6 +64,8 @@ module API
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
@@ -93,12 +95,16 @@ module API
authorize! :update_project_snippet, snippet
snippet_params = declared_params(include_missing: false)
+ .merge(request: request, api: true)
+
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
UpdateSnippetService.new(user_project, current_user, snippet,
snippet_params).execute
- if snippet.persisted?
+ render_spam_error! if snippet.spam?
+
+ if snippet.valid?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
render_validation_error!(snippet)
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
new file mode 100644
index 00000000000..3549ea225ef
--- /dev/null
+++ b/lib/api/v3/repositories.rb
@@ -0,0 +1,55 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Repositories < Grape::API
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ desc 'Get a project repository tree' do
+ success ::API::Entities::RepoTreeObject
+ end
+ params do
+ optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+ optional :path, type: String, desc: 'The path of the tree'
+ optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+ end
+ get ':id/repository/tree' do
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.commit(ref)
+ not_found!('Tree') unless commit
+
+ tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
+
+ present tree.sorted_entries, with: ::API::Entities::RepoTreeObject
+ end
+
+ desc 'Get repository contributors' do
+ success ::API::Entities::Contributor
+ end
+ get ':id/repository/contributors' do
+ begin
+ present user_project.repository.contributors,
+ with: ::API::Entities::Contributor
+ rescue
+ not_found!
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/subscriptions.rb b/lib/api/v3/subscriptions.rb
new file mode 100644
index 00000000000..02a4157c26e
--- /dev/null
+++ b/lib/api/v3/subscriptions.rb
@@ -0,0 +1,53 @@
+module API
+ module V3
+ class Subscriptions < Grape::API
+ before { authenticate! }
+
+ subscribable_types = {
+ 'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
+ 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
+ 'issues' => proc { |id| find_project_issue(id) },
+ 'labels' => proc { |id| find_project_label(id) },
+ }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :subscribable_id, type: String, desc: 'The ID of a resource'
+ end
+ resource :projects do
+ subscribable_types.each do |type, finder|
+ type_singularized = type.singularize
+ entity_class = ::API::Entities.const_get(type_singularized.camelcase)
+
+ desc 'Subscribe to a resource' do
+ success entity_class
+ end
+ post ":id/#{type}/:subscribable_id/subscription" do
+ resource = instance_exec(params[:subscribable_id], &finder)
+
+ if resource.subscribed?(current_user, user_project)
+ not_modified!
+ else
+ resource.subscribe(current_user, user_project)
+ present resource, with: entity_class, current_user: current_user, project: user_project
+ end
+ end
+
+ desc 'Unsubscribe from a resource' do
+ success entity_class
+ end
+ delete ":id/#{type}/:subscribable_id/subscription" do
+ resource = instance_exec(params[:subscribable_id], &finder)
+
+ if !resource.subscribed?(current_user, user_project)
+ not_modified!
+ else
+ resource.unsubscribe(current_user, user_project)
+ present resource, with: entity_class, current_user: current_user, project: user_project
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/system_hooks.rb b/lib/api/v3/system_hooks.rb
new file mode 100644
index 00000000000..391510b9ee0
--- /dev/null
+++ b/lib/api/v3/system_hooks.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ class SystemHooks < Grape::API
+ before do
+ authenticate!
+ authenticated_as_admin!
+ end
+
+ resource :hooks do
+ desc 'Get the list of system hooks' do
+ success ::API::Entities::Hook
+ end
+ get do
+ present SystemHook.all, with: ::API::Entities::Hook
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
new file mode 100644
index 00000000000..016e3d86932
--- /dev/null
+++ b/lib/api/v3/tags.rb
@@ -0,0 +1,20 @@
+module API
+ module V3
+ class Tags < Grape::API
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository tags' do
+ success ::API::Entities::RepoTag
+ end
+ get ":id/repository/tags" do
+ tags = user_project.repository.tags.sort_by(&:name).reverse
+ present tags, with: ::API::Entities::RepoTag, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb
new file mode 100644
index 00000000000..4f9b5fe72a6
--- /dev/null
+++ b/lib/api/v3/todos.rb
@@ -0,0 +1,28 @@
+module API
+ module V3
+ class Todos < Grape::API
+ before { authenticate! }
+
+ resource :todos do
+ desc 'Mark a todo as done' do
+ success ::API::Entities::Todo
+ end
+ params do
+ 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)
+
+ present todo.reload, with: ::API::Entities::Todo, current_user: current_user
+ end
+
+ desc 'Mark all todos as done'
+ delete do
+ todos = TodosFinder.new(current_user, params).execute
+ TodoService.new.mark_todos_as_done(todos, current_user)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
new file mode 100644
index 00000000000..e05e457a5df
--- /dev/null
+++ b/lib/api/v3/users.rb
@@ -0,0 +1,96 @@
+module API
+ module V3
+ class Users < Grape::API
+ include PaginationParams
+
+ before do
+ allow_access_with_scope :read_user if request.get?
+ authenticate!
+ end
+
+ resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
+ desc 'Get the SSH keys of a specified user. Available only for admins.' do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present paginate(user.keys), with: ::API::Entities::SSHKey
+ end
+
+ desc 'Get the emails addresses of a specified user. Available only for admins.' do
+ success ::API::Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/emails' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present user.emails, with: ::API::Entities::Email
+ end
+
+ desc 'Block a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ put ':id/block' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ if !user.ldap_blocked?
+ user.block
+ else
+ forbidden!('LDAP blocked users cannot be modified by the API')
+ end
+ end
+
+ desc 'Unblock a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ put ':id/unblock' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ if user.ldap_blocked?
+ forbidden!('LDAP blocked users cannot be unblocked by the API')
+ else
+ user.activate
+ end
+ end
+ end
+
+ resource :user do
+ desc "Get the currently authenticated user's SSH keys" do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ use :pagination
+ end
+ get "keys" do
+ present current_user.keys, with: ::API::Entities::SSHKey
+ end
+
+ desc "Get the currently authenticated user's email addresses" do
+ success ::API::Entities::Email
+ end
+ get "emails" do
+ present current_user.emails, with: ::API::Entities::Email
+ end
+ end
+ end
+ end
+end