Commit a7055be1 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge pull request #2835 from Asquera/fixes/api

Fix API return codes
parents d2cec126 ecf53bb9
......@@ -91,7 +91,7 @@ class MergeRequest < ActiveRecord::Base
def validate_branches
if target_branch == source_branch
errors.add :base, "You can not use same branch for source and target branches"
errors.add :branch_conflict, "You can not use same branch for source and target branches"
end
end
......
......@@ -160,7 +160,7 @@ class Project < ActiveRecord::Base
def check_limit
unless creator.can_create_project?
errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
errors[:limit_reached] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
end
rescue
errors[:base] << ("Can't check your ability to create project")
......
......@@ -8,6 +8,7 @@ Gitlab::Application.routes.draw do
# API
require 'api'
Gitlab::API.logger Rails.logger
mount Gitlab::API => '/api'
constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
......
# GitLab API
All API requests require authentication. You need to pass a `private_token` parameter by url or header. You can find or reset your private token in your profile.
All API requests require authentication. You need to pass a `private_token` parameter by url or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
......@@ -18,8 +18,48 @@ Example of a valid API request:
GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U
```
Example for a valid API request using curl and authentication via header:
```
curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/projects"
```
The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
## Status codes
The API is designed to return different status codes according to context and action. In this way
if a request results in an error the caller is able to get insight into what went wrong, e.g.
status code `400 Bad Request` is returned if a required attribute is missing from the request.
The following list gives an overview of how the API functions generally behave.
API request types:
* `GET` requests access one or more resources and return the result as JSON
* `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
* `GET`, `PUT` and `DELETE` return `200 Ok` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
* `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 Ok` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
The following list shows the possible return codes for API requests.
Return values:
* `200 Ok` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
* `201 Created` - The `POST` request was successful and the resource is returned as JSON
* `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
* `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
* `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
* `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
* `405 Method Not Allowed` - The request is not supported
* `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
* `500 Server Error` - While handling the request something went wrong on the server side
#### Pagination
When listing resources you can pass the following parameters:
......
......@@ -17,7 +17,8 @@ GET /groups
]
```
## Details of group
## Details of a group
Get all details of a group.
......@@ -29,19 +30,19 @@ Parameters:
+ `id` (required) - The ID of a group
## New group
Create a new project group. Available only for admin
Creates a new project group. Available only for admin.
```
POST /groups
```
Parameters:
+ `name` (required) - Email
+ `path` - Password
Will return created group with status `201 Created` on success, or `404 Not found` on fail.
+ `name` (required) - The name of the group
+ `path` (required) - The path of the group
## Transfer project to group
......
## List issues
Get all issues created by authenticed user.
Get all issues created by authenticed user. This function takes pagination parameters
`page` and `per_page` to restrict the list of issues.
```
GET /issues
......@@ -68,9 +69,11 @@ GET /issues
]
```
## List project issues
Get a list of project issues.
Get a list of project issues. This function accepts pagination parameters `page` and `per_page`
to return the list of project issues.
```
GET /projects/:id/issues
......@@ -80,9 +83,10 @@ Parameters:
+ `id` (required) - The ID of a project
## Single issue
Get a project issue.
Gets a single project issue.
```
GET /projects/:id/issues/:issue_id
......@@ -133,9 +137,10 @@ Parameters:
}
```
## New issue
Create a new project issue.
Creates a new project issue.
```
POST /projects/:id/issues
......@@ -150,11 +155,10 @@ Parameters:
+ `milestone_id` (optional) - The ID of a milestone to assign issue
+ `labels` (optional) - Comma-separated label names for an issue
Will return created issue with status `201 Created` on success, or `404 Not found` on fail.
## Edit issue
Update an existing project issue.
Updates an existing project issue. This function is also used to mark an issue as closed.
```
PUT /projects/:id/issues/:issue_id
......@@ -171,5 +175,19 @@ Parameters:
+ `labels` (optional) - Comma-separated label names for an issue
+ `closed` (optional) - The state of an issue (0 = false, 1 = true)
Will return updated issue with status `200 OK` on success, or `404 Not found` on fail.
## Delete existing issue (**Deprecated**)
The function is deprecated and returns a `405 Method Not Allowed`
error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with
parameter `closed` set to 1.
```
DELETE /projects/:id/issues/:issue_id
```
Parameters:
+ `id` (required) - The project ID
+ `issue_id` (required) - The ID of the issue
## List merge requests
Get all MR for this project.
Get all merge requests for this project. This function takes pagination parameters
`page` and `per_page` to restrict the list of merge requests.
```
GET /projects/:id/merge_requests
......@@ -40,9 +41,10 @@ Parameters:
]
```
## Show MR
Show information about MR.
## Get single MR
Shows information about a single merge request.
```
GET /projects/:id/merge_request/:merge_request_id
......@@ -84,7 +86,7 @@ Parameters:
## Create MR
Create MR.
Creates a new merge request.
```
POST /projects/:id/merge_requests
......@@ -126,9 +128,10 @@ Parameters:
}
```
## Update MR
Update MR. You can change branches, title, or even close the MR.
Updates an existing merge request. You can change branches, title, or even close the MR.
```
PUT /projects/:id/merge_request/:merge_request_id
......@@ -172,9 +175,11 @@ Parameters:
}
}
```
## Post comment to MR
Post comment to MR
Adds a comment to a merge request.
```
POST /projects/:id/merge_request/:merge_request_id/comments
......@@ -183,10 +188,9 @@ POST /projects/:id/merge_request/:merge_request_id/comments
Parameters:
+ `id` (required) - The ID of a project
+ `merge_request_id` (required) - ID of MR
+ `merge_request_id` (required) - ID of merge request
+ `note` (required) - Text of comment
Will return created note with status `201 Created` on success, or `404 Not found` on fail.
```json
{
......
## List project milestones
Get a list of project milestones.
Returns a list of project milestones.
```
GET /projects/:id/milestones
......@@ -10,9 +10,10 @@ Parameters:
+ `id` (required) - The ID of a project
## Single milestone
Get a single project milestone.
## Get single milestone
Gets a single project milestone.
```
GET /projects/:id/milestones/:milestone_id
......@@ -23,9 +24,10 @@ Parameters:
+ `id` (required) - The ID of a project
+ `milestone_id` (required) - The ID of a project milestone
## New milestone
Create a new project milestone.
## Create new milestone
Creates a new project milestone.
```
POST /projects/:id/milestones
......@@ -38,9 +40,10 @@ Parameters:
+ `description` (optional) - The description of the milestone
+ `due_date` (optional) - The due date of the milestone
## Edit milestone
Update an existing project milestone.
Updates an existing project milestone.
```
PUT /projects/:id/milestones/:milestone_id
......@@ -54,3 +57,4 @@ Parameters:
+ `description` (optional) - The description of a milestone
+ `due_date` (optional) - The due date of the milestone
+ `closed` (optional) - The status of the milestone
## List notes
## Wall
### List project wall notes
......@@ -30,22 +30,40 @@ Parameters:
+ `id` (required) - The ID of a project
### List merge request notes
Get a list of merge request notes.
### Get single wall note
Returns a single wall note.
```
GET /projects/:id/merge_requests/:merge_request_id/notes
GET /projects/:id/notes/:note_id
```
Parameters:
+ `id` (required) - The ID of a project
+ `merge_request_id` (required) - The ID of an merge request
+ `note_id` (required) - The ID of a wall note
### List issue notes
Get a list of issue notes.
### Create new wall note
Creates a new wall note.
```
POST /projects/:id/notes
```
Parameters:
+ `id` (required) - The ID of a project
+ `body` (required) - The content of a note
## Issues
### List project issue notes
Gets a list of all notes for a single issue.
```
GET /projects/:id/issues/:issue_id/notes
......@@ -56,54 +74,59 @@ Parameters:
+ `id` (required) - The ID of a project
+ `issue_id` (required) - The ID of an issue
### List snippet notes
Get a list of snippet notes.
### Get single issue note
Returns a single note for a specific project issue
```
GET /projects/:id/snippets/:snippet_id/notes
GET /projects/:id/issues/:issue_id/notes/:note_id
```
Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of a snippet
+ `issue_id` (required) - The ID of a project issue
+ `note_id` (required) - The ID of an issue note
## Single note
### Single wall note
### Create new issue note
Get a wall note.
Creates a new note to a single project issue.
```
GET /projects/:id/notes/:note_id
POST /projects/:id/issues/:issue_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
+ `note_id` (required) - The ID of a wall note
+ `issue_id` (required) - The ID of an issue
+ `body` (required) - The content of a note
### Single issue note
Get an issue note.
## Snippets
### List all snippet notes
Gets a list of all notes for a single snippet. Snippet notes are comments users can post to a snippet.
```
GET /projects/:id/issues/:issue_id/:notes/:note_id
GET /projects/:id/snippets/:snippet_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
+ `issue_id` (required) - The ID of a project issue
+ `note_id` (required) - The ID of an issue note
+ `snippet_id` (required) - The ID of a project snippet
### Single snippet note
### Get single snippet note
Get a snippet note.
Returns a single note for a given snippet.
```
GET /projects/:id/issues/:snippet_id/:notes/:note_id
GET /projects/:id/snippets/:snippet_id/notes/:note_id
```
Parameters:
......@@ -112,52 +135,64 @@ Parameters:
+ `snippet_id` (required) - The ID of a project snippet
+ `note_id` (required) - The ID of an snippet note
## New note
### New wall note
### Create new snippet note
Create a new wall note.
Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
```
POST /projects/:id/notes
POST /projects/:id/snippets/:snippet_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of an snippet
+ `body` (required) - The content of a note
Will return created note with status `201 Created` on success, or `404 Not found` on fail.
## Merge Requests
### New issue note
### List all merge request notes
Create a new issue note.
Gets a list of all notes for a single merge request.
```
POST /projects/:id/issues/:issue_id/notes
GET /projects/:id/merge_requests/:merge_request_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
+ `issue_id` (required) - The ID of an issue
+ `body` (required) - The content of a note
+ `merge_request_id` (required) - The ID of a project merge request
Will return created note with status `201 Created` on success, or `404 Not found` on fail.
### New snippet note
### Get single merge request note
Create a new snippet note.
Returns a single note for a given merge request.
```
POST /projects/:id/snippets/:snippet_id/notes
GET /projects/:id/merge_requests/:merge_request_id/notes/:note_id
```
Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of an snippet
+ `merge_request_id` (required) - The ID of a project merge request
+ `note_id` (required) - The ID of a merge request note
### Create new merge request note
Creates a new note for a single merge request.
```
POST /projects/:id/merge_requests/:merge_request_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
+ `merge_request_id` (required) - The ID of a merge request
+ `body` (required) - The content of a note
Will return created note with status `201 Created` on success, or `404 Not found` on fail.
This diff is collapsed.
## Project repository branches
## List repository branches
Get a list of repository branches from a project, sorted by name alphabetically.
......@@ -39,7 +39,8 @@ Parameters:
]
```
## Project repository branch
## Get single repository branch
Get a single project repository branch.
......@@ -79,12 +80,11 @@ Parameters:
}
```
Will return status code `200` on success or `404 Not found` if the branch is not available.
## Protect a project repository branch
## Protect repository branch
Protect a single project repository branch.
Protects a single project repository branch. This is an idempotent function, protecting an already
protected repository branch still returns a `200 Ok` status code.
```
PUT /projects/:id/repository/branches/:branch/protect
......@@ -122,9 +122,11 @@ Parameters:
}
```
## Unprotect a project repository branch
Unprotect a single project repository branch.
## Unprotect repository branch
Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
unprotected repository branch still returns a `200 Ok` status code.
```
PUT /projects/:id/repository/branches/:branch/unprotect
......@@ -162,7 +164,8 @@ Parameters:
}
```
## Project repository tags
## List project repository tags
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
......@@ -201,7 +204,8 @@ Parameters:
]
```
## Project repository commits
## List repository commits
Get a list of repository commits in a project.
......@@ -212,7 +216,7 @@ GET /projects/:id/repository/commits
Parameters:
+ `id` (required) - The ID of a project
+ `ref_name` (optional) - The name of a repository branch or tag
+ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
```json
[
......@@ -235,6 +239,7 @@ Parameters:
]
```
## Raw blob content
Get the raw file contents for a file.
......@@ -248,5 +253,3 @@ Parameters:
+ `id` (required) - The ID of a project
+ `sha` (required) - The commit or branch name
+ `filepath` (required) - The path the file
Will return the raw file contents.
......@@ -10,9 +10,10 @@ Parameters:
+ `id` (required) - The ID of a project
## Single snippet
Get a project snippet.
Get a single project snippet.
```
GET /projects/:id/snippets/:snippet_id
......@@ -42,22 +43,10 @@ Parameters:
}
```
## Snippet content
Get a raw project snippet.
```
GET /projects/:id/snippets/:snippet_id/raw
```
Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of a project's snippet
## New snippet
## Create new snippet
Create a new project snippet.
Creates a new project snippet. The user must have permission to create new snippets.
```
POST /projects/:id/snippets
......@@ -71,11 +60,10 @@ Parameters:
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (required) - The content of a snippet
Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.
## Edit snippet
## Update snippet
Update an existing project snippet.
Updates an existing project snippet. The user must have permission to change an existing snippet.
```
PUT /projects/:id/snippets/:snippet_id
......@@ -90,11 +78,11 @@ Parameters:
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (optional) - The content of a snippet
Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.
## Delete snippet
Delete existing project snippet.
Deletes an existing project snippet. This is an idempotent function and deleting a non-existent
snippet still returns a `200 Ok` status code.
```
DELETE /projects/:id/snippets/:snippet_id
......@@ -105,5 +93,16 @@ Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of a project's snippet
Status code `200` will be returned on success.
## Snippet content
Returns the raw project snippet as plain text.
```
GET /projects/:id/snippets/:snippet_id/raw
```
Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of a project's snippet
......@@ -43,6 +43,7 @@ GET /users
]
```
## Single user
Get a single user.
......@@ -74,37 +75,40 @@ Parameters:
}
```
## User creation
Create user. Available only for admin
Creates a new user. Note only administrators can create new users.
```
POST /users
```
Parameters:
+ `email` (required) - Email
+ `password` (required) - Password
+ `username` (required) - Username
+ `name` (required) - Name
+ `skype` - Skype ID
+ `linkedin` - Linkedin
+ `twitter` - Twitter account
+ `projects_limit` - Number of projects user can create
+ `extern_uid` - External UID
+ `provider` - External provider name
+ `bio` - User's bio
+ `skype` (optional) - Skype ID
+ `linkedin` (optional) - Linkedin
+ `twitter` (optional) - Twitter account
+ `projects_limit` (optional) - Number of projects user can create
+ `extern_uid` (optional) - External UID
+ `provider` (optional) - External provider name
+ `bio` (optional) - User's bio
Will return created user with status `201 Created` on success, or `404 Not
found` on fail.
## User modification
Modify user. Available only for admin
Modifies an existing user. Only administrators can change attributes of a user.
```
PUT /users/:id
```
Parameters:
+ `email` - Email
+ `username` - Username
+ `name` - Name
......@@ -117,23 +121,28 @@ Parameters:
+ `provider` - External provider name
+ `bio` - User's bio
Note, at the moment this method does only return a 404 error, even in cases where a 409 (Conflict) would
be more appropriate, e.g. when renaming the email address to some exsisting one.
Will return created user with status `200 OK` on success, or `404 Not
found` on fail.
## User deletion
Delete user. Available only for admin
Deletes a user. Available only for administrators. This is an idempotent function, calling this function
for a non-existent user id still returns a status code `200 Ok`. The JSON response differs if the user
was actually deleted or not. In the former the user is returned and in the latter not.
```
DELETE /users/:id
```
Will return deleted user with status `200 OK` on success, or `404 Not
found` on fail.
Parameters:
+ `id` (required) - The ID of the user
## Current user
Get currently authenticated user.
Gets currently authenticated user.
```
GET /user
......@@ -156,6 +165,7 @@ GET /user
}
```
## List SSH keys
Get a list of currently authenticated user's SSH keys.
......@@ -183,6 +193,11 @@ GET /user/keys
]
```
Parameters:
+ **none**
## Single SSH key
Get a single key.
......@@ -204,9 +219,11 @@ Parameters:
soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}
```
## Add SSH key
Create new key owned by currently authenticated user
Creates a new key owned by the currently authenticated user.
```
POST /user/keys
......@@ -217,8 +234,6 @@ Parameters:
+ `title` (required) - new SSH Key's title
+ `key` (required) - new SSH key
Will return created key with status `201 Created` on success, or `404 Not
found` on fail.
## Add SSH key for user
......@@ -239,7 +254,8 @@ found` on fail.
## Delete SSH key
Delete key owned by currently authenticated user
Deletes key owned by currently authenticated user. This is an idempotent function and calling it on a key that is already
deleted or not available results in `200 Ok`.
```
DELETE /user/keys/:id
......@@ -249,4 +265,3 @@ Parameters:
+ `id` (required) - SSH key ID
Will return `200 OK` on success, or `404 Not Found` on fail.
......@@ -8,6 +8,19 @@ module Gitlab
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
......
......@@ -20,12 +20,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
......
......@@ -41,6 +41,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 +66,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
......
......@@ -48,6 +48,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
......
......@@ -4,6 +4,16 @@ module Gitlab
before { authenticate! }
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
end
# List merge requests
#
......@@ -51,6 +61,7 @@ module Gitlab
#
post ":id/merge_requests" do
authorize! :write_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
merge_request = user_project.merge_requests.new(attrs)
......@@ -60,7 +71,7 @@ module Gitlab
merge_request.reload_code
present merge_request, with: Entities::MergeRequest
else
not_found!
handle_merge_request_errors! merge_request.errors
end
end
......@@ -88,7 +99,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 +113,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
......
......@@ -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
......
......@@ -37,12 +37,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 +93,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
......
......@@ -4,6 +4,15 @@ module Gitlab
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:
......@@ -33,9 +42,11 @@ module Gitlab
# wall_enabled (optional) - enabled by default
# merge_requests_enabled (optional) - enabled by default
# wiki_enabled (optional) - enabled by default
# namespace_id (optional) - defaults to user namespace
# Example Request
# POST /projects
post do
required_attributes! [:name]
attrs = attributes_for_keys [:name,
:description,
:default_branch,
......@@ -48,6 +59,9 @@ module Gitlab
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
......@@ -122,16 +136,22 @@ module Gitlab
# POST /projects/:id/members
post ":id/members" do
authorize! :admin_project, user_project
users_project = user_project.users_projects.new(
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
......@@ -145,13 +165,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]
if users_project.update_attributes(project_access: params[:access_level])
@member = users_project.user
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 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
......@@ -164,8 +187,12 @@ 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
team_member = user_project.users_projects.find_by_user_id(params[:user_id])
unless team_member.nil?
team_member.destroy
else
{:message => "Access revoked", :id => params[:user_id].to_i}
end
end
# Get project hooks
......@@ -203,11 +230,16 @@ module Gitlab
# 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
error!({'message' => '404 Not found'}, 404)
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
not_found!
end
end
......@@ -222,27 +254,36 @@ module Gitlab
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
# Delete project hook
# 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
delete ":id/hooks" do
authorize! :admin_project, user_project
@hook = user_project.hooks.find(params[:hook_id])
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
# Get a project repository branches
......@@ -277,6 +318,7 @@ module Gitlab
# 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
......@@ -295,6 +337,7 @@ module Gitlab
# 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
......@@ -318,7 +361,7 @@ module Gitlab
#
# Parameters:
# id (required) - The ID of a project
# ref_name (optional) - The name of a repository branch or tag
# 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
......@@ -366,6 +409,7 @@ module Gitlab
# POST /projects/:id/snippets
post ":id/snippets" do
authorize! :write_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?
......@@ -414,10 +458,12 @@ module Gitlab
# 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_snippet, @snippet
authorize! :modify_snippet, user_project
@snippet.destroy
rescue
end
end
# Get a raw project snippet
......@@ -443,6 +489,7 @@ module Gitlab
# GET /projects/:id/repository/commits/:sha/blob
get ":id/repository/commits/:sha/blob" do
authorize! :download_code, user_project
required_attributes! [:filepath]
ref = params[:sha]
......
......@@ -41,6 +41,8 @@ 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
if user.save
......@@ -67,10 +69,12 @@ 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!
......@@ -147,6 +151,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
......@@ -156,15 +162,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
begin
key = current_user.keys.find params[:id]
key.delete
rescue
end
end
end
end
......
......@@ -65,7 +65,7 @@ describe Project do
it "should not allow new projects beyond user limits" do
project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1))
project.should_not be_valid
project.errors[:base].first.should match(/Your own projects limit is 1/)
project.errors[:limit_reached].first.should match(/Your own projects limit is 1/)
end
end
......
......@@ -88,6 +88,16 @@ describe Gitlab::API do
post api("/groups", admin), {:name => "Duplicate Test", :path => group2.path}
response.status.should == 404
end
it "should return 400 bad request error if name not given" do
post api("/groups", admin), { :path => group2.path }
response.status.should == 400
end
it "should return 400 bad request error if path not given" do
post api("/groups", admin), { :name => 'test' }
response.status.should == 400
end
end
end
......
......@@ -41,6 +41,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == issue.title
end
it "should return 404 if issue id not found" do
get api("/projects/#{project.id}/issues/54321", user)
response.status.should == 404
end
end
describe "POST /projects/:id/issues" do
......@@ -52,6 +57,11 @@ describe Gitlab::API do
json_response['description'].should be_nil
json_response['labels'].should == ['label', 'label2']
end
it "should return a 400 bad request if title not given" do
post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
response.status.should == 400
end
end
describe "PUT /projects/:id/issues/:issue_id to update only title" do
......@@ -62,6 +72,12 @@ describe Gitlab::API do
json_response['title'].should == 'updated title'
end
it "should return 404 error if issue id not found" do
put api("/projects/#{project.id}/issues/44444", user),
title: 'updated title'
response.status.should == 404
end
end
describe "PUT /projects/:id/issues/:issue_id to update state and label" do
......
......@@ -32,6 +32,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == merge_request.title
end
it "should return a 404 error if merge_request_id not found" do
get api("/projects/#{project.id}/merge_request/999", user)
response.status.should == 404
end
end
describe "POST /projects/:id/merge_requests" do
......@@ -41,6 +46,30 @@ describe Gitlab::API do
response.status.should == 201
json_response['title'].should == 'Test merge_request'
end
it "should return 422 when source_branch equals target_branch" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
response.status.should == 422
end
it "should return 400 when source_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", target_branch: "master", author: user
response.status.should == 400
end
it "should return 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "stable", author: user
response.status.should == 400
end
it "should return 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user),
target_branch: 'master', source_branch: 'stable'
response.status.should == 400
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
......@@ -59,13 +88,24 @@ describe Gitlab::API do
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
response.status.should == 200
json_response['title'].should == 'New title'
end
it "should return 422 when source_branch and target_branch are renamed the same" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
response.status.should == 422
end
it "should return merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "test"
response.status.should == 200
json_response['target_branch'].should == 'test'
end
end
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
......@@ -74,6 +114,16 @@ describe Gitlab::API do
response.status.should == 201
json_response['note'].should == 'My comment'
end
it "should return 400 if note is missing" do
post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
response.status.should == 400
end
it "should return 404 if note is attached to non existent merge request" do
post api("/projects/#{project.id}/merge_request/111/comments", user), note: "My comment"
response.status.should == 404
end
end
end
......@@ -16,6 +16,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['title'].should == milestone.title
end
it "should return a 401 error if user not authenticated" do
get api("/projects/#{project.id}/milestones")
response.status.should == 401
end
end
describe "GET /projects/:id/milestones/:milestone_id" do
......@@ -24,16 +29,38 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == milestone.title
end
it "should return 401 error if user not authenticated" do
get api("/projects/#{project.id}/milestones/#{milestone.id}")
response.status.should == 401
end
it "should return a 404 error if milestone id not found" do
get api("/projects/#{project.id}/milestones/1234", user)
response.status.should == 404
end
end
describe "POST /projects/:id/milestones" do
it "should create a new project milestone" do
post api("/projects/#{project.id}/milestones", user),
title: 'new milestone'
post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
response.status.should == 201
json_response['title'].should == 'new milestone'
json_response['description'].should be_nil
end
it "should create a new project milestone with description and due date" do
post api("/projects/#{project.id}/milestones", user),
title: 'new milestone', description: 'release', due_date: '2013-03-02'
response.status.should == 201
json_response['description'].should == 'release'
json_response['due_date'].should == '2013-03-02'
end
it "should return a 400 error if title is missing" do
post api("/projects/#{project.id}/milestones", user)
response.status.should == 400
end
end
describe "PUT /projects/:id/milestones/:milestone_id" do
......@@ -43,6 +70,12 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == 'updated title'
end
it "should return a 404 error if milestone id not found" do
put api("/projects/#{project.id}/milestones/1234", user),
title: 'updated title'
response.status.should == 404
end
end
describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
......
......@@ -38,6 +38,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['body'].should == wall_note.note
end
it "should return a 404 error if note not found" do
get api("/projects/#{project.id}/notes/123", user)
response.status.should == 404
end
end
describe "POST /projects/:id/notes" do
......@@ -46,6 +51,16 @@ describe Gitlab::API do
response.status.should == 201
json_response['body'].should == 'hi!'
end
it "should return 401 unauthorized error" do
post api("/projects/#{project.id}/notes")
response.status.should == 401
end
it "should return a 400 bad request if body is missing" do
post api("/projects/#{project.id}/notes", user)
response.status.should == 400
end
end
describe "GET /projects/:id/noteable/:noteable_id/notes" do
......@@ -56,6 +71,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['body'].should == issue_note.note
end
it "should return a 404 error when issue id not found" do
get api("/projects/#{project.id}/issues/123/notes", user)
response.status.should == 404
end
end
context "when noteable is a Snippet" do
......@@ -65,6 +85,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['body'].should == snippet_note.note
end
it "should return a 404 error when snippet id not found" do
get api("/projects/#{project.id}/snippets/42/notes", user)
response.status.should == 404
end
end
context "when noteable is a Merge Request" do
......@@ -74,6 +99,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['body'].should == merge_request_note.note
end
it "should return a 404 error if merge request id not found" do
get api("/projects/#{project.id}/merge_requests/4444/notes", user)
response.status.should == 404
end
end
end
......@@ -84,6 +114,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['body'].should == issue_note.note
end
it "should return a 404 error if issue note not found" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
response.status.should == 404
end
end
context "when noteable is a Snippet" do
......@@ -92,6 +127,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['body'].should == snippet_note.note
end
it "should return a 404 error if snippet note not found" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
response.status.should == 404
end
end
end
......@@ -103,6 +143,16 @@ describe Gitlab::API do
json_response['body'].should == 'hi!'
json_response['author']['email'].should == user.email
end
it "should return a 400 bad request error if body not given" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
response.status.should == 400
end
it "should return a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
response.status.should == 401
end
end
context "when noteable is a Snippet" do
......@@ -112,6 +162,16 @@ describe Gitlab::API do
json_response['body'].should == 'hi!'
json_response['author']['email'].should == user.email
end
it "should return a 400 bad request error if body not given" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
response.status.should == 400
end
it "should return a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
response.status.should == 401
end
end
end
end
This diff is collapsed.
......@@ -35,5 +35,15 @@ describe Gitlab::API do
json_response['private_token'].should be_nil
end
end
context "when empty name" do
it "should return authentication error" do
post api("/session"), password: user.password
response.status.should == 401
json_response['email'].should be_nil
json_response['private_token'].should be_nil
end
end
end
end
......@@ -31,15 +31,20 @@ describe Gitlab::API do
response.status.should == 200
json_response['email'].should == user.email
end
end
describe "POST /users" do
before{ admin }
it "should return a 401 if unauthenticated" do
get api("/users/9998")
response.status.should == 401
end
it "should not create invalid user" do
post api("/users", admin), { email: "invalid email" }
it "should return a 404 error if user id not found" do
get api("/users/9999", user)
response.status.should == 404
end
end
describe "POST /users" do
before{ admin }
it "should create user" do
expect {
......@@ -47,10 +52,48 @@ describe Gitlab::API do
}.to change { User.count }.by(1)
end
it "should return 201 Created on success" do
post api("/users", admin), attributes_for(:user, projects_limit: 3)
response.status.should == 201
end
it "should not create user with invalid email" do
post api("/users", admin), { email: "invalid email", password: 'password' }
response.status.should == 400
end
it "should return 400 error if password not given" do
post api("/users", admin), { email: 'test@example.com' }
response.status.should == 400
end
it "should return 400 error if email not given" do
post api("/users", admin), { password: 'pass1234' }
response.status.should == 400
end
it "shouldn't available for non admin users" do
post api("/users", user), attributes_for(:user)
response.status.should == 403
end
context "with existing user" do
before { post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test' } }
it "should not create user with same email" do
expect {
post api("/users", admin), { email: 'test@example.com', password: 'password' }
}.to change { User.count }.by(0)
end
it "should return 409 conflict error if user with email exists" do
post api("/users", admin), { email: 'test@example.com', password: 'password' }
end
it "should return 409 conflict error if same username exists" do
post api("/users", admin), { email: 'foo@example.com', password: 'pass', username: 'test' }
end
end
end
describe "GET /users/sign_up" do
......@@ -81,7 +124,7 @@ describe Gitlab::API do
describe "PUT /users/:id" do
before { admin }
it "should update user" do
it "should update user with new bio" do
put api("/users/#{user.id}", admin), {bio: 'new test bio'}
response.status.should == 200
json_response['bio'].should == 'new test bio'
......@@ -103,6 +146,25 @@ describe Gitlab::API do
put api("/users/999999", admin), {bio: 'update should fail'}
response.status.should == 404
end
context "with existing user" do
before {
post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' }
post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' }
@user_id = User.all.last.id
}
# it "should return 409 conflict error if email address exists" do
# put api("/users/#{@user_id}", admin), { email: 'test@example.com' }
# response.status.should == 409
# end
#
# it "should return 409 conflict error if username taken" do
# @user_id = User.all.last.id
# put api("/users/#{@user_id}", admin), { username: 'test' }
# response.status.should == 409
# end
end
end
describe "POST /users/:id/keys" do
......@@ -131,6 +193,11 @@ describe Gitlab::API do
json_response['email'].should == user.email
end
it "should not delete for unauthenticated user" do
delete api("/users/#{user.id}")
response.status.should == 401
end
it "shouldn't available for non admin users" do
delete api("/users/#{user.id}", user)
response.status.should == 403
......@@ -148,6 +215,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['email'].should == user.email
end
it "should return 401 error if user is unauthenticated" do
get api("/user")
response.status.should == 401
end
end
describe "GET /user/keys" do
......@@ -183,19 +255,38 @@ describe Gitlab::API do
get api("/user/keys/42", user)
response.status.should == 404
end
end
describe "POST /user/keys" do
it "should not create invalid ssh key" do
post api("/user/keys", user), { title: "invalid key" }
it "should return 404 error if admin accesses user's ssh key" do
user.keys << key
user.save
admin
get api("/user/keys/#{key.id}", admin)
response.status.should == 404
end
end
describe "POST /user/keys" do
it "should create ssh key" do
key_attrs = attributes_for :key
expect {
post api("/user/keys", user), key_attrs
}.to change{ user.keys.count }.by(1)
response.status.should == 201
end
it "should return a 401 error if unauthorized" do
post api("/user/keys"), title: 'some title', key: 'some key'
response.status.should == 401
end
it "should not create ssh key without key" do
post api("/user/keys", user), title: 'title'
response.status.should == 400
end
it "should not create ssh key without title" do
post api("/user/keys", user), key: "somekey"
response.status.should == 400
end
end
......@@ -206,11 +297,19 @@ describe Gitlab::API do
expect {
delete api("/user/keys/#{key.id}", user)
}.to change{user.keys.count}.by(-1)
response.status.should == 200
end
it "should return 404 Not Found within invalid ID" do
it "should return sucess if key ID not found" do
delete api("/user/keys/42", user)
response.status.should == 404
response.status.should == 200
end
it "should return 401 error if unauthorized" do
user.keys << key
user.save
delete api("/user/keys/#{key.id}")
response.status.should == 401
end
end
end
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