##// END OF EJS Templates
Files REST API (#19116)....
Jean-Philippe Lang -
r15727:e068f855dcc7
parent child
Show More
@@ -0,0 +1,14
1 api.array :files do
2 @containers.each do |container|
3 container.attachments.each do |attachment|
4 api.file do
5 render_api_attachment_attributes(attachment, api)
6 if container.is_a?(Version)
7 api.version :id => container.id, :name => container.name
8 end
9 api.digest attachment.digest
10 api.downloads attachment.downloads
11 end
12 end
13 end
14 end
@@ -0,0 +1,115
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../test_helper', __FILE__)
19
20 class Redmine::ApiTest::FilesTest < Redmine::ApiTest::Base
21 fixtures :projects,
22 :users,
23 :members,
24 :roles,
25 :member_roles,
26 :enabled_modules,
27 :attachments,
28 :versions
29
30 test "GET /projects/:project_id/files.xml should return the list of uploaded files" do
31 get '/projects/1/files.xml', {}, credentials('jsmith')
32 assert_response :success
33 assert_select 'files>file>id', :text => '8'
34 end
35
36 test "POST /projects/:project_id/files.json should create a file" do
37 set_tmp_attachments_directory
38 post '/uploads.xml', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
39 token = Attachment.last.token
40 payload = <<-JSON
41 { "file": {
42 "token": "#{token}"
43 }
44 }
45 JSON
46 post '/projects/1/files.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
47 assert_response :success
48 assert_equal 1, Attachment.last.container_id
49 assert_equal "Project", Attachment.last.container_type
50 end
51
52 test "POST /projects/:project_id/files.xml should create a file" do
53 set_tmp_attachments_directory
54 post '/uploads.xml', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
55 token = Attachment.last.token
56 payload = <<-XML
57 <file>
58 <token>#{token}</token>
59 </file>
60 XML
61 post '/projects/1/files.xml', payload, {"CONTENT_TYPE" => 'application/xml'}.merge(credentials('jsmith'))
62 assert_response :success
63 assert_equal 1, Attachment.last.container_id
64 assert_equal "Project", Attachment.last.container_type
65 end
66
67 test "POST /projects/:project_id/files.json should refuse requests without the :token parameter" do
68 payload = <<-JSON
69 { "file": {
70 "filename": "project_file.zip",
71 }
72 }
73 JSON
74 post '/projects/1/files.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
75 assert_response :bad_request
76 end
77
78 test "POST /projects/:project_id/files.json should accept :filename, :description, :content_type as optional parameters" do
79 set_tmp_attachments_directory
80 post '/uploads.xml', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
81 token = Attachment.last.token
82 payload = <<-JSON
83 { "file": {
84 "filename": "New filename",
85 "description": "New description",
86 "content_type": "application/txt",
87 "token": "#{token}"
88 }
89 }
90 JSON
91 post '/projects/1/files.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
92 assert_response :success
93 assert_equal "New filename", Attachment.last.filename
94 assert_equal "New description", Attachment.last.description
95 assert_equal "application/txt", Attachment.last.content_type
96 end
97
98 test "POST /projects/:project_id/files.json should accept :version_id to attach the files to a version" do
99 set_tmp_attachments_directory
100 post '/uploads.xml', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
101 token = Attachment.last.token
102 payload = <<-JSON
103 { "file": {
104 "version_id": 3,
105 "filename": "New filename",
106 "description": "New description",
107 "token": "#{token}"
108 }
109 }
110 JSON
111 post '/projects/1/files.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
112 assert_equal 3, Attachment.last.container_id
113 assert_equal "Version", Attachment.last.container_type
114 end
115 end
@@ -1,62 +1,76
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class FilesController < ApplicationController
19 19 menu_item :files
20 20
21 21 before_action :find_project_by_project_id
22 22 before_action :authorize
23 accept_api_auth :index, :create
23 24
25 helper :attachments
24 26 helper :sort
25 27 include SortHelper
26 28
27 29 def index
28 30 sort_init 'filename', 'asc'
29 31 sort_update 'filename' => "#{Attachment.table_name}.filename",
30 32 'created_on' => "#{Attachment.table_name}.created_on",
31 33 'size' => "#{Attachment.table_name}.filesize",
32 34 'downloads' => "#{Attachment.table_name}.downloads"
33 35
34 36 @containers = [Project.includes(:attachments).
35 37 references(:attachments).reorder(sort_clause).find(@project.id)]
36 38 @containers += @project.versions.includes(:attachments).
37 39 references(:attachments).reorder(sort_clause).to_a.sort.reverse
38 render :layout => !request.xhr?
40 respond_to do |format|
41 format.html { render :layout => !request.xhr? }
42 format.api
43 end
39 44 end
40 45
41 46 def new
42 47 @versions = @project.versions.sort
43 48 end
44 49
45 50 def create
46 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
47 attachments = Attachment.attach_files(container, params[:attachments])
51 version_id = params[:version_id] || (params[:file] && params[:file][:version_id])
52 container = version_id.blank? ? @project : @project.versions.find_by_id(version_id)
53 attachments = Attachment.attach_files(container, (params[:attachments] || (params[:file] && params[:file][:token] && params)))
48 54 render_attachment_warning_if_needed(container)
49 55
50 56 if attachments[:files].present?
51 57 if Setting.notified_events.include?('file_added')
52 58 Mailer.attachments_added(attachments[:files]).deliver
53 59 end
54 flash[:notice] = l(:label_file_added)
55 redirect_to project_files_path(@project)
60 respond_to do |format|
61 format.html {
62 flash[:notice] = l(:label_file_added)
63 redirect_to project_files_path(@project) }
64 format.api { render_api_ok }
65 end
56 66 else
57 flash.now[:error] = l(:label_attachment) + " " + l('activerecord.errors.messages.invalid')
58 new
59 render :action => 'new'
67 respond_to do |format|
68 format.html {
69 flash.now[:error] = l(:label_attachment) + " " + l('activerecord.errors.messages.invalid')
70 new
71 render :action => 'new' }
72 format.api { render :status => :bad_request }
73 end
60 74 end
61 75 end
62 76 end
@@ -1,69 +1,76
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module AttachmentsHelper
21 21
22 22 def container_attachments_edit_path(container)
23 23 object_attachments_edit_path container.class.name.underscore.pluralize, container.id
24 24 end
25 25
26 26 def container_attachments_path(container)
27 27 object_attachments_path container.class.name.underscore.pluralize, container.id
28 28 end
29 29
30 30 # Displays view/delete links to the attachments of the given object
31 31 # Options:
32 32 # :author -- author names are not displayed if set to false
33 33 # :thumbails -- display thumbnails if enabled in settings
34 34 def link_to_attachments(container, options = {})
35 35 options.assert_valid_keys(:author, :thumbnails)
36 36
37 37 attachments = container.attachments.preload(:author).to_a
38 38 if attachments.any?
39 39 options = {
40 40 :editable => container.attachments_editable?,
41 41 :deletable => container.attachments_deletable?,
42 42 :author => true
43 43 }.merge(options)
44 44 render :partial => 'attachments/links',
45 45 :locals => {
46 46 :container => container,
47 47 :attachments => attachments,
48 48 :options => options,
49 49 :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)
50 50 }
51 51 end
52 52 end
53 53
54 def render_api_attachment(attachment, api)
54 def render_api_attachment(attachment, api, options={})
55 55 api.attachment do
56 api.id attachment.id
57 api.filename attachment.filename
58 api.filesize attachment.filesize
59 api.content_type attachment.content_type
60 api.description attachment.description
61 api.content_url download_named_attachment_url(attachment, attachment.filename)
62 if attachment.thumbnailable?
63 api.thumbnail_url thumbnail_url(attachment)
64 end
65 api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author
66 api.created_on attachment.created_on
56 render_api_attachment_attributes(attachment, api)
57 options.each { |key, value| eval("api.#{key} value") }
67 58 end
68 59 end
60
61 def render_api_attachment_attributes(attachment, api)
62 api.id attachment.id
63 api.filename attachment.filename
64 api.filesize attachment.filesize
65 api.content_type attachment.content_type
66 api.description attachment.description
67 api.content_url download_named_attachment_url(attachment, attachment.filename)
68 if attachment.thumbnailable?
69 api.thumbnail_url thumbnail_url(attachment)
70 end
71 if attachment.author
72 api.author(:id => attachment.author.id, :name => attachment.author.name)
73 end
74 api.created_on attachment.created_on
75 end
69 76 end
@@ -1,160 +1,165
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class Redmine::ApiTest::ApiRoutingTest < Redmine::ApiTest::Routing
21 21
22 22 def test_attachments
23 23 should_route 'GET /attachments/1' => 'attachments#show', :id => '1'
24 24 should_route 'PATCH /attachments/1' => 'attachments#update', :id => '1'
25 25 should_route 'DELETE /attachments/1' => 'attachments#destroy', :id => '1'
26 26 should_route 'POST /uploads' => 'attachments#upload'
27 27 end
28 28
29 29 def test_custom_fields
30 30 should_route 'GET /custom_fields' => 'custom_fields#index'
31 31 end
32 32
33 33 def test_enumerations
34 34 should_route 'GET /enumerations/issue_priorities' => 'enumerations#index', :type => 'issue_priorities'
35 35 end
36 36
37 def test_files
38 should_route 'GET /projects/foo/files' => 'files#index', :project_id => 'foo'
39 should_route 'POST /projects/foo/files' => 'files#create', :project_id => 'foo'
40 end
41
37 42 def test_groups
38 43 should_route 'GET /groups' => 'groups#index'
39 44 should_route 'POST /groups' => 'groups#create'
40 45
41 46 should_route 'GET /groups/1' => 'groups#show', :id => '1'
42 47 should_route 'PUT /groups/1' => 'groups#update', :id => '1'
43 48 should_route 'DELETE /groups/1' => 'groups#destroy', :id => '1'
44 49 end
45 50
46 51 def test_group_users
47 52 should_route 'POST /groups/567/users' => 'groups#add_users', :id => '567'
48 53 should_route 'DELETE /groups/567/users/12' => 'groups#remove_user', :id => '567', :user_id => '12'
49 54 end
50 55
51 56 def test_issue_categories
52 57 should_route 'GET /projects/foo/issue_categories' => 'issue_categories#index', :project_id => 'foo'
53 58 should_route 'POST /projects/foo/issue_categories' => 'issue_categories#create', :project_id => 'foo'
54 59
55 60 should_route 'GET /issue_categories/1' => 'issue_categories#show', :id => '1'
56 61 should_route 'PUT /issue_categories/1' => 'issue_categories#update', :id => '1'
57 62 should_route 'DELETE /issue_categories/1' => 'issue_categories#destroy', :id => '1'
58 63 end
59 64
60 65 def test_issue_relations
61 66 should_route 'GET /issues/1/relations' => 'issue_relations#index', :issue_id => '1'
62 67 should_route 'POST /issues/1/relations' => 'issue_relations#create', :issue_id => '1'
63 68
64 69 should_route 'GET /relations/23' => 'issue_relations#show', :id => '23'
65 70 should_route 'DELETE /relations/23' => 'issue_relations#destroy', :id => '23'
66 71 end
67 72
68 73 def test_issue_statuses
69 74 should_route 'GET /issue_statuses' => 'issue_statuses#index'
70 75 end
71 76
72 77 def test_issues
73 78 should_route 'GET /issues' => 'issues#index'
74 79 should_route 'POST /issues' => 'issues#create'
75 80
76 81 should_route 'GET /issues/64' => 'issues#show', :id => '64'
77 82 should_route 'PUT /issues/64' => 'issues#update', :id => '64'
78 83 should_route 'DELETE /issues/64' => 'issues#destroy', :id => '64'
79 84 end
80 85
81 86 def test_issue_watchers
82 87 should_route 'POST /issues/12/watchers' => 'watchers#create', :object_type => 'issue', :object_id => '12'
83 88 should_route 'DELETE /issues/12/watchers/3' => 'watchers#destroy', :object_type => 'issue', :object_id => '12', :user_id => '3'
84 89 end
85 90
86 91 def test_memberships
87 92 should_route 'GET /projects/5234/memberships' => 'members#index', :project_id => '5234'
88 93 should_route 'POST /projects/5234/memberships' => 'members#create', :project_id => '5234'
89 94
90 95 should_route 'GET /memberships/5234' => 'members#show', :id => '5234'
91 96 should_route 'PUT /memberships/5234' => 'members#update', :id => '5234'
92 97 should_route 'DELETE /memberships/5234' => 'members#destroy', :id => '5234'
93 98 end
94 99
95 100 def test_news
96 101 should_route 'GET /news' => 'news#index'
97 102 should_route 'GET /projects/567/news' => 'news#index', :project_id => '567'
98 103 end
99 104
100 105 def test_projects
101 106 should_route 'GET /projects' => 'projects#index'
102 107 should_route 'POST /projects' => 'projects#create'
103 108
104 109 should_route 'GET /projects/1' => 'projects#show', :id => '1'
105 110 should_route 'PUT /projects/1' => 'projects#update', :id => '1'
106 111 should_route 'DELETE /projects/1' => 'projects#destroy', :id => '1'
107 112 end
108 113
109 114 def test_queries
110 115 should_route 'GET /queries' => 'queries#index'
111 116 end
112 117
113 118 def test_roles
114 119 should_route 'GET /roles' => 'roles#index'
115 120 should_route 'GET /roles/2' => 'roles#show', :id => '2'
116 121 end
117 122
118 123 def test_time_entries
119 124 should_route 'GET /time_entries' => 'timelog#index'
120 125 should_route 'POST /time_entries' => 'timelog#create'
121 126
122 127 should_route 'GET /time_entries/1' => 'timelog#show', :id => '1'
123 128 should_route 'PUT /time_entries/1' => 'timelog#update', :id => '1'
124 129 should_route 'DELETE /time_entries/1' => 'timelog#destroy', :id => '1'
125 130 end
126 131
127 132 def test_trackers
128 133 should_route 'GET /trackers' => 'trackers#index'
129 134 end
130 135
131 136 def test_users
132 137 should_route 'GET /users' => 'users#index'
133 138 should_route 'POST /users' => 'users#create'
134 139
135 140 should_route 'GET /users/44' => 'users#show', :id => '44'
136 141 should_route 'GET /users/current' => 'users#show', :id => 'current'
137 142 should_route 'PUT /users/44' => 'users#update', :id => '44'
138 143 should_route 'DELETE /users/44' => 'users#destroy', :id => '44'
139 144 end
140 145
141 146 def test_versions
142 147 should_route 'GET /projects/foo/versions' => 'versions#index', :project_id => 'foo'
143 148 should_route 'POST /projects/foo/versions' => 'versions#create', :project_id => 'foo'
144 149
145 150 should_route 'GET /versions/1' => 'versions#show', :id => '1'
146 151 should_route 'PUT /versions/1' => 'versions#update', :id => '1'
147 152 should_route 'DELETE /versions/1' => 'versions#destroy', :id => '1'
148 153 end
149 154
150 155 def test_wiki
151 156 should_route 'GET /projects/567/wiki/index' => 'wiki#index', :project_id => '567'
152 157
153 158 should_route 'GET /projects/567/wiki/my_page' => 'wiki#show', :project_id => '567', :id => 'my_page'
154 159 should_route 'GET /projects/567/wiki/my_page' => 'wiki#show', :project_id => '567', :id => 'my_page'
155 160 should_route 'GET /projects/1/wiki/my_page/2' => 'wiki#show', :project_id => '1', :id => 'my_page', :version => '2'
156 161
157 162 should_route 'PUT /projects/567/wiki/my_page' => 'wiki#update', :project_id => '567', :id => 'my_page'
158 163 should_route 'DELETE /projects/567/wiki/my_page' => 'wiki#destroy', :project_id => '567', :id => 'my_page'
159 164 end
160 165 end
General Comments 0
You need to be logged in to leave comments. Login now