summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api.rb42
-rw-r--r--lib/api/deploy_keys.rb84
-rw-r--r--lib/api/entities.rb66
-rw-r--r--lib/api/groups.rb92
-rw-r--r--lib/api/helpers.rb54
-rw-r--r--lib/api/internal.rb40
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/api/merge_requests.rb49
-rw-r--r--lib/api/milestones.rb7
-rw-r--r--lib/api/notes.rb12
-rw-r--r--lib/api/project_hooks.rb108
-rw-r--r--lib/api/project_snippets.rb123
-rw-r--r--lib/api/projects.rb463
-rw-r--r--lib/api/repositories.rb190
-rw-r--r--lib/api/session.rb19
-rw-r--r--lib/api/system_hooks.rb70
-rw-r--r--lib/api/users.rb51
17 files changed, 1109 insertions, 371 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
new file mode 100644
index 00000000000..c4c9f166db1
--- /dev/null
+++ b/lib/api/api.rb
@@ -0,0 +1,42 @@
+Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
+
+module API
+ class API < Grape::API
+ version 'v3', using: :path
+
+ rescue_from ActiveRecord::RecordNotFound do
+ rack_response({'message' => '404 Not found'}.to_json, 404)
+ end
+
+ rescue_from :all do |exception|
+ # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
+ # why is this not wrapped in something reusable?
+ trace = exception.backtrace
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << trace.join("\n ")
+
+ API.logger.add Logger::FATAL, message
+ rack_response({'message' => '500 Internal Server Error'}, 500)
+ end
+
+ format :json
+ helpers APIHelpers
+
+ mount Groups
+ mount Users
+ mount Projects
+ mount Repositories
+ mount Issues
+ mount Milestones
+ mount Session
+ mount MergeRequests
+ mount Notes
+ mount Internal
+ mount SystemHooks
+ mount ProjectSnippets
+ mount DeployKeys
+ mount ProjectHooks
+ end
+end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
new file mode 100644
index 00000000000..55c947eb176
--- /dev/null
+++ b/lib/api/deploy_keys.rb
@@ -0,0 +1,84 @@
+module API
+ # Projects API
+ class DeployKeys < Grape::API
+ before { authenticate! }
+
+ 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
+
+
+ # Get a specific project's keys
+ #
+ # Example Request:
+ # GET /projects/:id/keys
+ get ":id/keys" do
+ present user_project.deploy_keys, with: Entities::SSHKey
+ end
+
+ # Get single key owned by currently authenticated user
+ #
+ # Example Request:
+ # GET /projects/:id/keys/:id
+ get ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ present key, with: Entities::SSHKey
+ end
+
+ # Add new ssh key to currently authenticated user
+ # If deploy key already exists - it will be joined to project
+ # but only if original one was is accessible by same user
+ #
+ # Parameters:
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /projects/:id/keys
+ post ":id/keys" do
+ attrs = attributes_for_keys [:title, :key]
+
+ if attrs[:key].present?
+ attrs[:key].strip!
+
+ # check if key already exist in project
+ key = user_project.deploy_keys.find_by_key(attrs[:key])
+ if key
+ present key, with: Entities::SSHKey
+ return
+ end
+
+ # Check for available deploy keys in other projects
+ key = current_user.accessible_deploy_keys.find_by_key(attrs[:key])
+ if key
+ user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ return
+ end
+ end
+
+ key = DeployKey.new attrs
+
+ if key.valid? && user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
+ # Delete existed ssh key of currently authenticated user
+ #
+ # Example Request:
+ # DELETE /projects/:id/keys/:id
+ delete ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ key.destroy
+ end
+ end
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c1873d87b55..1f35e9ec5fc 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1,46 +1,78 @@
-module Gitlab
+module API
module Entities
class User < Grape::Entity
expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
- :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider
+ :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider
+ end
+
+ class UserSafe < Grape::Entity
+ expose :name
end
class UserBasic < Grape::Entity
- expose :id, :username, :email, :name, :blocked, :created_at
+ expose :id, :username, :email, :name, :state, :created_at
end
- class UserLogin < UserBasic
+ class UserLogin < User
expose :private_token
+ expose :is_admin?, as: :is_admin
+ expose :can_create_group?, as: :can_create_group
+ expose :can_create_project?, as: :can_create_project
+ expose :can_create_team?, as: :can_create_team
end
class Hook < Grape::Entity
expose :id, :url, :created_at
end
+ class ForkedFromProject < Grape::Entity
+ expose :id
+ expose :name, :name_with_namespace
+ expose :path, :path_with_namespace
+ end
+
class Project < Grape::Entity
- expose :id, :name, :description, :default_branch
+ expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :owner, using: Entities::UserBasic
- expose :private_flag, as: :private
+ expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
+ expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public
expose :namespace
+ expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
end
class ProjectMember < UserBasic
- expose :project_access, :as => :access_level do |user, options|
+ expose :project_access, as: :access_level do |user, options|
options[:project].users_projects.find_by_user_id(user.id).project_access
end
end
+ class TeamMember < UserBasic
+ expose :permission, as: :access_level do |user, options|
+ options[:user_team].user_team_user_relationships.find_by_user_id(user.id).permission
+ end
+ end
+
+ class TeamProject < Project
+ expose :greatest_access, as: :greatest_access_level do |project, options|
+ options[:user_team].user_team_project_relationships.find_by_project_id(project.id).greatest_access
+ end
+ end
+
class Group < Grape::Entity
expose :id, :name, :path, :owner_id
end
-
+
class GroupDetail < Group
expose :projects, using: Entities::Project
end
-
+ class GroupMember < UserBasic
+ expose :group_access, as: :access_level do |user, options|
+ options[:group].users_groups.find_by_user_id(user.id).group_access
+ end
+ end
+
class RepoObject < Grape::Entity
expose :name, :commit
expose :protected do |repo, options|
@@ -63,7 +95,7 @@ module Gitlab
class Milestone < Grape::Entity
expose :id
expose (:project_id) {|milestone| milestone.project.id}
- expose :title, :description, :due_date, :closed, :updated_at, :created_at
+ expose :title, :description, :due_date, :state, :updated_at, :created_at
end
class Issue < Grape::Entity
@@ -73,7 +105,7 @@ module Gitlab
expose :label_list, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :closed, :updated_at, :created_at
+ expose :state, :updated_at, :created_at
end
class SSHKey < Grape::Entity
@@ -81,13 +113,15 @@ module Gitlab
end
class MergeRequest < Grape::Entity
- expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged
+ expose :id, :target_branch, :source_branch, :title, :state
+ expose :target_project_id, as: :project_id
expose :author, :assignee, using: Entities::UserBasic
end
class Note < Grape::Entity
expose :id
expose :note, as: :body
+ expose :attachment_identifier, as: :attachment
expose :author, using: Entities::UserBasic
expose :created_at
end
@@ -96,5 +130,11 @@ module Gitlab
expose :note
expose :author, using: Entities::UserBasic
end
+
+ class Event < Grape::Entity
+ expose :title, :project_id, :action_name
+ expose :target_id, :target_type, :author_id
+ expose :data, :target_title
+ end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index a67caef0bc5..396554404af 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -1,9 +1,23 @@
-module Gitlab
+module API
# groups API
class Groups < Grape::API
before { authenticate! }
resource :groups do
+ helpers do
+ def find_group(id)
+ group = Group.find(id)
+ if current_user.admin or current_user.groups.include? group
+ group
+ else
+ render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
+ end
+ end
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+ end
+
# Get a groups list
#
# Example Request:
@@ -20,12 +34,14 @@ module Gitlab
# Create group. Available only for admin
#
# Parameters:
- # name (required) - Name
- # path (required) - Path
+ # name (required) - The name of the group
+ # path (required) - The path of the group
# Example Request:
# POST /groups
post do
authenticated_as_admin!
+ required_attributes! [:name, :path]
+
attrs = attributes_for_keys [:name, :path]
@group = Group.new(attrs)
@group.owner = current_user
@@ -44,13 +60,79 @@ module Gitlab
# Example Request:
# GET /groups/:id
get ":id" do
+ group = find_group(params[:id])
+ present group, with: Entities::GroupDetail
+ end
+
+ # Transfer a project to the Group namespace
+ #
+ # Parameters:
+ # id - group id
+ # project_id - project id
+ # Example Request:
+ # POST /groups/:id/projects/:project_id
+ post ":id/projects/:project_id" do
+ authenticated_as_admin!
@group = Group.find(params[:id])
- if current_user.admin or current_user.groups.include? @group
- present @group, with: Entities::GroupDetail
+ project = Project.find(params[:project_id])
+ if project.transfer(@group)
+ present @group
else
not_found!
end
end
+
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ members = group.users_groups
+ users = (paginate members).collect(&:user)
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ required_attributes! [:user_id, :access_level]
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+ group = find_group(params[:id])
+ if group.users_groups.find_by_user_id(params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+ group.add_users([params[:user_id]], params[:access_level])
+ member = group.users_groups.find_by_user_id(params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ member = group.users_groups.find_by_user_id(params[:user_id])
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6bd8111c2b2..4f189f35196 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -1,16 +1,43 @@
-module Gitlab
+module API
module APIHelpers
+ PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
+ PRIVATE_TOKEN_PARAM = :private_token
+ SUDO_HEADER ="HTTP_SUDO"
+ SUDO_PARAM = :sudo
+
def current_user
- @current_user ||= User.find_by_authentication_token(params[:private_token] || env["HTTP_PRIVATE_TOKEN"])
+ @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER])
+ identifier = sudo_identifier()
+ # If the sudo is the current user do nothing
+ if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
+ render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
+ begin
+ @current_user = User.by_username_or_id(identifier)
+ rescue => ex
+ not_found!("No user id or username for: #{identifier}")
+ end
+ not_found!("No user id or username for: #{identifier}") if current_user.nil?
+ end
+ @current_user
+ end
+
+ def sudo_identifier()
+ identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
+ # Regex for integers
+ if (!!(identifier =~ /^[0-9]+$/))
+ identifier.to_i
+ else
+ identifier
+ end
end
def user_project
- @project ||= find_project
+ @project ||= find_project(params[:id])
@project || not_found!
end
- def find_project
- project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id])
+ def find_project(id)
+ project = Project.find_by_id(id) || Project.find_with_namespace(id)
if project && can?(current_user, :read_project, project)
project
@@ -41,6 +68,17 @@ module Gitlab
abilities.allowed?(object, action, subject)
end
+ # Checks the occurrences of required attributes, each attribute must be present in the params hash
+ # or a Bad Request error is invoked.
+ #
+ # Parameters:
+ # keys (required) - A hash consisting of keys that must be present
+ def required_attributes!(keys)
+ keys.each do |key|
+ bad_request!(key) unless params[key].present?
+ end
+ end
+
def attributes_for_keys(keys)
attrs = {}
keys.each do |key|
@@ -55,6 +93,12 @@ module Gitlab
render_api_error!('403 Forbidden', 403)
end
+ def bad_request!(attribute)
+ message = ["400 (Bad request)"]
+ message << "\"" + attribute.to_s + "\" not given"
+ render_api_error!(message.join(' '), 400)
+ end
+
def not_found!(resource = nil)
message = ["404"]
message << resource if resource
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 3e5e3a478ba..79f8eb3a543 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,23 +1,45 @@
-module Gitlab
+module API
# Internal access API
class Internal < Grape::API
+
+ DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
+ PUSH_COMMANDS = %w{ git-receive-pack }
+
namespace 'internal' do
#
# Check if ssh key has access to project code
#
+ # Params:
+ # key_id - SSH Key id
+ # project - project path with namespace
+ # action - git action (git-upload-pack or git-receive-pack)
+ # ref - branch name
+ #
get "/allowed" do
+ # Check for *.wiki repositories.
+ # Strip out the .wiki from the pathname before finding the
+ # project. This applies the correct project permissions to
+ # the wiki repository as well.
+ project_path = params[:project]
+ project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
+
key = Key.find(params[:key_id])
- project = Project.find_with_namespace(params[:project])
+ project = Project.find_with_namespace(project_path)
git_cmd = params[:action]
+ return false unless project
- if key.is_deploy_key
- project == key.project && git_cmd == 'git-upload-pack'
+
+ if key.is_a? DeployKey
+ key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd)
else
user = key.user
+
+ return false if user.blocked?
+
action = case git_cmd
- when 'git-upload-pack'
+ when *DOWNLOAD_COMMANDS
then :download_code
- when 'git-receive-pack'
+ when *PUSH_COMMANDS
then
if project.protected_branch?(params[:ref])
:push_code_to_protected_branches
@@ -35,12 +57,14 @@ module Gitlab
#
get "/discover" do
key = Key.find(params[:key_id])
- present key.user, with: Entities::User
+ present key.user, with: Entities::UserSafe
end
get "/check" do
{
- api_version: '3'
+ api_version: API.version,
+ gitlab_version: Gitlab::VERSION,
+ gitlab_rev: Gitlab::REVISION,
}
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 4d832fbe593..a15203d1563 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -1,7 +1,8 @@
-module Gitlab
+module API
# Issues API
class Issues < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :issues do
# Get currently authenticated user's issues
@@ -48,6 +49,7 @@ module Gitlab
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
+ required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present?
@issue = user_project.issues.new attrs
@@ -69,16 +71,16 @@ module Gitlab
# assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
- # closed (optional) - The state of an issue (0 = false, 1 = true)
+ # state_event (optional) - The state event of an issue (close|reopen)
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed]
+ attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
- IssueObserver.current_user = current_user
+
if @issue.update_attributes attrs
present @issue, with: Entities::Issue
else
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 470cd1e1c2d..d690f1d07e7 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -1,9 +1,28 @@
-module Gitlab
+module API
# MergeRequest API
class MergeRequests < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :projects do
+ helpers do
+ def handle_merge_request_errors!(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ elsif errors[:branch_conflict].any?
+ error!(errors[:branch_conflict], 422)
+ end
+ not_found!
+ end
+
+ def not_fork?(target_project_id, user_project)
+ target_project_id.nil? || target_project_id == user_project.id.to_s
+ end
+
+ def target_matches_fork(target_project_id,user_project)
+ user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
+ end
+ end
# List merge requests
#
@@ -40,9 +59,10 @@ module Gitlab
#
# Parameters:
#
- # id (required) - The ID of a project
+ # id (required) - The ID of a project - this will be the source of the merge request
# source_branch (required) - The source branch
# target_branch (required) - The target branch
+ # target_project - The target project of the merge request defaults to the :id of the project
# assignee_id - Assignee user ID
# title (required) - Title of MR
#
@@ -51,16 +71,27 @@ module Gitlab
#
post ":id/merge_requests" do
authorize! :write_merge_request, user_project
-
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
+ required_attributes! [:source_branch, :target_branch, :title]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
merge_request = user_project.merge_requests.new(attrs)
merge_request.author = current_user
+ merge_request.source_project = user_project
+ target_project_id = attrs[:target_project_id]
+ if not_fork?(target_project_id, user_project)
+ merge_request.target_project = user_project
+ else
+ if target_matches_fork(target_project_id,user_project)
+ merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
+ else
+ render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
+ end
+ end
if merge_request.save
merge_request.reload_code
present merge_request, with: Entities::MergeRequest
else
- not_found!
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -73,12 +104,12 @@ module Gitlab
# target_branch - The target branch
# assignee_id - Assignee user ID
# title - Title of MR
- # closed - Status of MR. true - closed
+ # state_event - Status of MR. (close|reopen|merge)
# Example:
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
@@ -88,7 +119,7 @@ module Gitlab
merge_request.mark_as_unchecked
present merge_request, with: Entities::MergeRequest
else
- not_found!
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -102,6 +133,8 @@ module Gitlab
# POST /projects/:id/merge_request/:merge_request_id/comments
#
post ":id/merge_request/:merge_request_id/comments" do
+ required_attributes! [:note]
+
merge_request = user_project.merge_requests.find(params[:merge_request_id])
note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
note.author = current_user
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 6aca9d01b09..aee12e7dc40 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Milestones API
class Milestones < Grape::API
before { authenticate! }
@@ -41,6 +41,7 @@ module Gitlab
# POST /projects/:id/milestones
post ":id/milestones" do
authorize! :admin_milestone, user_project
+ required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :due_date]
@milestone = user_project.milestones.new attrs
@@ -59,14 +60,14 @@ module Gitlab
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
- # closed (optional) - The status of the milestone
+ # state_event (optional) - The state event of the milestone (close|activate)
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- attrs = attributes_for_keys [:title, :description, :due_date, :closed]
+ attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
if @milestone.update_attributes attrs
present @milestone, with: Entities::Milestone
else
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 70344d6e381..cb2bc764476 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Notes API
class Notes < Grape::API
before { authenticate! }
@@ -14,6 +14,10 @@ module Gitlab
# GET /projects/:id/notes
get ":id/notes" do
@notes = user_project.notes.common
+
+ # Get recent notes if recent = true
+ @notes = @notes.order('id DESC') if params[:recent]
+
present paginate(@notes), with: Entities::Note
end
@@ -37,12 +41,16 @@ module Gitlab
# Example Request:
# POST /projects/:id/notes
post ":id/notes" do
+ required_attributes! [:body]
+
@note = user_project.notes.new(note: params[:body])
@note.author = current_user
if @note.save
present @note, with: Entities::Note
else
+ # :note is exposed as :body, but :note is set on error
+ bad_request!(:note) if @note.errors[:note].any?
not_found!
end
end
@@ -89,6 +97,8 @@ module Gitlab
# POST /projects/:id/issues/:noteable_id/notes
# POST /projects/:id/snippets/:noteable_id/notes
post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
+ required_attributes! [:body]
+
@noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
@note = @noteable.notes.new(note: params[:body])
@note.author = current_user
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
new file mode 100644
index 00000000000..28501256795
--- /dev/null
+++ b/lib/api/project_hooks.rb
@@ -0,0 +1,108 @@
+module API
+ # Projects API
+ class ProjectHooks < Grape::API
+ before { authenticate! }
+
+ 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
+
+ # Get project hooks
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/hooks
+ get ":id/hooks" do
+ authorize! :admin_project, user_project
+ @hooks = paginate user_project.hooks
+ present @hooks, with: Entities::Hook
+ end
+
+ # Get a project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # Example Request:
+ # GET /projects/:id/hooks/:hook_id
+ get ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ @hook = user_project.hooks.find(params[:hook_id])
+ present @hook, with: Entities::Hook
+ end
+
+
+ # Add hook to project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # url (required) - The hook URL
+ # Example Request:
+ # POST /projects/:id/hooks
+ post ":id/hooks" do
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ @hook = user_project.hooks.new({"url" => params[:url]})
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Update an existing project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # url (required) - The hook URL
+ # Example Request:
+ # PUT /projects/:id/hooks/:hook_id
+ put ":id/hooks/:hook_id" do
+ @hook = user_project.hooks.find(params[:hook_id])
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ attrs = attributes_for_keys [:url]
+ if @hook.update_attributes attrs
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Deletes project hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of hook to delete
+ # Example Request:
+ # DELETE /projects/:id/hooks/:hook_id
+ delete ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ required_attributes! [:hook_id]
+
+ begin
+ @hook = ProjectHook.find(params[:hook_id])
+ @hook.destroy
+ rescue
+ # ProjectHook can raise Error if hook_id not found
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
new file mode 100644
index 00000000000..bee6544ea3d
--- /dev/null
+++ b/lib/api/project_snippets.rb
@@ -0,0 +1,123 @@
+module API
+ # Projects API
+ class ProjectSnippets < Grape::API
+ before { authenticate! }
+
+ 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
+
+ # Get a project snippets
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/snippets
+ get ":id/snippets" do
+ present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ end
+
+ # Get a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id
+ get ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ present @snippet, with: Entities::ProjectSnippet
+ end
+
+ # Create a new project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # title (required) - The title of a snippet
+ # file_name (required) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (required) - The content of a snippet
+ # Example Request:
+ # POST /projects/:id/snippets
+ post ":id/snippets" do
+ authorize! :write_project_snippet, user_project
+ required_attributes! [:title, :file_name, :code]
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+ @snippet = user_project.snippets.new attrs
+ @snippet.author = current_user
+
+ if @snippet.save
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Update an existing project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # title (optional) - The title of a snippet
+ # file_name (optional) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (optional) - The content of a snippet
+ # Example Request:
+ # PUT /projects/:id/snippets/:snippet_id
+ put ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+
+ if @snippet.update_attributes attrs
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Delete a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # DELETE /projects/:id/snippets/:snippet_id
+ delete ":id/snippets/:snippet_id" do
+ begin
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+ @snippet.destroy
+ rescue
+ end
+ end
+
+ # Get a raw project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id/raw
+ get ":id/snippets/:snippet_id/raw" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ present @snippet.content
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index d416121a78a..cf357b23c40 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,9 +1,18 @@
-module Gitlab
+module API
# Projects API
class Projects < Grape::API
before { authenticate! }
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
+
# Get a projects list for authenticated user
#
# Example Request:
@@ -13,6 +22,15 @@ module Gitlab
present @projects, with: Entities::Project
end
+ # Get an owned projects list for authenticated user
+ #
+ # Example Request:
+ # GET /projects/owned
+ get '/owned' do
+ @projects = paginate current_user.owned_projects
+ present @projects, with: Entities::Project
+ end
+
# Get a single project
#
# Parameters:
@@ -23,32 +41,128 @@ module Gitlab
present user_project, with: Entities::Project
end
+ # Get a single project events
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id
+ get ":id/events" do
+ limit = (params[:per_page] || 20).to_i
+ offset = (params[:page] || 0).to_i * limit
+ events = user_project.events.recent.limit(limit).offset(offset)
+
+ present events, with: Entities::Event
+ end
+
# Create new project
#
# Parameters:
# name (required) - name for new project
# description (optional) - short project description
# default_branch (optional) - 'master' by default
- # issues_enabled (optional) - enabled by default
- # wall_enabled (optional) - enabled by default
- # merge_requests_enabled (optional) - enabled by default
- # wiki_enabled (optional) - enabled by default
+ # issues_enabled (optional)
+ # wall_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # namespace_id (optional) - defaults to user namespace
+ # public (optional) - false by default
# Example Request
# POST /projects
post do
+ required_attributes! [:name]
attrs = attributes_for_keys [:name,
- :description,
- :default_branch,
- :issues_enabled,
- :wall_enabled,
- :merge_requests_enabled,
- :wiki_enabled]
+ :path,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :wall_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :namespace_id,
+ :public]
@project = ::Projects::CreateContext.new(current_user, attrs).execute
if @project.saved?
present @project, with: Entities::Project
else
+ if @project.errors[:limit_reached].present?
+ error!(@project.errors[:limit_reached], 403)
+ end
+ not_found!
+ end
+ end
+
+ # Create new project for a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # user_id (required) - The ID of a user
+ # name (required) - name for new project
+ # description (optional) - short project description
+ # default_branch (optional) - 'master' by default
+ # issues_enabled (optional)
+ # wall_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # public (optional)
+ # Example Request
+ # POST /projects/user/:user_id
+ post "user/:user_id" do
+ authenticated_as_admin!
+ user = User.find(params[:user_id])
+ attrs = attributes_for_keys [:name,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :wall_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :public]
+ @project = ::Projects::CreateContext.new(user, attrs).execute
+ if @project.saved?
+ present @project, with: Entities::Project
+ else
+ not_found!
+ end
+ end
+
+
+ # Mark this project as forked from another
+ #
+ # Parameters:
+ # id: (required) - The ID of the project being marked as a fork
+ # forked_from_id: (required) - The ID of the project it was forked from
+ # Example Request:
+ # POST /projects/:id/fork/:forked_from_id
+ post ":id/fork/:forked_from_id" do
+ authenticated_as_admin!
+ forked_from_project = find_project(params[:forked_from_id])
+ unless forked_from_project.nil?
+ if user_project.forked_from_project.nil?
+ user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+ else
+ render_api_error!("Project already forked", 409)
+ end
+ else
not_found!
end
+
+ end
+
+ # Remove a forked_from relationship
+ #
+ # Parameters:
+ # id: (required) - The ID of the project being marked as a fork
+ # Example Request:
+ # DELETE /projects/:id/fork
+ delete ":id/fork" do
+ authenticated_as_admin!
+ unless user_project.forked_project_link.nil?
+ user_project.forked_project_link.destroy
+ end
end
# Get a project team members
@@ -89,16 +203,22 @@ module Gitlab
# POST /projects/:id/members
post ":id/members" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.new(
- user_id: params[:user_id],
- project_access: params[:access_level]
- )
+ required_attributes! [:user_id, :access_level]
+
+ # either the user is already a team member or a new one
+ team_member = user_project.team_member_by_id(params[:user_id])
+ if team_member.nil?
+ team_member = user_project.users_projects.new(
+ user_id: params[:user_id],
+ project_access: params[:access_level]
+ )
+ end
- if users_project.save
- @member = users_project.user
+ if team_member.save
+ @member = team_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- not_found!
+ handle_project_member_errors team_member.errors
end
end
@@ -112,13 +232,16 @@ module Gitlab
# PUT /projects/:id/members/:user_id
put ":id/members/:user_id" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.find_by_user_id params[:user_id]
+ required_attributes! [:access_level]
+
+ team_member = user_project.users_projects.find_by_user_id(params[:user_id])
+ not_found!("User can not be found") if team_member.nil?
- if users_project.update_attributes(project_access: params[:access_level])
- @member = users_project.user
+ if team_member.update_attributes(project_access: params[:access_level])
+ @member = team_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- not_found!
+ handle_project_member_errors team_member.errors
end
end
@@ -131,297 +254,27 @@ module Gitlab
# DELETE /projects/:id/members/:user_id
delete ":id/members/:user_id" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.find_by_user_id params[:user_id]
- users_project.destroy
- end
-
- # Get project hooks
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/hooks
- get ":id/hooks" do
- authorize! :admin_project, user_project
- @hooks = paginate user_project.hooks
- present @hooks, with: Entities::Hook
- end
-
- # Get a project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # Example Request:
- # GET /projects/:id/hooks/:hook_id
- get ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- present @hook, with: Entities::Hook
- end
-
-
- # Add hook to project
- #
- # Parameters:
- # id (required) - The ID of a project
- # url (required) - The hook URL
- # Example Request:
- # POST /projects/:id/hooks
- post ":id/hooks" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.new({"url" => params[:url]})
- if @hook.save
- present @hook, with: Entities::Hook
+ team_member = user_project.users_projects.find_by_user_id(params[:user_id])
+ unless team_member.nil?
+ team_member.destroy
else
- error!({'message' => '404 Not found'}, 404)
+ {message: "Access revoked", id: params[:user_id].to_i}
end
end
- # Update an existing project hook
+ # search for projects current_user has access to
#
# Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # url (required) - The hook URL
+ # query (required) - A string contained in the project name
+ # per_page (optional) - number of projects to return per page
+ # page (optional) - the page to retrieve
# Example Request:
- # PUT /projects/:id/hooks/:hook_id
- put ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- authorize! :admin_project, user_project
-
- attrs = attributes_for_keys [:url]
-
- if @hook.update_attributes attrs
- present @hook, with: Entities::Hook
- else
- not_found!
- end
+ # GET /projects/search/:query
+ get "/search/:query" do
+ ids = current_user.authorized_projects.map(&:id)
+ projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%")
+ present paginate(projects), with: Entities::Project
end
-
- # Delete project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of hook to delete
- # Example Request:
- # DELETE /projects/:id/hooks
- delete ":id/hooks" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.find(params[:hook_id])
- @hook.destroy
- end
-
- # Get a project repository branches
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/branches
- get ":id/repository/branches" do
- present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
- end
-
- # Get a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # GET /projects/:id/repository/branches/:branch
- get ":id/repository/branches/:branch" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Protect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/protect
- put ":id/repository/branches/:branch/protect" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- protected = user_project.protected_branches.find_by_name(@branch.name)
-
- unless protected
- user_project.protected_branches.create(:name => @branch.name)
- end
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Unprotect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/unprotect
- put ":id/repository/branches/:branch/unprotect" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- protected = user_project.protected_branches.find_by_name(@branch.name)
-
- if protected
- protected.destroy
- end
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Get a project repository tags
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/tags
- get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
- end
-
- # Get a project repository commits
- #
- # Parameters:
- # id (required) - The ID of a project
- # ref_name (optional) - The name of a repository branch or tag
- # Example Request:
- # GET /projects/:id/repository/commits
- get ":id/repository/commits" do
- authorize! :download_code, user_project
-
- page = params[:page] || 0
- per_page = params[:per_page] || 20
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
-
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
- present CommitDecorator.decorate(commits), with: Entities::RepoCommit
- end
-
- # Get a project snippets
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/snippets
- get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
- end
-
- # Get a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id
- get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- present @snippet, with: Entities::ProjectSnippet
- end
-
- # Create a new project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # title (required) - The title of a snippet
- # file_name (required) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (required) - The content of a snippet
- # Example Request:
- # POST /projects/:id/snippets
- post ":id/snippets" do
- authorize! :write_snippet, user_project
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
- @snippet = user_project.snippets.new attrs
- @snippet.author = current_user
-
- if @snippet.save
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Update an existing project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # title (optional) - The title of a snippet
- # file_name (optional) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (optional) - The content of a snippet
- # Example Request:
- # PUT /projects/:id/snippets/:snippet_id
- put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
-
- if @snippet.update_attributes attrs
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Delete a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # DELETE /projects/:id/snippets/:snippet_id
- delete ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- @snippet.destroy
- end
-
- # Get a raw project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id/raw
- get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- content_type 'text/plain'
- present @snippet.content
- end
-
- # Get a raw file contents
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit or branch name
- # filepath (required) - The path to the file to display
- # Example Request:
- # GET /projects/:id/repository/commits/:sha/blob
- get ":id/repository/commits/:sha/blob" do
- authorize! :download_code, user_project
-
- ref = params[:sha]
-
- commit = user_project.repository.commit ref
- not_found! "Commit" unless commit
-
- tree = Tree.new commit.tree, ref, params[:filepath]
- not_found! "File" unless tree.try(:tree)
-
- content_type tree.mime_type
- present tree.data
- end
-
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
new file mode 100644
index 00000000000..fef32d3a2fe
--- /dev/null
+++ b/lib/api/repositories.rb
@@ -0,0 +1,190 @@
+module API
+ # Projects API
+ class Repositories < Grape::API
+ before { authenticate! }
+
+ 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
+
+ # Get a project repository branches
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/branches
+ get ":id/repository/branches" do
+ present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # GET /projects/:id/repository/branches/:branch
+ get ":id/repository/branches/:branch" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found!("Branch does not exist") if @branch.nil?
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Protect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/protect
+ put ":id/repository/branches/:branch/protect" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found! unless @branch
+ protected = user_project.protected_branches.find_by_name(@branch.name)
+
+ unless protected
+ user_project.protected_branches.create(name: @branch.name)
+ end
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Unprotect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/unprotect
+ put ":id/repository/branches/:branch/unprotect" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found! unless @branch
+ protected = user_project.protected_branches.find_by_name(@branch.name)
+
+ if protected
+ protected.destroy
+ end
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a project repository tags
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/tags
+ get ":id/repository/tags" do
+ present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
+ end
+
+ # Get a project repository commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/commits
+ get ":id/repository/commits" do
+ authorize! :download_code, user_project
+
+ page = (params[:page] || 0).to_i
+ per_page = (params[:per_page] || 20).to_i
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+
+ commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ present commits, with: Entities::RepoCommit
+ end
+
+ # Get a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash or name of a repository branch or tag
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha
+ get ":id/repository/commits/:sha" do
+ authorize! :download_code, user_project
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ present commit, with: Entities::RepoCommit
+ end
+
+ # Get the diff for a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha/diff
+ get ":id/repository/commits/:sha/diff" do
+ authorize! :download_code, user_project
+ sha = params[:sha]
+ result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute
+ not_found! "Commit" unless result[:commit]
+ result[:commit].diffs
+ end
+
+ # Get a project repository tree
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/tree
+ get ":id/repository/tree" do
+ authorize! :download_code, user_project
+
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.repository.commit(ref)
+ tree = Tree.new(user_project.repository, commit.id, ref, path)
+
+ trees = []
+
+ %w(trees blobs submodules).each do |type|
+ trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } }
+ end
+
+ trees
+ end
+
+ # Get a raw file contents
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # filepath (required) - The path to the file to display
+ # Example Request:
+ # GET /projects/:id/repository/blobs/:sha
+ get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do
+ authorize! :download_code, user_project
+ required_attributes! [:filepath]
+
+ ref = params[:sha]
+
+ repo = user_project.repository
+
+ commit = repo.commit(ref)
+ not_found! "Commit" unless commit
+
+ blob = Gitlab::Git::Blob.new(repo, commit.id, ref, params[:filepath])
+ not_found! "File" unless blob.exists?
+
+ env['api.format'] = :txt
+
+ content_type blob.mime_type
+ present blob.data
+ end
+ end
+ end
+end
+
diff --git a/lib/api/session.rb b/lib/api/session.rb
index b4050160ae4..cc646895914 100644
--- a/lib/api/session.rb
+++ b/lib/api/session.rb
@@ -1,20 +1,21 @@
-module Gitlab
+module API
# Users API
class Session < Grape::API
# Login to get token
#
+ # Parameters:
+ # login (*required) - user login
+ # email (*required) - user email
+ # password (required) - user password
+ #
# Example Request:
# POST /session
post "/session" do
- resource = User.find_for_database_authentication(email: params[:email])
-
- return unauthorized! unless resource
+ auth = Gitlab::Auth.new
+ user = auth.find(params[:email] || params[:login], params[:password])
- if resource.valid_password?(params[:password])
- present resource, with: Entities::UserLogin
- else
- unauthorized!
- end
+ return unauthorized! unless user
+ present user, with: Entities::UserLogin
end
end
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
new file mode 100644
index 00000000000..3e239c5afe7
--- /dev/null
+++ b/lib/api/system_hooks.rb
@@ -0,0 +1,70 @@
+module API
+ # Hooks API
+ class SystemHooks < Grape::API
+ before {
+ authenticate!
+ authenticated_as_admin!
+ }
+
+ resource :hooks do
+ # Get the list of system hooks
+ #
+ # Example Request:
+ # GET /hooks
+ get do
+ @hooks = SystemHook.all
+ present @hooks, with: Entities::Hook
+ end
+
+ # Create new system hook
+ #
+ # Parameters:
+ # url (required) - url for system hook
+ # Example Request
+ # POST /hooks
+ post do
+ attrs = attributes_for_keys [:url]
+ required_attributes! [:url]
+ @hook = SystemHook.new attrs
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ not_found!
+ end
+ end
+
+ # Test a hook
+ #
+ # Example Request
+ # GET /hooks/:id
+ get ":id" do
+ @hook = SystemHook.find(params[:id])
+ data = {
+ event_name: "project_create",
+ name: "Ruby",
+ path: "ruby",
+ project_id: 1,
+ owner_name: "Someone",
+ owner_email: "example@gitlabhq.com"
+ }
+ @hook.execute(data)
+ data
+ end
+
+ # Delete a hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - ID of the hook
+ # Example Request:
+ # DELETE /hooks/:id
+ delete ":id" do
+ begin
+ @hook = SystemHook.find(params[:id])
+ @hook.destroy
+ rescue
+ # SystemHook raises an Error if no hook with id found
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7ea90c75e9e..00dc2311ffd 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Users API
class Users < Grape::API
before { authenticate! }
@@ -9,7 +9,10 @@ module Gitlab
# Example Request:
# GET /users
get do
- @users = paginate User
+ @users = User.scoped
+ @users = @users.active if params[:active].present?
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = paginate @users
present @users, with: Entities::User
end
@@ -41,8 +44,9 @@ module Gitlab
# POST /users
post do
authenticated_as_admin!
+ required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
- user = User.new attrs, as: :admin
+ user = User.build_user(attrs, as: :admin)
if user.save
present user, with: Entities::User
else
@@ -59,7 +63,7 @@ module Gitlab
# skype - Skype ID
# linkedin - Linkedin
# twitter - Twitter account
- # projects_limit - Limit projects wich user can create
+ # projects_limit - Limit projects each user can create
# extern_uid - External authentication provider UID
# provider - External provider
# bio - Bio
@@ -67,16 +71,38 @@ module Gitlab
# PUT /users/:id
put ":id" do
authenticated_as_admin!
+
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
- user = User.find_by_id(params[:id])
+ user = User.find(params[:id])
+ not_found!("User not found") unless user
- if user && user.update_attributes(attrs)
+ if user.update_attributes(attrs)
present user, with: Entities::User
else
not_found!
end
end
+ # Add ssh key to a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # id (required) - The ID of a user
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /users/:id/keys
+ post ":id/keys" do
+ authenticated_as_admin!
+ user = User.find(params[:id])
+ attrs = attributes_for_keys [:title, :key]
+ key = user.keys.new attrs
+ if key.save
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
# Delete user. Available only for admin
#
# Example Request:
@@ -99,7 +125,7 @@ module Gitlab
# Example Request:
# GET /user
get do
- present @current_user, with: Entities::User
+ present @current_user, with: Entities::UserLogin
end
# Get currently authenticated user's keys
@@ -127,6 +153,8 @@ module Gitlab
# Example Request:
# POST /user/keys
post "keys" do
+ required_attributes! [:title, :key]
+
attrs = attributes_for_keys [:title, :key]
key = current_user.keys.new attrs
if key.save
@@ -136,15 +164,18 @@ module Gitlab
end
end
- # Delete existed ssh key of currently authenticated user
+ # Delete existing ssh key of currently authenticated user
#
# Parameters:
# id (required) - SSH Key ID
# Example Request:
# DELETE /user/keys/:id
delete "keys/:id" do
- key = current_user.keys.find params[:id]
- key.delete
+ begin
+ key = current_user.keys.find params[:id]
+ key.destroy
+ rescue
+ end
end
end
end