Removed API endpoint and specs

parent 439adb96
...@@ -8,8 +8,6 @@ engines: ...@@ -8,8 +8,6 @@ engines:
languages: languages:
- ruby - ruby
- javascript - javascript
exclude_paths:
- "lib/api/v3/*"
ratings: ratings:
paths: paths:
- Gemfile.lock - Gemfile.lock
......
...@@ -173,7 +173,6 @@ Lint/UriEscapeUnescape: ...@@ -173,7 +173,6 @@ Lint/UriEscapeUnescape:
- 'spec/requests/api/files_spec.rb' - 'spec/requests/api/files_spec.rb'
- 'spec/requests/api/internal_spec.rb' - 'spec/requests/api/internal_spec.rb'
- 'spec/requests/api/issues_spec.rb' - 'spec/requests/api/issues_spec.rb'
- 'spec/requests/api/v3/issues_spec.rb'
# Offense count: 1 # Offense count: 1
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
...@@ -333,8 +332,6 @@ RSpec/ScatteredSetup: ...@@ -333,8 +332,6 @@ RSpec/ScatteredSetup:
- 'spec/lib/gitlab/bitbucket_import/importer_spec.rb' - 'spec/lib/gitlab/bitbucket_import/importer_spec.rb'
- 'spec/lib/gitlab/git/env_spec.rb' - 'spec/lib/gitlab/git/env_spec.rb'
- 'spec/requests/api/jobs_spec.rb' - 'spec/requests/api/jobs_spec.rb'
- 'spec/requests/api/v3/builds_spec.rb'
- 'spec/requests/api/v3/projects_spec.rb'
- 'spec/services/projects/create_service_spec.rb' - 'spec/services/projects/create_service_spec.rb'
# Offense count: 1 # Offense count: 1
...@@ -618,7 +615,6 @@ Style/OrAssignment: ...@@ -618,7 +615,6 @@ Style/OrAssignment:
Exclude: Exclude:
- 'app/models/concerns/token_authenticatable.rb' - 'app/models/concerns/token_authenticatable.rb'
- 'lib/api/commit_statuses.rb' - 'lib/api/commit_statuses.rb'
- 'lib/api/v3/members.rb'
- 'lib/gitlab/project_transfer.rb' - 'lib/gitlab/project_transfer.rb'
# Offense count: 50 # Offense count: 50
...@@ -781,7 +777,6 @@ Style/TernaryParentheses: ...@@ -781,7 +777,6 @@ Style/TernaryParentheses:
- 'app/finders/projects_finder.rb' - 'app/finders/projects_finder.rb'
- 'app/helpers/namespaces_helper.rb' - 'app/helpers/namespaces_helper.rb'
- 'features/support/capybara.rb' - 'features/support/capybara.rb'
- 'lib/api/v3/projects.rb'
- 'lib/gitlab/ci/build/artifacts/metadata/entry.rb' - 'lib/gitlab/ci/build/artifacts/metadata/entry.rb'
- 'spec/requests/api/pipeline_schedules_spec.rb' - 'spec/requests/api/pipeline_schedules_spec.rb'
- 'spec/support/capybara.rb' - 'spec/support/capybara.rb'
......
...@@ -360,17 +360,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -360,17 +360,6 @@ class ApplicationSetting < ActiveRecord::Base
Array(read_attribute(:repository_storages)) Array(read_attribute(:repository_storages))
end end
# DEPRECATED
# repository_storage is still required in the API. Remove in 9.0
# Still used in API v3
def repository_storage
repository_storages.first
end
def repository_storage=(value)
self.repository_storages = [value]
end
def default_project_visibility=(level) def default_project_visibility=(level)
super(Gitlab::VisibilityLevel.level_value(level)) super(Gitlab::VisibilityLevel.level_value(level))
end end
......
# Makes api V3 compatible with old project features permissions methods # Makes api V4 compatible with old project features permissions methods
# #
# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled # After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
# fields to a new table "project_features", support for the old fields is still needed in the API. # fields to a new table "project_features", support for the old fields is still needed in the API.
......
---
title: Removed API v3 from the codebase
merge_request: 18970
author:
type: removed
...@@ -106,8 +106,7 @@ between v3 and v4; please read the [v3 to v4 documentation](v3_to_v4.md) ...@@ -106,8 +106,7 @@ between v3 and v4; please read the [v3 to v4 documentation](v3_to_v4.md)
#### Current status #### Current status
Currently two API versions are available, v3 and v4. v3 is deprecated and Currently only API version v4 is available. Version v3 was removed in
will soon be removed. Deletion is scheduled for
[GitLab 11.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/36819). [GitLab 11.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/36819).
## Basic usage ## Basic usage
......
...@@ -23,7 +23,7 @@ POST /applications ...@@ -23,7 +23,7 @@ POST /applications
| `scopes` | string | yes | The scopes of the application | | `scopes` | string | yes | The scopes of the application |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v3/applications curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v4/applications
``` ```
Example response: Example response:
......
...@@ -123,7 +123,7 @@ POST /projects/:id/environments/:environment_id/stop ...@@ -123,7 +123,7 @@ POST /projects/:id/environments/:environment_id/stop
| `environment_id` | integer | yes | The ID of the environment | | `environment_id` | integer | yes | The ID of the environment |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1/stop" curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1/stop"
``` ```
Example response: Example response:
......
...@@ -1169,7 +1169,7 @@ The `file=` parameter must point to a file on your filesystem and be preceded ...@@ -1169,7 +1169,7 @@ The `file=` parameter must point to a file on your filesystem and be preceded
by `@`. For example: by `@`. For example:
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "file=@dk.png" https://gitlab.example.com/api/v3/projects/5/uploads curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "file=@dk.png" https://gitlab.example.com/api/v4/projects/5/uploads
``` ```
Returned object: Returned object:
......
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
Since GitLab 9.0, API V4 is the preferred version to be used. Since GitLab 9.0, API V4 is the preferred version to be used.
API V3 will be unsupported from GitLab 9.5, to be released on August API V3 was unsupported from GitLab 9.5, released on August
22, 2017. It will be removed in GitLab 9.5 or later. In the meantime, we advise 22, 2017. API v3 was removed in [GitLab 11.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/36819).
you to make any necessary changes to applications that use V3. The V3 API The V3 API documentation is still
documentation is still
[available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md). [available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md).
Below are the changes made between V3 and V4. Below are the changes made between V3 and V4.
......
...@@ -107,7 +107,7 @@ you will not get a shibboleth session! ...@@ -107,7 +107,7 @@ you will not get a shibboleth session!
RewriteEngine on RewriteEngine on
#Don't escape encoded characters in api requests #Don't escape encoded characters in api requests
RewriteCond %{REQUEST_URI} ^/api/v3/.* RewriteCond %{REQUEST_URI} ^/api/v4/.*
RewriteCond %{REQUEST_URI} !/Shibboleth.sso RewriteCond %{REQUEST_URI} !/Shibboleth.sso
RewriteCond %{REQUEST_URI} !/shibboleth-sp RewriteCond %{REQUEST_URI} !/shibboleth-sp
RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA,NE] RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA,NE]
......
...@@ -22,48 +22,14 @@ module API ...@@ -22,48 +22,14 @@ module API
allow_access_with_scope :api allow_access_with_scope :api
prefix :api prefix :api
version %w(v3 v4), using: :path
version 'v3', using: :path do version 'v3', using: :path do
helpers ::API::V3::Helpers route :any, '*path' do
helpers ::API::Helpers::CommonHelpers error!('API V3 is no longer supported. Use API V4 instead.', 410)
end
mount ::API::V3::AwardEmoji
mount ::API::V3::Boards
mount ::API::V3::Branches
mount ::API::V3::BroadcastMessages
mount ::API::V3::Builds
mount ::API::V3::Commits
mount ::API::V3::DeployKeys
mount ::API::V3::Environments
mount ::API::V3::Files
mount ::API::V3::Groups
mount ::API::V3::Issues
mount ::API::V3::Labels
mount ::API::V3::Members
mount ::API::V3::MergeRequestDiffs
mount ::API::V3::MergeRequests
mount ::API::V3::Notes
mount ::API::V3::Pipelines
mount ::API::V3::ProjectHooks
mount ::API::V3::Milestones
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
mount ::API::V3::Repositories
mount ::API::V3::Runners
mount ::API::V3::Services
mount ::API::V3::Settings
mount ::API::V3::Snippets
mount ::API::V3::Subscriptions
mount ::API::V3::SystemHooks
mount ::API::V3::Tags
mount ::API::V3::Templates
mount ::API::V3::Todos
mount ::API::V3::Triggers
mount ::API::V3::Users
mount ::API::V3::Variables
end end
version 'v4', using: :path
before do before do
header['X-Frame-Options'] = 'SAMEORIGIN' header['X-Frame-Options'] = 'SAMEORIGIN'
header['X-Content-Type-Options'] = 'nosniff' header['X-Content-Type-Options'] = 'nosniff'
......
module API
module V3
class AwardEmoji < Grape::API
include PaginationParams
before { authenticate! }
AWARDABLES = %w[issue merge_request snippet].freeze
resource :projects, requirements: { id: %r{[^/]+} } do
AWARDABLES.each do |awardable_type|
awardable_string = awardable_type.pluralize
awardable_id_string = "#{awardable_type}_id"
params do
requires :id, type: String, desc: 'The ID of a project'
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
[
":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
].each do |endpoint|
desc 'Get a list of project +awardable+ award emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
use :pagination
end
get endpoint do
if can_read_awardable?
awards = awardable.award_emoji
present paginate(awards), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
end
desc 'Get a specific award emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
requires :award_id, type: Integer, desc: 'The ID of the award'
end
get "#{endpoint}/:award_id" do
if can_read_awardable?
present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
end
desc 'Award a new Emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
requires :name, type: String, desc: 'The name of a award_emoji (without colons)'
end
post endpoint do
not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
award = awardable.create_award_emoji(params[:name], current_user)
if award.persisted?
present award, with: Entities::AwardEmoji
else
not_found!("Award Emoji #{award.errors.messages}")
end
end
desc 'Delete a +awardables+ award emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
requires :award_id, type: Integer, desc: 'The ID of an award emoji'
end
delete "#{endpoint}/:award_id" do
award = awardable.award_emoji.find(params[:award_id])
unauthorized! unless award.user == current_user || current_user.admin?
award.destroy
present award, with: Entities::AwardEmoji
end
end
end
end
helpers do
def can_read_awardable?
can?(current_user, read_ability(awardable), awardable)
end
def can_award_awardable?
awardable.user_can_award?(current_user, params[:name])
end
def awardable
@awardable ||=
begin
if params.include?(:note_id)
note_id = params.delete(:note_id)
awardable.notes.find(note_id)
elsif params.include?(:issue_id)
user_project.issues.find(params[:issue_id])
elsif params.include?(:merge_request_id)
user_project.merge_requests.find(params[:merge_request_id])
else
user_project.snippets.find(params[:snippet_id])
end
end
end
def read_ability(awardable)
case awardable
when Note
read_ability(awardable.noteable)
else
:"read_#{awardable.class.to_s.underscore}"
end
end
end
end
end
end
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, requirements: { id: %r{[^/]+} } 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
desc 'Delete a board list' do
detail 'This feature was introduced in 8.13'
success ::API::Entities::List
end
params do
requires :list_id, type: Integer, desc: 'The ID of a board list'
end
delete "/lists/:list_id" do
authorize!(:admin_list, user_project)
list = board_lists.find(params[:list_id])
service = ::Boards::Lists::DestroyService.new(user_project, current_user)
if service.execute(list)
present list, with: ::API::Entities::List
else
render_api_error!({ error: 'List could not be deleted!' }, 400)
end
end
end
end
end
end
end
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, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository branches' do
success ::API::Entities::Branch
end
get ":id/repository/branches" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42276')
repository = user_project.repository
branches = repository.branches.sort_by(&:name)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present branches, with: ::API::Entities::Branch, project: user_project, merged_branch_names: merged_branch_names
end
desc 'Delete a branch'
params do
requires :branch, type: String, desc: 'The name of the branch'
end
delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
authorize_push_project
result = DeleteBranchService.new(user_project, current_user)
.execute(params[:branch])
if result[:status] == :success
status(200)
{
branch_name: params[:branch]
}
else
render_api_error!(result[:message], result[:return_code])
end
end
desc 'Delete all merged branches'
delete ":id/repository/merged_branches" do
DeleteMergedBranchesService.new(user_project, current_user).async_execute
status(200)
end
desc 'Create branch' do
success ::API::Entities::Branch
end
params do
requires :branch_name, 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])
if result[:status] == :success
present result[:branch],
with: ::API::Entities::Branch,
project: user_project
else
render_api_error!(result[:message], 400)
end
end
end
end
end
end
module API
module V3
class BroadcastMessages < Grape::API
include PaginationParams
before { authenticate! }
before { authenticated_as_admin! }
resource :broadcast_messages do
helpers do
def find_message
BroadcastMessage.find(params[:id])
end
end
desc 'Delete a broadcast message' do
detail 'This feature was introduced in GitLab 8.12.'
success ::API::Entities::BroadcastMessage
end
params do
requires :id, type: Integer, desc: 'Broadcast message ID'
end
delete ':id' do
message = find_message
present message.destroy, with: ::API::Entities::BroadcastMessage
end
end
end
end
end
module API
module V3
class Builds < Grape::API
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
helpers do
params :optional_scope do
optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
values: %w(pending running failed success canceled skipped),
coerce_with: ->(scope) {
if scope.is_a?(String)
[scope]
elsif scope.is_a?(::Hash)
scope.values
else
['unknown']
end
}
end
end
desc 'Get a project builds' do
success ::API::V3::Entities::Build
end
params do
use :optional_scope
use :pagination
end
get ':id/builds' do
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project)
present paginate(builds), with: ::API::V3::Entities::Build
end
desc 'Get builds for a specific commit of a project' do
success ::API::V3::Entities::Build
end
params do
requires :sha, type: String, desc: 'The SHA id of a commit'
use :optional_scope
use :pagination
end
get ':id/repository/commits/:sha/builds' do
authorize_read_builds!
break not_found! unless user_project.commit(params[:sha])
pipelines = user_project.pipelines.where(sha: params[:sha])
builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: ::API::V3::Entities::Build
end
desc 'Get a specific build of a project' do
success ::API::V3::Entities::Build
end
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
get ':id/builds/:build_id' do
authorize_read_builds!
build = get_build!(params[:build_id])
present build, with: ::API::V3::Entities::Build
end
desc 'Download the artifacts file from build' do
detail 'This feature was introduced in GitLab 8.5'
end
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
get ':id/builds/:build_id/artifacts' do
authorize_read_builds!
build = get_build!(params[:build_id])
present_carrierwave_file!(build.artifacts_file)
end
desc 'Download the artifacts file from build' do
detail 'This feature was introduced in GitLab 8.10'
end
params do
requires :ref_name, type: String, desc: 'The ref from repository'
requires :job, type: String, desc: 'The name for the build'
end
get ':id/builds/artifacts/:ref_name/download',
requirements: { ref_name: /.+/ } do
authorize_read_builds!
builds = user_project.latest_successful_builds_for(params[:ref_name])
latest_build = builds.find_by!(name: params[:job])
present_carrierwave_file!(latest_build.artifacts_file)
end
# TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
desc 'Get a trace of a specific build of a project'
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
get ':id/builds/:build_id/trace' do
authorize_read_builds!
build = get_build!(params[:build_id])
header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
content_type 'text/plain'
env['api.format'] = :binary
trace = build.trace.raw
body trace
end
desc 'Cancel a specific build of a project' do
success ::API::V3::Entities::Build
end
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
post ':id/builds/:build_id/cancel' do
authorize_update_builds!
build = get_build!(params[:build_id])
authorize!(:update_build, build)
build.cancel
present build, with: ::API::V3::Entities::Build
end
desc 'Retry a specific build of a project' do
success ::API::V3::Entities::Build
end
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
post ':id/builds/:build_id/retry' do
authorize_update_builds!
build = get_build!(params[:build_id])
authorize!(:update_build, build)
break forbidden!('Build is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user)
present build, with: ::API::V3::Entities::Build
end
desc 'Erase build (remove artifacts and build trace)' do
success ::API::V3::Entities::Build
end
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
post ':id/builds/:build_id/erase' do
authorize_update_builds!
build = get_build!(params[:build_id])
authorize!(:erase_build, build)
break forbidden!('Build is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
present build, with: ::API::V3::Entities::Build
end
desc 'Keep the artifacts to prevent them from being deleted' do
success ::API::V3::Entities::Build
end
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
post ':id/builds/:build_id/artifacts/keep' do
authorize_update_builds!
build = get_build!(params[:build_id])
authorize!(:update_build, build)
break not_found!(build) unless build.artifacts?
build.keep_artifacts!
status 200
present build, with: ::API::V3::Entities::Build
end
desc 'Trigger a manual build' do
success ::API::V3::Entities::Build
detail 'This feature was added in GitLab 8.11'
end
params do
requires :build_id, type: Integer, desc: 'The ID of a Build'
end
post ":id/builds/:build_id/play" do
authorize_read_builds!
build = get_build!(params[:build_id])
authorize!(:update_build, build)
bad_request!("Unplayable Job") unless build.playable?
build.play(current_user)
status 200
present build, with: ::API::V3::Entities::Build
end
end
helpers do
def find_build(id)
user_project.builds.find_by(id: id.to_i)
end
def get_build!(id)
find_build(id) || not_found!
end
def filter_builds(builds, scope)
return builds if scope.nil? || scope.empty?
available_statuses = ::CommitStatus::AVAILABLE_STATUSES
unknown = scope - available_statuses
render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
builds.where(status: available_statuses && scope)
end
def authorize_read_builds!
authorize! :read_build, user_project
end
def authorize_update_builds!
authorize! :update_build, user_project
end
end
end
end
end
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, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a project repository commits' do
success ::API::Entities::Commit
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::Commit
end
desc 'Commit multiple file changes as one commit' do
success ::API::Entities::CommitDetail
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!(start_branch: branch, branch_name: branch)
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::CommitDetail
else
render_api_error!(result[:message], 400)
end
end
desc 'Get a specific commit of a project' do
success ::API::Entities::CommitDetail
failure [[404, 'Not Found']]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
end
get ":id/repository/commits/:sha", requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! "Commit" unless commit
present commit, with: ::API::Entities::CommitDetail, stats: params[:stats]
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", requirements: API::COMMIT_ENDPOINT_REQUIREMENTS 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', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
notes = commit.notes.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::Commit
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', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS 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,
start_branch: params[:branch],
branch_name: 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::Commit
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: %w(new old), default: 'new', desc: 'The type of the line'
end
end
post ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS 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(limits: false).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::Git.diff_line_code(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
module API
module V3
class DeployKeys < Grape::API
before { authenticate! }
helpers do
def add_deploy_keys_project(project, attrs = {})
project.deploy_keys_projects.create(attrs)
end
def find_by_deploy_key(project, key_id)
project.deploy_keys_projects.find_by!(deploy_key: key_id)
end
end
get "deploy_keys" do
authenticated_as_admin!
keys = DeployKey.all
present keys, with: ::API::Entities::SSHKey
end
params do
requires :id, type: String, desc: 'The ID of the project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
before { authorize_admin_project }
%w(keys deploy_keys).each do |path|
desc "Get a specific project's deploy keys" do
success ::API::Entities::DeployKeysProject
end
get ":id/#{path}" do
keys = user_project.deploy_keys_projects.preload(:deploy_key)
present keys, with: ::API::Entities::DeployKeysProject
end
desc 'Get single deploy key' do
success ::API::Entities::DeployKeysProject
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
get ":id/#{path}/:key_id" do
key = find_by_deploy_key(user_project, params[:key_id])
present key, with: ::API::Entities::DeployKeysProject
end
desc 'Add new deploy key to currently authenticated user' do
success ::API::Entities::DeployKeysProject
end
params do
requires :key, type: String, desc: 'The new deploy key'
requires :title, type: String, desc: 'The name of the deploy key'
optional :can_push, type: Boolean, desc: "Can deploy key push to the project's repository"
end
post ":id/#{path}" do
params[:key].strip!
# Check for an existing key joined to this project
key = user_project.deploy_keys_projects
.joins(:deploy_key)
.find_by(keys: { key: params[:key] })
if key
present key, with: ::API::Entities::DeployKeysProject
break
end
# Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key
added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
present added_key, with: ::API::Entities::DeployKeysProject
break
end
# Create a new deploy key
key_attributes = { can_push: !!params[:can_push],
deploy_key_attributes: declared_params.except(:can_push) }
key = add_deploy_keys_project(user_project, key_attributes)
if key.valid?
present key, with: ::API::Entities::DeployKeysProject
else
render_validation_error!(key)
end
end
desc 'Enable a deploy key for a project' do
detail 'This feature was added in GitLab 8.11'
success ::API::Entities::SSHKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
post ":id/#{path}/:key_id/enable" do
key = ::Projects::EnableDeployKeyService.new(user_project,
current_user, declared_params).execute
if key
present key, with: ::API::Entities::SSHKey
else
not_found!('Deploy Key')
end
end
desc 'Disable a deploy key for a project' do
detail 'This feature was added in GitLab 8.11'
success ::API::Entities::SSHKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/#{path}/: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: ::API::Entities::SSHKey
end
desc 'Delete deploy key for a project' do
success Key
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/#{path}/: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
end
end
end
end
end
end
module API
module V3
# Deployments RESTful API endpoints
class Deployments < Grape::API
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The project ID'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all deployments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success ::API::V3::Deployments
end
params do
use :pagination
end
get ':id/deployments' do
authorize! :read_deployment, user_project
present paginate(user_project.deployments), with: ::API::V3::Deployments
end
desc 'Gets a specific deployment' do
detail 'This feature was introduced in GitLab 8.11.'
success ::API::V3::Deployments
end
params do
requires :deployment_id, type: Integer, desc: 'The deployment ID'
end
get ':id/deployments/:deployment_id' do
authorize! :read_deployment, user_project
deployment = user_project.deployments.find(params[:deployment_id])
present deployment, with: ::API::V3::Deployments
end
end
end
end
end
This diff is collapsed.
module API
module V3
class Environments < Grape::API
include ::API::Helpers::CustomValidators
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The project ID'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all environments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
end
params do
use :pagination
end
get ':id/environments' do
authorize! :read_environment, user_project
present paginate(user_project.environments), with: Entities::Environment
end
desc 'Creates a new environment' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
end
params do
requires :name, type: String, desc: 'The name of the environment to be created'
optional :external_url, type: String, desc: 'URL on which this deployment is viewable'
optional :slug, absence: { message: "is automatically generated and cannot be changed" }
end
post ':id/environments' do
authorize! :create_environment, user_project
environment = user_project.environments.create(declared_params)
if environment.persisted?
present environment, with: Entities::Environment
else
render_validation_error!(environment)
end
end
desc 'Updates an existing environment' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
end
params do
requires :environment_id, type: Integer, desc: 'The environment ID'
optional :name, type: String, desc: 'The new environment name'
optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable'
optional :slug, absence: { message: "is automatically generated and cannot be changed" }
end
put ':id/environments/:environment_id' do
authorize! :update_environment, user_project
environment = user_project.environments.find(params[:environment_id])
update_params = declared_params(include_missing: false).extract!(:name, :external_url)
if environment.update(update_params)
present environment, with: Entities::Environment
else
render_validation_error!(environment)
end
end
desc 'Deletes an existing environment' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
end
params do
requires :environment_id, type: Integer, desc: 'The environment ID'
end
delete ':id/environments/:environment_id' do
authorize! :update_environment, user_project
environment = user_project.environments.find(params[:environment_id])
present environment.destroy, with: Entities::Environment
end
end
end
end
end
module API
module V3
class Files < Grape::API
helpers do
def commit_params(attrs)
{
file_path: attrs[:file_path],
start_branch: attrs[:branch],
branch_name: 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, requirements: { id: %r{[^/]+} } 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!
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::DeleteService.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
module API
module V3
class Groups < Grape::API
include PaginationParams
before { authenticate! }
helpers do
params :optional_params do
optional :description, type: String, desc: 'The description of the group'
optional :visibility_level, type: Integer, desc: 'The visibility level of the group'
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
end
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
def present_groups(groups, options = {})
options = options.reverse_merge(
with: Entities::Group,
current_user: current_user
)
groups = groups.with_statistics if options[:statistics]
present paginate(groups), options
end
end
resource :groups do
desc 'Get a groups list' do
success Entities::Group
end
params do
use :statistics_params
optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
optional :search, type: String, desc: 'Search for a specific group'
optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path'
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get do
groups = if current_user.admin
Group.all
elsif params[:all_available]
GroupsFinder.new(current_user).execute
else
current_user.groups
end
groups = groups.search(params[:search]) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
present_groups groups, statistics: params[:statistics] && current_user.admin?
end
desc 'Get list of owned groups for authenticated user' do
success Entities::Group
end
params do
use :pagination
use :statistics_params
end
get '/owned' do
present_groups current_user.owned_groups, statistics: params[:statistics]
end
desc 'Create a group. Available only for users who can create groups.' do
success Entities::Group
end
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
if ::Group.supports_nested_groups?
optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
end
use :optional_params
end
post do
authorize! :create_group
group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
if group.persisted?
present group, with: Entities::Group, current_user: current_user
else
render_api_error!("Failed to save group #{group.errors.messages}", 400)
end
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Update a group. Available only for users who can administrate groups.' do
success Entities::Group
end
params do
optional :name, type: String, desc: 'The name of the group'
optional :path, type: String, desc: 'The path of the group'
use :optional_params
at_least_one_of :name, :path, :description, :visibility_level,
:lfs_enabled, :request_access_enabled
end
put ':id' do
group = find_group!(params[:id])
authorize! :admin_group, group
if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
present group, with: Entities::GroupDetail, current_user: current_user
else
render_validation_error!(group)
end
end
desc 'Get a single group, with containing projects.' do
success Entities::GroupDetail
end
get ":id" do
group = find_group!(params[:id])
present group, with: Entities::GroupDetail, current_user: current_user
end
desc 'Remove a group.'
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
::Groups::DestroyService.new(group, current_user).async_execute
accepted!
end
desc 'Get a list of projects in this group.' do
success Entities::Project
end
params do
optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
optional :visibility, type: String, values: %w[public internal private],
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
default: 'created_at', desc: 'Return projects ordered by field'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return projects sorted in ascending and descending order'
optional :simple, type: Boolean, default: false,
desc: 'Return only the ID, URL, name, and path of each project'
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
use :pagination
end
get ":id/projects" do
group = find_group!(params[:id])
projects = GroupProjectsFinder.new(group: group, current_user: current_user).execute
projects = filter_projects(projects)
entity = params[:simple] ? ::API::Entities::BasicProjectDetails : Entities::Project
present paginate(projects), with: entity, current_user: current_user
end
desc 'Transfer a project to the group namespace. Available only for admin.' do
success Entities::GroupDetail
end
params do
requires :project_id, type: String, desc: 'The ID or path of the project'
end
post ":id/projects/:project_id", requirements: { project_id: /.+/ } do
authenticated_as_admin!
group = find_group!(params[:id])
project = find_project!(params[:project_id])
result = ::Projects::TransferService.new(project, current_user).execute(group)
if result
present group, with: Entities::GroupDetail, current_user: current_user
else
render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end
end
end
end
end
end
module API
module V3
module Helpers
def find_project_issue(id)
IssuesFinder.new(current_user, project_id: user_project.id).find(id)
end
def find_project_merge_request(id)
MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
end
def find_merge_request_with_access(id, access_level = :read_merge_request)
merge_request = user_project.merge_requests.find(id)
authorize! access_level, merge_request
merge_request
end
# project helpers
def filter_projects(projects)
if params[:membership]
projects = projects.merge(current_user.authorized_projects)
end
if params[:owned]
projects = projects.merge(current_user.owned_projects)
end
if params[:starred]
projects = projects.merge(current_user.starred_projects)
end
if params[:search].present?
projects = projects.search(params[:search])
end
if params[:visibility].present?
projects = projects.where(visibility_level: Gitlab::VisibilityLevel.level_value(params[:visibility]))
end
unless params[:archived].nil?
projects = projects.where(archived: to_boolean(params[:archived]))
end
projects.reorder(params[:order_by] => params[:sort])
end
end
end
end
module API
module V3
class Issues < Grape::API
include PaginationParams
before { authenticate! }
helpers do
def find_issues(args = {})
args = params.merge(args)
args = convert_parameters_from_legacy_format(args)
args.delete(:id)
args[:milestone_title] = args.delete(:milestone)
match_all_labels = args.delete(:match_all_labels)
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
# IssuesFinder expects iids
args[:iids] = args.delete(:iid) if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
if !match_all_labels && labels.present?
issues = issues.includes(:labels).where('labels.title' => labels.split(','))
end
issues.reorder(args[:order_by] => args[:sort])
end
params :issues_params do
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return issues for a specific milestone'
use :pagination
end
params :issue_params do
optional :description, type: String, desc: 'The description of an issue'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
end
end
resource :issues do
desc "Get currently authenticated user's issues" do
success ::API::V3::Entities::Issue
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
use :issues_params
end
get do
issues = find_issues(scope: 'authored')
present paginate(issues), with: ::API::V3::Entities::Issue, current_user: current_user
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Get a list of group issues' do
success ::API::V3::Entities::Issue
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
use :issues_params
end
get ":id/issues" do
group = find_group!(params[:id])
issues = find_issues(group_id: group.id, match_all_labels: true)
present paginate(issues), with: ::API::V3::Entities::Issue, current_user: current_user
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
detail 'iid filter is deprecated have been removed on V4'
success ::API::V3::Entities::Issue
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :iid, type: Integer, desc: 'Return the issue having the given `iid`'
use :issues_params
end
get ":id/issues" do
project = find_project!(params[:id])
issues = find_issues(project_id: project.id)
present paginate(issues), with: ::API::V3::Entities::Issue, current_user: current_user, project: user_project
end
desc 'Get a single project issue' do
success ::API::V3::Entities::Issue
end
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
end
get ":id/issues/:issue_id" do
issue = find_project_issue(params[:issue_id])
present issue, with: ::API::V3::Entities::Issue, current_user: current_user, project: user_project
end
desc 'Create a new project issue' do
success ::API::V3::Entities::Issue
end
params do
requires :title, type: String, desc: 'The title of an issue'
optional :created_at, type: DateTime,
desc: 'Date time when the issue was created. Available only for admins and project owners.'
optional :merge_request_for_resolving_discussions, type: Integer,
desc: 'The IID of a merge request for which to resolve discussions'
use :issue_params
end
post ':id/issues' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42131')
# Setting created_at time only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user
params.delete(:created_at)
end
issue_params = declared_params(include_missing: false)
issue_params = issue_params.merge(merge_request_to_resolve_discussions_of: issue_params.delete(:merge_request_for_resolving_discussions))
issue_params = convert_parameters_from_legacy_format(issue_params)
issue = ::Issues::CreateService.new(user_project,
current_user,
issue_params.merge(request: request, api: true)).execute
render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::V3::Entities::Issue, current_user: current_user, project: user_project
else
render_validation_error!(issue)
end
end
desc 'Update an existing issue' do
success ::API::V3::Entities::Issue
end
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
optional :title, type: String, desc: 'The title of an issue'
optional :updated_at, type: DateTime,
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
use :issue_params
at_least_one_of :title, :description, :assignee_id, :milestone_id,
:labels, :created_at, :due_date, :confidential, :state_event
end
put ':id/issues/:issue_id' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42132')
issue = user_project.issues.find(params.delete(:issue_id))
authorize! :update_issue, issue
# Setting created_at time only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user
params.delete(:updated_at)
end
update_params = declared_params(include_missing: false).merge(request: request, api: true)
update_params = convert_parameters_from_legacy_format(update_params)
issue = ::Issues::UpdateService.new(user_project,
current_user,
update_params).execute(issue)
render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::V3::Entities::Issue, current_user: current_user, project: user_project
else
render_validation_error!(issue)
end
end
desc 'Move an existing issue' do
success ::API::V3::Entities::Issue
end
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
post ':id/issues/:issue_id/move' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42133')
issue = user_project.issues.find_by(id: params[:issue_id])
not_found!('Issue') unless issue
new_project = Project.find_by(id: params[:to_project_id])
not_found!('Project') unless new_project
begin
issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
present issue, with: ::API::V3::Entities::Issue, current_user: current_user, project: user_project
rescue ::Issues::MoveService::MoveError => error
render_api_error!(error.message, 400)
end
end
desc 'Delete a project issue'
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
end
delete ":id/issues/:issue_id" do
issue = user_project.issues.find_by(id: params[:issue_id])
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
status(200)
issue.destroy
end
end
end
end
end
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, requirements: { id: %r{[^/]+} } do
desc 'Get all labels of the project' do
success ::API::Entities::Label
end
get ':id/labels' do
present available_labels_for(user_project), with: ::API::Entities::Label, current_user: current_user, project: user_project
end
desc 'Delete an existing label' do
success ::API::Entities::Label
end
params do
requires :name, type: String, desc: 'The name of the label to be deleted'
end
delete ':id/labels' do
authorize! :admin_label, user_project
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
present label.destroy, with: ::API::Entities::Label, current_user: current_user, project: user_project
end
end
end
end
end
module API
module V3
class Members < Grape::API
include PaginationParams
before { authenticate! }
helpers ::API::Helpers::MembersHelpers
%w[group project].each do |source_type|
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
desc 'Gets a list of group or project members viewable by the authenticated user.' do
success ::API::Entities::Member
end
params do
optional :query, type: String, desc: 'A query string to search for members'
use :pagination
end
get ":id/members" do
source = find_source(source_type, params[:id])
members = source.members.where.not(user_id: nil).includes(:user)
members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
members = paginate(members)
present members, with: ::API::Entities::Member
end
desc 'Gets a member of a group or project.' do
success ::API::Entities::Member
end
params do
requires :user_id, type: Integer, desc: 'The user ID of the member'
end
get ":id/members/:user_id" do
source = find_source(source_type, params[:id])
members = source.members
member = members.find_by!(user_id: params[:user_id])
present member, with: ::API::Entities::Member
end
desc 'Adds a member to a group or project.' do
success ::API::Entities::Member
end
params do
requires :user_id, type: Integer, desc: 'The user ID of the new member'
requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
end
post ":id/members" do
source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source)
member = source.members.find_by(user_id: params[:user_id])
# We need this explicit check because `source.add_user` doesn't
# currently return the member created so it would return 201 even if
# the member already existed...
# The `source_type == 'group'` check is to ensure back-compatibility
# but 409 behavior should be used for both project and group members in 9.0!
conflict!('Member already exists') if source_type == 'group' && member
unless member
member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
end
if member.persisted? && member.valid?
present member, with: ::API::Entities::Member
else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member)
end
end
desc 'Updates a member of a group or project.' do
success ::API::Entities::Member
end
params do
requires :user_id, type: Integer, desc: 'The user ID of the new member'
requires :access_level, type: Integer, desc: 'A valid access level'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
end
put ":id/members/:user_id" do
source = find_source(source_type, params.delete(:id))
authorize_admin_source!(source_type, source)
member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false))
present member, with: ::API::Entities::Member
else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member)
end
end
desc 'Removes a user from a group or project.'
params do
requires :user_id, type: Integer, desc: 'The user ID of the member'
end
delete ":id/members/:user_id" do
source = find_source(source_type, params[:id])
# This is to ensure back-compatibility but find_by! should be used
# in that casse in 9.0!
member = source.members.find_by(user_id: params[:user_id])
# This is to ensure back-compatibility but this should be removed in
# favor of find_by! in 9.0!
not_found!("Member: user_id:#{params[:user_id]}") if source_type == 'group' && member.nil?
# This is to ensure back-compatibility but 204 behavior should be used
# for all DELETE endpoints in 9.0!
if member.nil?
status(200 )
{ message: "Access revoked", id: params[:user_id].to_i }
else
::Members::DestroyService.new(current_user).execute(member)
present member, with: ::API::Entities::Member
end
end
end
end
end
end
end
module API
module V3
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a list of merge request diff versions' do
detail 'This feature was introduced in GitLab 8.12.'
success ::API::Entities::MergeRequestDiff
end
params do
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
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.order_id_desc, with: ::API::Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
detail 'This feature was introduced in GitLab 8.12.'
success ::API::Entities::MergeRequestDiffFull
end
params do
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
get ":id/merge_requests/:merge_request_id/versions/:version_id" do
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request.merge_request_diffs.find(params[:version_id]), with: ::API::Entities::MergeRequestDiffFull
end
end
end
end
end
This diff is collapsed.
module API
module V3
class Milestones < Grape::API
include PaginationParams
before { authenticate! }
helpers do
def filter_milestones_state(milestones, state)
case state
when 'active' then milestones.active
when 'closed' then milestones.closed
else milestones
end
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a list of project milestones' do
success ::API::Entities::Milestone
end
params do
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
optional :iid, type: Array[Integer], desc: 'The IID of the milestone'
use :pagination
end
get ":id/milestones" do
authorize! :read_milestone, user_project
milestones = user_project.milestones
milestones = filter_milestones_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present?
milestones = milestones.order_id_desc
present paginate(milestones), with: ::API::Entities::Milestone
end
desc 'Get all issues for a single project milestone' do
success ::API::V3::Entities::Issue
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
use :pagination
end
get ':id/milestones/:milestone_id/issues' do
authorize! :read_milestone, user_project
milestone = user_project.milestones.find(params[:milestone_id])
finder_params = {
project_id: user_project.id,
milestone_title: milestone.title
}
issues = IssuesFinder.new(current_user, finder_params).execute
present paginate(issues), with: ::API::V3::Entities::Issue, current_user: current_user, project: user_project
end
end
end
end
end
module API
module V3
class Notes < Grape::API
include PaginationParams
before { authenticate! }
NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
NOTEABLE_TYPES.each do |noteable_type|
noteables_str = noteable_type.to_s.underscore.pluralize
desc 'Get a list of project +noteable+ notes' do
success ::API::V3::Entities::Note
end
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
use :pagination
end
get ":id/#{noteables_str}/:noteable_id/notes" do
noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
if can?(current_user, noteable_read_ability_name(noteable), noteable)
# We exclude notes that are cross-references and that cannot be viewed
# by the current user. By doing this exclusion at this level and not
# at the DB query level (which we cannot in that case), the current
# page can have less elements than :per_page even if
# there's more than one page.
notes =
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
# array returned, but this is really a edge-case.
paginate(noteable.notes)
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: ::API::V3::Entities::Note
else
not_found!("Notes")
end
end
desc 'Get a single +noteable+ note' do
success ::API::V3::Entities::Note
end
params do
requires :note_id, type: Integer, desc: 'The ID of a note'
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
end
get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
note = noteable.notes.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
if can_read_note
present note, with: ::API::V3::Entities::Note
else
not_found!("Note")
end
end
desc 'Create a new +noteable+ note' do
success ::API::V3::Entities::Note
end
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
requires :body, type: String, desc: 'The content of a note'
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_str}/:noteable_id/notes" do
opts = {
note: params[:body],
noteable_type: noteables_str.classify,
noteable_id: params[:noteable_id]
}
noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
if can?(current_user, noteable_read_ability_name(noteable), noteable)
if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.valid?
present note, with: ::API::V3::Entities.const_get(note.class.name)
else
not_found!("Note #{note.errors.messages}")
end
else
not_found!("Note")
end
end
desc 'Update an existing +noteable+ note' do
success ::API::V3::Entities::Note
end
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
requires :note_id, type: Integer, desc: 'The ID of a note'
requires :body, type: String, desc: 'The content of a note'
end
put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
opts = {
note: params[:body]
}
note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note)
if note.valid?
present note, with: ::API::V3::Entities::Note
else
render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
desc 'Delete a +noteable+ note' do
success ::API::V3::Entities::Note
end
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
requires :note_id, type: Integer, desc: 'The ID of a note'
end
delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
::Notes::DestroyService.new(user_project, current_user).execute(note)
present note, with: ::API::V3::Entities::Note
end
end
end
helpers do
def noteable_read_ability_name(noteable)
"read_#{noteable.class.to_s.underscore}".to_sym
end
end
end
end
end
module API
module V3
class Pipelines < Grape::API
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The project ID'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all Pipelines of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success ::API::Entities::Pipeline
end
params do
use :pagination
optional :scope, type: String, values: %w(running branches tags),
desc: 'Either running, branches, or tags'
end
get ':id/pipelines' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42123')
authorize! :read_pipeline, user_project
pipelines = PipelinesFinder.new(user_project, scope: params[:scope]).execute
present paginate(pipelines), with: ::API::Entities::Pipeline
end
end
helpers do
def pipeline
@pipeline ||= user_project.pipelines.find(params[:pipeline_id])
end
end
end
end
end
module API
module V3
class ProjectHooks < Grape::API
include PaginationParams
before { authenticate! }
before { authorize_admin_project }
helpers do
params :project_hook_properties do
requires :url, type: String, desc: "The URL to send the request to"
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
optional :confidential_issues_events, type: Boolean, desc: "Trigger hook on confidential issues events"
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
optional :build_events, type: Boolean, desc: "Trigger hook on build events"
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get project hooks' do
success ::API::V3::Entities::ProjectHook
end
params do
use :pagination
end
get ":id/hooks" do
hooks = paginate user_project.hooks
present hooks, with: ::API::V3::Entities::ProjectHook
end
desc 'Get a project hook' do
success ::API::V3::Entities::ProjectHook
end
params do
requires :hook_id, type: Integer, desc: 'The ID of a project hook'
end
get ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params[:hook_id])
present hook, with: ::API::V3::Entities::ProjectHook
end
desc 'Add hook to project' do
success ::API::V3::Entities::ProjectHook
end
params do
use :project_hook_properties
end
post ":id/hooks" do
attrs = declared_params(include_missing: false)
attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events)
hook = user_project.hooks.new(attrs)
if hook.save
present hook, with: ::API::V3::Entities::ProjectHook
else
error!("Invalid url given", 422) if hook.errors[:url].present?
not_found!("Project hook #{hook.errors.messages}")
end
end
desc 'Update an existing project hook' do
success ::API::V3::Entities::ProjectHook
end
params do
requires :hook_id, type: Integer, desc: "The ID of the hook to update"
use :project_hook_properties
end
put ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
attrs = declared_params(include_missing: false)
attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events)
if hook.update_attributes(attrs)
present hook, with: ::API::V3::Entities::ProjectHook
else
error!("Invalid url given", 422) if hook.errors[:url].present?
not_found!("Project hook #{hook.errors.messages}")
end
end
desc 'Deletes project hook' do
success ::API::V3::Entities::ProjectHook
end
params do
requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
end
delete ":id/hooks/:hook_id" do
begin
present user_project.hooks.destroy(params[:hook_id]), with: ::API::V3::Entities::ProjectHook
rescue
# ProjectHook can raise Error if hook_id not found
not_found!("Error deleting hook #{params[:hook_id]}")
end
end
end
end
end
end
module API
module V3
class ProjectSnippets < Grape::API
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
error!(errors[:project_access], 422)
end
not_found!
end
def snippets_for_current_user
SnippetsFinder.new(current_user, project: user_project).execute
end
end
desc 'Get all project snippets' do
success ::API::V3::Entities::ProjectSnippet
end
params do
use :pagination
end
get ":id/snippets" do
present paginate(snippets_for_current_user), with: ::API::V3::Entities::ProjectSnippet
end
desc 'Get a single project snippet' do
success ::API::V3::Entities::ProjectSnippet
end
params do
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
end
get ":id/snippets/:snippet_id" do
snippet = snippets_for_current_user.find(params[:snippet_id])
present snippet, with: ::API::V3::Entities::ProjectSnippet
end
desc 'Create a new project snippet' do
success ::API::V3::Entities::ProjectSnippet
end
params do
requires :title, type: String, desc: 'The title of the snippet'
requires :file_name, type: String, desc: 'The file name of the snippet'
requires :code, type: String, desc: 'The content of the snippet'
requires :visibility_level, type: Integer,
values: [Gitlab::VisibilityLevel::PRIVATE,
Gitlab::VisibilityLevel::INTERNAL,
Gitlab::VisibilityLevel::PUBLIC],
desc: 'The visibility level of the snippet'
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
snippet_params = declared_params.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code)
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
render_spam_error! if snippet.spam?
if snippet.persisted?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
render_validation_error!(snippet)
end
end
desc 'Update an existing project snippet' do
success ::API::V3::Entities::ProjectSnippet
end
params do
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
optional :title, type: String, desc: 'The title of the snippet'
optional :file_name, type: String, desc: 'The file name of the snippet'
optional :code, type: String, desc: 'The content of the snippet'
optional :visibility_level, type: Integer,
values: [Gitlab::VisibilityLevel::PRIVATE,
Gitlab::VisibilityLevel::INTERNAL,
Gitlab::VisibilityLevel::PUBLIC],
desc: 'The visibility level of the snippet'
at_least_one_of :title, :file_name, :code, :visibility_level
end
put ":id/snippets/:snippet_id" do
snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id))
not_found!('Snippet') unless snippet
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
render_spam_error! if snippet.spam?
if snippet.valid?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
render_validation_error!(snippet)
end
end
desc 'Delete a project snippet'
params do
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
end
delete ":id/snippets/:snippet_id" do
snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet
snippet.destroy
status(200)
end
desc 'Get a raw project snippet'
params do
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
end
get ":id/snippets/:snippet_id/raw" do
snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
not_found!('Snippet') unless snippet
env['api.format'] = :txt
content_type 'text/plain'
present snippet.content
end
end
end
end
end
This diff is collapsed.
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, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS 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::TreeObject
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::TreeObject
end
desc 'Get a raw file contents'
params do
requires :sha, type: String, desc: 'The commit, branch name, or tag name'
requires :filepath, type: String, desc: 'The path to the file to display'
end
get [":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob"], requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
repo = user_project.repository
commit = repo.commit(params[:sha])
not_found! "Commit" unless commit
blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
not_found! "File" unless blob
send_git_blob repo, blob
end
desc 'Get a raw blob contents by blob sha'
params do
requires :sha, type: String, desc: 'The commit, branch name, or tag name'
end
get ':id/repository/raw_blobs/:sha', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
repo = user_project.repository
begin
blob = Gitlab::Git::Blob.raw(repo, params[:sha])
rescue
not_found! 'Blob'
end
not_found! 'Blob' unless blob
send_git_blob repo, blob
end
desc 'Get an archive of the repository'
params do
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
optional :format, type: String, desc: 'The archive format'
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue
not_found!('File')
end
end
desc 'Compare two branches, tags, or commits' do
success ::API::Entities::Compare
end
params do
requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison'
requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison'
end
get ':id/repository/compare' do
compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to])
present compare, with: ::API::Entities::Compare
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
module API
module V3
class Runners < Grape::API
include PaginationParams
before { authenticate! }
resource :runners do
desc 'Remove a runner' do
success ::API::Entities::Runner
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
delete ':id' do
runner = Ci::Runner.find(params[:id])
not_found!('Runner') unless runner
authenticate_delete_runner!(runner)
status(200)
runner.destroy
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
before { authorize_admin_project }
desc "Disable project's runner" do
success ::API::Entities::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
delete ':id/runners/:runner_id' do
runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
not_found!('Runner') unless runner_project
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
runner_project.destroy
present runner, with: ::API::Entities::Runner
end
end
helpers do
def authenticate_delete_runner!(runner)
return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def user_can_access_runner?(runner)
current_user.ci_owned_runners.exists?(runner.id)
end
end
end
end
end
This diff is collapsed.
This diff is collapsed.
module API
module V3
class Snippets < Grape::API
include PaginationParams
before { authenticate! }
resource :snippets do
helpers do
def snippets_for_current_user
SnippetsFinder.new(current_user, author: current_user).execute
end
def public_snippets
SnippetsFinder.new(current_user, visibility: Snippet::PUBLIC).execute
end
end
desc 'Get a snippets list for authenticated user' do
detail 'This feature was introduced in GitLab 8.15.'
success ::API::Entities::PersonalSnippet
end
params do
use :pagination
end
get do
present paginate(snippets_for_current_user), with: ::API::Entities::PersonalSnippet
end
desc 'List all public snippets current_user has access to' do
detail 'This feature was introduced in GitLab 8.15.'
success ::API::Entities::PersonalSnippet
end
params do
use :pagination
end
get 'public' do
present paginate(public_snippets), with: ::API::Entities::PersonalSnippet
end
desc 'Get a single snippet' do
detail 'This feature was introduced in GitLab 8.15.'
success ::API::Entities::PersonalSnippet
end
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
end
get ':id' do
snippet = snippets_for_current_user.find(params[:id])
present snippet, with: ::API::Entities::PersonalSnippet
end
desc 'Create new snippet' do
detail 'This feature was introduced in GitLab 8.15.'
success ::API::Entities::PersonalSnippet
end
params do
requires :title, type: String, desc: 'The title of a snippet'
requires :file_name, type: String, desc: 'The name of a snippet file'
requires :content, type: String, desc: 'The content of a snippet'
optional :visibility_level, type: Integer,
values: Gitlab::VisibilityLevel.values,
default: Gitlab::VisibilityLevel::INTERNAL,
desc: 'The visibility level of the snippet'
end
post do
attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
if snippet.persisted?
present snippet, with: ::API::Entities::PersonalSnippet
else
render_validation_error!(snippet)
end
end
desc 'Update an existing snippet' do
detail 'This feature was introduced in GitLab 8.15.'
success ::API::Entities::PersonalSnippet
end
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
optional :title, type: String, desc: 'The title of a snippet'
optional :file_name, type: String, desc: 'The name of a snippet file'
optional :content, type: String, desc: 'The content of a snippet'
optional :visibility_level, type: Integer,
values: Gitlab::VisibilityLevel.values,
desc: 'The visibility level of the snippet'
at_least_one_of :title, :file_name, :content, :visibility_level
end
put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
break not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet
attrs = declared_params(include_missing: false)
UpdateSnippetService.new(nil, current_user, snippet, attrs).execute
if snippet.persisted?
present snippet, with: ::API::Entities::PersonalSnippet
else
render_validation_error!(snippet)
end
end
desc 'Remove snippet' do
detail 'This feature was introduced in GitLab 8.15.'
success ::API::Entities::PersonalSnippet
end
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
end
delete ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
break not_found!('Snippet') unless snippet
authorize! :destroy_personal_snippet, snippet
snippet.destroy
no_content!
end
desc 'Get a raw snippet' do
detail 'This feature was introduced in GitLab 8.15.'
end
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
end
get ":id/raw" do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
break not_found!('Snippet') unless snippet
env['api.format'] = :txt
content_type 'text/plain'
present snippet.content
end
end
end
end
end
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, requirements: { id: %r{[^/]+} } 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
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
desc 'Delete a hook' do
success ::API::Entities::Hook
end
params do
requires :id, type: Integer, desc: 'The ID of the system hook'
end
delete ":id" do
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
present hook.destroy, with: ::API::Entities::Hook
end
end
end
end
end
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, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository tags' do
success ::API::Entities::Tag
end
get ":id/repository/tags" do
tags = user_project.repository.tags.sort_by(&:name).reverse
present tags, with: ::API::Entities::Tag, project: user_project
end
desc 'Delete a repository tag'
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
authorize_push_project
result = ::Tags::DestroyService.new(user_project, current_user)
.execute(params[:tag_name])
if result[:status] == :success
status(200)
{
tag_name: params[:tag_name]
}
else
render_api_error!(result[:message], result[:return_code])
end
end
end
end
end
end
This diff is collapsed.
module API
module V3
module TimeTrackingEndpoints
extend ActiveSupport::Concern
included do
helpers do
def issuable_name
declared_params.key?(:issue_id) ? 'issue' : 'merge_request'
end
def issuable_key
"#{issuable_name}_id".to_sym
end
def update_issuable_key
"update_#{issuable_name}".to_sym
end
def read_issuable_key
"read_#{issuable_name}".to_sym
end
def load_issuable
@issuable ||= begin
case issuable_name
when 'issue'
find_project_issue(params.delete(issuable_key))
when 'merge_request'
find_project_merge_request(params.delete(issuable_key))
end
end
end
def update_issuable(attrs)
custom_params = declared_params(include_missing: false)
custom_params.merge!(attrs)
issuable = update_service.new(user_project, current_user, custom_params).execute(load_issuable)
if issuable.valid?
present issuable, with: ::API::Entities::IssuableTimeStats
else
render_validation_error!(issuable)
end
end
def update_service
issuable_name == 'issue' ? ::Issues::UpdateService : ::MergeRequests::UpdateService
end
end
issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
issuable_collection_name = issuable_name.pluralize
issuable_key = "#{issuable_name}_id".to_sym
desc "Set a time estimate for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
requires :duration, type: String, desc: 'The duration to be parsed'
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/time_estimate" do
authorize! update_issuable_key, load_issuable
status :ok
update_issuable(time_estimate: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)))
end
desc "Reset the time estimate for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_time_estimate" do
authorize! update_issuable_key, load_issuable
status :ok
update_issuable(time_estimate: 0)
end
desc "Add spent time for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
requires :duration, type: String, desc: 'The duration to be parsed'
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/add_spent_time" do
authorize! update_issuable_key, load_issuable
update_issuable(spend_time: {
duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)),
user_id: current_user.id
})
end
desc "Reset spent time for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_spent_time" do
authorize! update_issuable_key, load_issuable
status :ok
update_issuable(spend_time: { duration: :reset, user_id: current_user.id })
end
desc "Show time stats for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
end
get ":id/#{issuable_collection_name}/:#{issuable_key}/time_stats" do
authorize! read_issuable_key, load_issuable
present load_issuable, with: ::API::Entities::IssuableTimeStats
end
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment