From f89483a206b4aea3a307000c5ed7c0e9da5bea93 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Mon, 18 Jul 2011 20:53:10 +0000 Subject: [PATCH] REST API for reading attachments (#7671). git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@6295 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/attachments_controller.rb | 24 +++--- app/helpers/attachments_helper.rb | 13 ++++ app/views/attachments/show.api.rsb | 1 + app/views/issues/show.api.rsb | 6 ++ config/routes.rb | 1 + test/integration/api_test/attachments_test.rb | 78 +++++++++++++++++++ test/integration/api_test/issues_test.rb | 30 ++++++- test/integration/routing_test.rb | 2 + 8 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 app/views/attachments/show.api.rsb create mode 100644 test/integration/api_test/attachments_test.rb diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index 2a68a2a9a..b0e09cb80 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -20,17 +20,22 @@ class AttachmentsController < ApplicationController before_filter :file_readable, :read_authorize, :except => :destroy before_filter :delete_authorize, :only => :destroy - verify :method => :post, :only => :destroy + accept_api_auth :show, :download def show - if @attachment.is_diff? - @diff = File.new(@attachment.diskfile, "rb").read - render :action => 'diff' - elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte - @content = File.new(@attachment.diskfile, "rb").read - render :action => 'file' - else - download + respond_to do |format| + format.html { + if @attachment.is_diff? + @diff = File.new(@attachment.diskfile, "rb").read + render :action => 'diff' + elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte + @content = File.new(@attachment.diskfile, "rb").read + render :action => 'file' + else + download + end + } + format.api end end @@ -46,6 +51,7 @@ class AttachmentsController < ApplicationController end + verify :method => :post, :only => :destroy def destroy # Make sure association callbacks are called @attachment.container.attachments.delete(@attachment) diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb index e6150a3b3..f7e4d8420 100644 --- a/app/helpers/attachments_helper.rb +++ b/app/helpers/attachments_helper.rb @@ -43,4 +43,17 @@ module AttachmentsHelper str end end + + def render_api_attachment(attachment, api) + api.attachment do + api.id attachment.id + api.filename attachment.filename + api.filesize attachment.filesize + api.content_type attachment.content_type + api.description attachment.description + api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false) + api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author + api.created_on attachment.created_on + end + end end diff --git a/app/views/attachments/show.api.rsb b/app/views/attachments/show.api.rsb new file mode 100644 index 000000000..5a9f74a91 --- /dev/null +++ b/app/views/attachments/show.api.rsb @@ -0,0 +1 @@ +render_api_attachment(@attachment, api) diff --git a/app/views/issues/show.api.rsb b/app/views/issues/show.api.rsb index 9d98959a9..ccc3e8767 100644 --- a/app/views/issues/show.api.rsb +++ b/app/views/issues/show.api.rsb @@ -25,6 +25,12 @@ api.issue do render_api_issue_children(@issue, api) if include_in_api_response?('children') + api.array :attachments do + @issue.attachments.each do |attachment| + render_api_attachment(attachment, api) + end + end if include_in_api_response?('attachments') + api.array :relations do @relations.each do |relation| api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) diff --git a/config/routes.rb b/config/routes.rb index 3188c9fc4..17c36785f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -220,6 +220,7 @@ ActionController::Routing::Routes.draw do |map| end map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/ + map.connect 'attachments/:id.:format', :controller => 'attachments', :action => 'show', :id => /\d+/ map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/ map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/ diff --git a/test/integration/api_test/attachments_test.rb b/test/integration/api_test/attachments_test.rb new file mode 100644 index 000000000..a0c57ee38 --- /dev/null +++ b/test/integration/api_test/attachments_test.rb @@ -0,0 +1,78 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../../test_helper', __FILE__) + +class ApiTest::AttachmentsTest < ActionController::IntegrationTest + fixtures :all + + def setup + Setting.rest_api_enabled = '1' + Attachment.storage_path = "#{Rails.root}/test/fixtures/files" + end + + context "/attachments/:id" do + context "GET" do + should "return the attachment" do + get '/attachments/7.xml', {}, :authorization => credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'attachment', + :child => { + :tag => 'id', + :content => '7', + :sibling => { + :tag => 'filename', + :content => 'archive.zip', + :sibling => { + :tag => 'content_url', + :content => 'http://www.example.com/attachments/download/7/archive.zip' + } + } + } + end + + should "deny access without credentials" do + get '/attachments/7.xml' + + assert_response 401 + end + end + end + + context "/attachments/download/:id/:filename" do + context "GET" do + should "return the attachment content" do + get '/attachments/download/7/archive.zip', {}, :authorization => credentials('jsmith') + + assert_response :success + assert_equal 'application/octet-stream', @response.content_type + end + + should "deny access without credentials" do + get '/attachments/download/7/archive.zip' + + assert_response 302 + end + end + end + + def credentials(user, password=nil) + ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) + end +end diff --git a/test/integration/api_test/issues_test.rb b/test/integration/api_test/issues_test.rb index bfcd63e2a..aca639556 100644 --- a/test/integration/api_test/issues_test.rb +++ b/test/integration/api_test/issues_test.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2010 Jean-Philippe Lang +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -40,7 +40,8 @@ class ApiTest::IssuesTest < ActionController::IntegrationTest :time_entries, :journals, :journal_details, - :queries + :queries, + :attachments def setup Setting.rest_api_enabled = '1' @@ -201,6 +202,31 @@ class ApiTest::IssuesTest < ActionController::IntegrationTest end end + context "with attachments" do + context ".xml" do + should "display attachments" do + get '/issues/3.xml?include=attachments' + + assert_tag :tag => 'issue', + :child => { + :tag => 'attachments', + :children => {:count => 5}, + :child => { + :tag => 'attachment', + :child => { + :tag => 'filename', + :content => 'source.rb', + :sibling => { + :tag => 'content_url', + :content => 'http://www.example.com/attachments/download/4/source.rb' + } + } + } + } + end + end + end + context "with subtasks" do setup do @c1 = Issue.generate!(:status_id => 1, :subject => "child c1", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1) diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index d25ed3d73..fd6ae632e 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -25,6 +25,8 @@ class RoutingTest < ActionController::IntegrationTest context "attachments" do should_route :get, "/attachments/1", :controller => 'attachments', :action => 'show', :id => '1' + should_route :get, "/attachments/1.xml", :controller => 'attachments', :action => 'show', :id => '1', :format => 'xml' + should_route :get, "/attachments/1.json", :controller => 'attachments', :action => 'show', :id => '1', :format => 'json' should_route :get, "/attachments/1/filename.ext", :controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext' should_route :get, "/attachments/download/1", :controller => 'attachments', :action => 'download', :id => '1' should_route :get, "/attachments/download/1/filename.ext", :controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext'