diff options
Diffstat (limited to 'lib/api')
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 |