From 0f604e62fb453f2359ebc0438fe7dfaff8e55d10 Mon Sep 17 00:00:00 2001 From: Nihad Abbasov Date: Fri, 6 Jul 2012 06:08:17 -0700 Subject: [PATCH 1/4] refactor projects API --- lib/api/entities.rb | 6 +----- lib/api/helpers.rb | 4 ++++ lib/api/projects.rb | 24 ++++++++---------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 35ad4d43..f57545ff 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -16,11 +16,7 @@ module Gitlab expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at end - class ProjectRepositoryBranches < Grape::Entity - expose :name, :commit - end - - class ProjectRepositoryTags < Grape::Entity + class RepoObject < Grape::Entity expose :name, :commit end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 424a17b2..e7b9a417 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -4,6 +4,10 @@ module Gitlab @current_user ||= User.find_by_authentication_token(params[:private_token]) end + def user_project + @project ||= current_user.projects.find_by_code(params[:id]) + end + def authenticate! error!({'message' => '401 Unauthorized'}, 401) unless current_user end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 70f8fa2a..34d1c236 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -20,8 +20,7 @@ module Gitlab # Example Request: # GET /projects/:id get ":id" do - @project = current_user.projects.find_by_code(params[:id]) - present @project, :with => Entities::Project + present user_project, :with => Entities::Project end # Get a project repository branches @@ -31,8 +30,7 @@ module Gitlab # Example Request: # GET /projects/:id/repository/branches get ":id/repository/branches" do - @project = current_user.projects.find_by_code(params[:id]) - present @project.repo.heads.sort_by(&:name), :with => Entities::ProjectRepositoryBranches + present user_project.repo.heads.sort_by(&:name), :with => Entities::RepoObject end # Get a project repository tags @@ -42,8 +40,7 @@ module Gitlab # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do - @project = current_user.projects.find_by_code(params[:id]) - present @project.repo.tags.sort_by(&:name).reverse, :with => Entities::ProjectRepositoryTags + present user_project.repo.tags.sort_by(&:name).reverse, :with => Entities::RepoObject end # Get a project snippet @@ -54,8 +51,7 @@ module Gitlab # Example Request: # GET /projects/:id/snippets/:snippet_id get ":id/snippets/:snippet_id" do - @project = current_user.projects.find_by_code(params[:id]) - @snippet = @project.snippets.find(params[:snippet_id]) + @snippet = user_project.snippets.find(params[:snippet_id]) present @snippet, :with => Entities::ProjectSnippet end @@ -70,8 +66,7 @@ module Gitlab # Example Request: # POST /projects/:id/snippets post ":id/snippets" do - @project = current_user.projects.find_by_code(params[:id]) - @snippet = @project.snippets.new( + @snippet = user_project.snippets.new( :title => params[:title], :file_name => params[:file_name], :expires_at => params[:lifetime], @@ -98,8 +93,7 @@ module Gitlab # Example Request: # PUT /projects/:id/snippets/:snippet_id put ":id/snippets/:snippet_id" do - @project = current_user.projects.find_by_code(params[:id]) - @snippet = @project.snippets.find(params[:snippet_id]) + @snippet = user_project.snippets.find(params[:snippet_id]) parameters = { :title => (params[:title] || @snippet.title), :file_name => (params[:file_name] || @snippet.file_name), @@ -122,8 +116,7 @@ module Gitlab # Example Request: # DELETE /projects/:id/snippets/:snippet_id delete ":id/snippets/:snippet_id" do - @project = current_user.projects.find_by_code(params[:id]) - @snippet = @project.snippets.find(params[:snippet_id]) + @snippet = user_project.snippets.find(params[:snippet_id]) @snippet.destroy end @@ -135,8 +128,7 @@ module Gitlab # Example Request: # GET /projects/:id/snippets/:snippet_id/raw get ":id/snippets/:snippet_id/raw" do - @project = current_user.projects.find_by_code(params[:id]) - @snippet = @project.snippets.find(params[:snippet_id]) + @snippet = user_project.snippets.find(params[:snippet_id]) present @snippet.content end end From 7b33d8cbcab3b0ee5789ec607455ab62130db69f Mon Sep 17 00:00:00 2001 From: Nihad Abbasov Date: Tue, 24 Jul 2012 05:19:51 -0700 Subject: [PATCH 2/4] add issues API --- lib/api.rb | 1 + lib/api/entities.rb | 12 +++++ lib/api/issues.rb | 111 ++++++++++++++++++++++++++++++++++++++++ spec/api/issues_spec.rb | 71 +++++++++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 lib/api/issues.rb create mode 100644 spec/api/issues_spec.rb diff --git a/lib/api.rb b/lib/api.rb index e24e0a78..6a8a3d65 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -15,5 +15,6 @@ module Gitlab mount Users mount Projects + mount Issues end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f57545ff..e5095f71 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -25,5 +25,17 @@ module Gitlab expose :author, :using => Entities::UserBasic expose :expires_at, :updated_at, :created_at end + + class Milestone < Grape::Entity + expose :id, :title, :description, :due_date, :closed, :updated_at, :created_at + end + + class Issue < Grape::Entity + expose :id, :title, :description + expose :label_list, :as => :labels + expose :milestone, :using => Entities::Milestone + expose :assignee, :author, :using => Entities::UserBasic + expose :closed, :updated_at, :created_at + end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb new file mode 100644 index 00000000..2b6a1ac2 --- /dev/null +++ b/lib/api/issues.rb @@ -0,0 +1,111 @@ +module Gitlab + # Issues API + class Issues < Grape::API + before { authenticate! } + + resource :issues do + # Get currently authenticated user's issues + # + # Example Request: + # GET /issues + get do + present current_user.issues, :with => Entities::Issue + end + end + + resource :projects do + # Get a list of project issues + # + # Parameters: + # id (required) - The code name of a project + # Example Request: + # GET /projects/:id/issues + get ":id/issues" do + present user_project.issues, :with => Entities::Issue + end + + # Get a single project issue + # + # Parameters: + # id (required) - The code name of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # GET /projects/:id/issues/:issue_id + get ":id/issues/:issue_id" do + @issue = user_project.issues.find(params[:issue_id]) + present @issue, :with => Entities::Issue + end + + # Create a new project issue + # + # Parameters: + # id (required) - The code name of a project + # title (required) - The title of an issue + # description (optional) - The description of an issue + # 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 + # Example Request: + # POST /projects/:id/issues + post ":id/issues" do + @issue = user_project.issues.new( + :title => params[:title], + :description => params[:description], + :assignee_id => params[:assignee_id], + :milestone_id => params[:milestone_id], + :label_list => params[:labels] + ) + @issue.author = current_user + + if @issue.save + present @issue, :with => Entities::Issue + else + error!({'message' => '404 Not found'}, 404) + end + end + + # Update an existing issue + # + # Parameters: + # id (required) - The code name of a project + # issue_id (required) - The ID of a project issue + # title (optional) - The title of an issue + # description (optional) - The description of an issue + # 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) + # Example Request: + # PUT /projects/:id/issues/:issue_id + put ":id/issues/:issue_id" do + @issue = user_project.issues.find(params[:issue_id]) + parameters = { + :title => (params[:title] || @issue.title), + :description => (params[:description] || @issue.description), + :assignee_id => (params[:assignee_id] || @issue.assignee_id), + :milestone_id => (params[:milestone_id] || @issue.milestone_id), + :label_list => (params[:labels] || @issue.label_list), + :closed => (params[:closed] || @issue.closed) + } + + if @issue.update_attributes(parameters) + present @issue, :with => Entities::Issue + else + error!({'message' => '404 Not found'}, 404) + end + end + + # Delete a project issue + # + # Parameters: + # id (required) - The code name of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # DELETE /projects/:id/issues/:issue_id + delete ":id/issues/:issue_id" do + @issue = user_project.issues.find(params[:issue_id]) + @issue.destroy + end + end + end +end diff --git a/spec/api/issues_spec.rb b/spec/api/issues_spec.rb new file mode 100644 index 00000000..d81a07e2 --- /dev/null +++ b/spec/api/issues_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Gitlab::API do + let(:user) { Factory :user } + let!(:project) { Factory :project, :owner => user } + let!(:issue) { Factory :issue, :author => user, :assignee => user, :project => project } + before { project.add_access(user, :read) } + + describe "GET /issues" do + it "should return authentication error" do + get "#{api_prefix}/issues" + response.status.should == 401 + end + + describe "authenticated GET /issues" do + it "should return an array of issues" do + get "#{api_prefix}/issues?private_token=#{user.private_token}" + response.status.should == 200 + json_response.should be_an Array + json_response.first['title'].should == issue.title + end + end + end + + describe "GET /projects/:id/issues" do + it "should return project issues" do + get "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}" + response.status.should == 200 + json_response.should be_an Array + json_response.first['title'].should == issue.title + end + end + + describe "GET /projects/:id/issues/:issue_id" do + it "should return a project issue by id" do + get "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" + response.status.should == 200 + json_response['title'].should == issue.title + end + end + + describe "POST /projects/:id/issues" do + it "should create a new project issue" do + post "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}", + :title => 'new issue', :labels => 'label, label2' + response.status.should == 201 + json_response['title'].should == 'new issue' + json_response['description'].should be_nil + json_response['labels'].should == ['label', 'label2'] + end + end + + describe "PUT /projects/:id/issues/:issue_id" do + it "should update a project issue" do + put "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}", + :title => 'updated title', :labels => 'label2', :closed => 1 + response.status.should == 200 + json_response['title'].should == 'updated title' + json_response['labels'].should == ['label2'] + json_response['closed'].should be_true + end + end + + describe "DELETE /projects/:id/issues/:issue_id" do + it "should delete a project issue" do + expect { + delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" + }.to change { Issue.count }.by(-1) + end + end +end From 024e0348904179a8dea81c01e27a5f014cf57499 Mon Sep 17 00:00:00 2001 From: Nihad Abbasov Date: Tue, 24 Jul 2012 05:25:01 -0700 Subject: [PATCH 3/4] update API docs --- doc/api/README.md | 1 + doc/api/issues.md | 181 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 doc/api/issues.md diff --git a/doc/api/README.md b/doc/api/README.md index dcf75afd..e0111966 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -27,3 +27,4 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md) + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md) ++ [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md) diff --git a/doc/api/issues.md b/doc/api/issues.md new file mode 100644 index 00000000..bad2d474 --- /dev/null +++ b/doc/api/issues.md @@ -0,0 +1,181 @@ +## List issues + +Get all issues created by authenticed user. + +``` +GET /issues +``` + +```json +[ + { + "id": 43, + "title": "4xx/5xx pages", + "description": "", + "labels": [ ], + "milestone": null, + "assignee": null, + "author": { + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "closed": true, + "updated_at": "2012-07-02T17:53:12Z", + "created_at": "2012-07-02T17:53:12Z" + }, + { + "id": 42, + "title": "Add user settings", + "description": "", + "labels": [ + "feature" + ], + "milestone": { + "id": 1, + "title": "v1.0", + "description": "", + "due_date": "2012-07-20", + "closed": false, + "updated_at": "2012-07-04T13:42:48Z", + "created_at": "2012-07-04T13:42:48Z" + }, + "assignee": { + "id": 2, + "email": "jack@example.com", + "name": "Jack Smith", + "blocked": false, + "created_at": "2012-05-23T08:01:01Z" + }, + "author": { + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "closed": false, + "updated_at": "2012-07-12T13:43:19Z", + "created_at": "2012-06-28T12:58:06Z" + } +] +``` + +## List project issues + +Get a list of project issues. + +``` +GET /projects/:id/issues +``` + +Parameters: + ++ `id` (required) - The code name of a project + +## Single issue + +Get a project issue. + +``` +GET /projects/:id/issues/:issue_id +``` + +Parameters: + ++ `id` (required) - The code name of a project ++ `issue_id` (required) - The ID of a project issue + +```json +{ + "id": 42, + "title": "Add user settings", + "description": "", + "labels": [ + "feature" + ], + "milestone": { + "id": 1, + "title": "v1.0", + "description": "", + "due_date": "2012-07-20", + "closed": false, + "updated_at": "2012-07-04T13:42:48Z", + "created_at": "2012-07-04T13:42:48Z" + }, + "assignee": { + "id": 2, + "email": "jack@example.com", + "name": "Jack Smith", + "blocked": false, + "created_at": "2012-05-23T08:01:01Z" + }, + "author": { + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "closed": false, + "updated_at": "2012-07-12T13:43:19Z", + "created_at": "2012-06-28T12:58:06Z" +} +``` + +## New issue + +Create a new project issue. + +``` +POST /projects/:id/issues +``` + +Parameters: + ++ `id` (required) - The code name of a project ++ `title` (required) - The title of an issue ++ `description` (optional) - The description of an issue ++ `assignee_id` (optional) - The ID of a user to assign issue ++ `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. + +``` +PUT /projects/:id/issues/:issue_id +``` + +Parameters: + ++ `id` (required) - The code name of a project ++ `issue_id` (required) - The ID of a project's issue ++ `title` (optional) - The title of an issue ++ `description` (optional) - The description of an issue ++ `assignee_id` (optional) - The ID of a user to assign issue ++ `milestone_id` (optional) - The ID of a milestone to assign issue ++ `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 issue + +Delete existing project issue. + +``` +DELETE /projects/:id/issues/:issue_id +``` + +Parameters: + ++ `id` (required) - The code name of a project ++ `issue_id` (required) - The ID of a project's issue + +Status code `200` will be returned on success. From fbb41100db35cf2def2c8b4d896b7015d56bd15b Mon Sep 17 00:00:00 2001 From: Nihad Abbasov Date: Tue, 24 Jul 2012 05:52:43 -0700 Subject: [PATCH 4/4] update help section with issues API docs --- app/views/help/api.html.haml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml index 4964c1bb..e184df54 100644 --- a/app/views/help/api.html.haml +++ b/app/views/help/api.html.haml @@ -1,16 +1,18 @@ %h3 API .back_link - = link_to help_path do + = link_to help_path do ← to index %hr %ol - %li + %li %a{:href => "#README"} README - %li + %li %a{:href => "#projects"} Projects - %li + %li %a{:href => "#users"} Users + %li + %a{:href => "#issues"} Issues .file_holder#README .file_title @@ -39,3 +41,13 @@ .file_content.wiki = preserve do = markdown File.read(Rails.root.join("doc", "api", "users.md")) + +%br + +.file_holder#issues + .file_title + %i.icon-file + Issues + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "issues.md"))