summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémy Coutable <remy@rymai.me>2016-04-13 15:05:21 +0000
committerRémy Coutable <remy@rymai.me>2016-04-13 15:05:21 +0000
commit94e130cec30a8cfa6687d254e1cabce568a22634 (patch)
treef92966b9197edc7360c94de457341c3f086e7a42
parenteebd7533ea780dade5e953b05c83c7b87f4bbf6f (diff)
parent54231aa4e036179d035ddd3641bc15a5b31883f2 (diff)
downloadgitlab-ce-94e130cec30a8cfa6687d254e1cabce568a22634.tar.gz
Merge branch 'api-star-project' into 'master'
API: Star and unstar a project Add two new endpoints `POST /projects/:id/star` and `POST /projects/:id/unstar` to star and unstar a project. * Closes #12739 See merge request !3578
-rw-r--r--CHANGELOG1
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/projects.md126
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/projects.rb34
-rw-r--r--spec/requests/api/projects_spec.rb48
6 files changed, 214 insertions, 0 deletions
diff --git a/CHANGELOG b/CHANGELOG
index ec8db471572..2ab0cc11248 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -28,6 +28,7 @@ v 8.7.0 (unreleased)
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
+ - API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService
diff --git a/doc/api/README.md b/doc/api/README.md
index 7629ef294ac..3a8fa6cebd1 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -108,6 +108,7 @@ The following table shows the possible return codes for API requests.
| ------------- | ----------- |
| `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. |
+| `304 Not Modified` | Indicates that the resource has not been modified since the last request. |
| `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](#authentication) is necessary. |
| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. |
diff --git a/doc/api/projects.md b/doc/api/projects.md
index ab716c229dc..de1faadebf5 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -491,6 +491,132 @@ Parameters:
- `id` (required) - The ID of the project to be forked
+### Star a project
+
+Stars a given project. Returns status code `201` and the project on success and
+`304` if the project is already starred.
+
+```
+POST /projects/:id/star
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
+```
+
+Example response:
+
+```json
+{
+ "id": 3,
+ "description": null,
+ "default_branch": "master",
+ "public": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "builds_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
+ "namespace": {
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "description": "",
+ "id": 3,
+ "name": "Diaspora",
+ "owner_id": 1,
+ "path": "diaspora",
+ "updated_at": "2013-09-30T13: 46: 02Z"
+ },
+ "archived": true,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 1
+}
+```
+
+### Unstar a project
+
+Unstars a given project. Returns status code `200` and the project on success
+and `304` if the project is not starred.
+
+```
+DELETE /projects/:id/star
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
+```
+
+Example response:
+
+```json
+{
+ "id": 3,
+ "description": null,
+ "default_branch": "master",
+ "public": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "builds_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
+ "namespace": {
+ "created_at": "2013-09-30T13: 46: 02Z",
+ "description": "",
+ "id": 3,
+ "name": "Diaspora",
+ "owner_id": 1,
+ "path": "diaspora",
+ "updated_at": "2013-09-30T13: 46: 02Z"
+ },
+ "archived": true,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0
+}
+```
+
### Archive a project
Archives the project if the user is either admin or the project owner of this project. This action is
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 96af7d7675c..5bbf721321d 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -240,6 +240,10 @@ module API
render_api_error!('413 Request Entity Too Large', 413)
end
+ def not_modified!
+ render_api_error!('304 Not Modified', 304)
+ end
+
def render_validation_error!(model)
if model.errors.any?
render_api_error!(model.errors.messages || '400 Bad Request', 400)
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 24b31005475..cc2c7a0c503 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -272,6 +272,40 @@ module API
present user_project, with: Entities::Project
end
+ # Star project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # POST /projects/:id/star
+ post ':id/star' do
+ if current_user.starred?(user_project)
+ not_modified!
+ else
+ current_user.toggle_star(user_project)
+ user_project.reload
+
+ present user_project, with: Entities::Project
+ end
+ end
+
+ # Unstar project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # DELETE /projects/:id/star
+ delete ':id/star' do
+ if current_user.starred?(user_project)
+ current_user.toggle_star(user_project)
+ user_project.reload
+
+ present user_project, with: Entities::Project
+ else
+ not_modified!
+ end
+ end
+
# Remove project
#
# Parameters:
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index be2034e0f39..fccd08bd6da 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1020,6 +1020,54 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/star' do
+ context 'on an unstarred project' do
+ it 'stars the project' do
+ expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response['star_count']).to eq(1)
+ end
+ end
+
+ context 'on a starred project' do
+ before do
+ user.toggle_star(project)
+ project.reload
+ end
+
+ it 'does not modify the star count' do
+ expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
+
+ expect(response.status).to eq(304)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/star' do
+ context 'on a starred project' do
+ before do
+ user.toggle_star(project)
+ project.reload
+ end
+
+ it 'unstars the project' do
+ expect { delete api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1)
+
+ expect(response.status).to eq(200)
+ expect(json_response['star_count']).to eq(0)
+ end
+ end
+
+ context 'on an unstarred project' do
+ it 'does not modify the star count' do
+ expect { delete api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
+
+ expect(response.status).to eq(304)
+ end
+ end
+ end
+
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
it 'should remove project' do