helios 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +50 -21
  3. data/README.md +27 -2
  4. data/helios.gemspec +10 -6
  5. data/lib/helios.rb +10 -4
  6. data/lib/helios/backend.rb +29 -22
  7. data/lib/helios/backend/data.rb +10 -5
  8. data/lib/helios/backend/in-app-purchase.rb +4 -2
  9. data/lib/helios/backend/newsstand.rb +97 -0
  10. data/lib/helios/backend/passbook.rb +8 -2
  11. data/lib/helios/backend/push-notification.rb +25 -12
  12. data/lib/helios/commands/console.rb +4 -10
  13. data/lib/helios/commands/new.rb +0 -1
  14. data/lib/helios/commands/server.rb +4 -1
  15. data/lib/helios/frontend.rb +6 -14
  16. data/lib/helios/frontend/images/helios.svg +33 -0
  17. data/lib/helios/frontend/javascripts/helios.coffee +39 -3
  18. data/lib/helios/frontend/javascripts/helios/collections.coffee +10 -2
  19. data/lib/helios/frontend/javascripts/helios/models.coffee +4 -1
  20. data/lib/helios/frontend/javascripts/helios/router.coffee +13 -1
  21. data/lib/helios/frontend/javascripts/helios/views.coffee +82 -8
  22. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload-ui.js +807 -0
  23. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload.js +1201 -0
  24. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.ui.widget.js +530 -0
  25. data/lib/helios/frontend/javascripts/vendor/linkheaders.js +117 -0
  26. data/lib/helios/frontend/stylesheets/_iphone.sass +1 -1
  27. data/lib/helios/frontend/stylesheets/screen.sass +2 -6
  28. data/lib/helios/frontend/templates/{entities.jst.tpl → data/entities.jst.tpl} +0 -0
  29. data/lib/helios/frontend/templates/{receipts.jst.tpl → in-app-purchase/receipts.jst.tpl} +0 -0
  30. data/lib/helios/frontend/templates/navigation.jst.tpl +31 -0
  31. data/lib/helios/frontend/templates/newsstand/issues.jst.tpl +16 -0
  32. data/lib/helios/frontend/templates/newsstand/new.jst.tpl +28 -0
  33. data/lib/helios/frontend/templates/{passes.jst.tpl → passbook/passes.jst.tpl} +0 -0
  34. data/lib/helios/frontend/templates/{compose.jst.tpl → push-notification/compose.jst.tpl} +0 -0
  35. data/lib/helios/frontend/templates/{devices.jst.tpl → push-notification/devices.jst.tpl} +0 -0
  36. data/lib/helios/frontend/views/index.haml +4 -28
  37. data/lib/helios/templates/.env.erb +1 -0
  38. data/lib/helios/templates/.gitignore +3 -0
  39. data/lib/helios/version.rb +1 -1
  40. metadata +86 -20
  41. data/lib/helios/frontend/stylesheets/_bariol.scss +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 402e29639eacaa1ba54ba2f4eccefb1ac228be91
4
- data.tar.gz: c2f8542dd3218c7a2b97c75aeea6a0cec5c57f76
3
+ metadata.gz: 804589d0f2e0ada259f0b9fed0746db10843ef8c
4
+ data.tar.gz: 05599e5a5f8d21b616dd6ae7e0cfdfd2cf33e34b
5
5
  SHA512:
6
- metadata.gz: 4bfd61221daf7968ccba1a273930dfc0787610ce175e2d2cc0fab8e30fa7828b27c30810d8acf0597e9f5d09239b9ca6d974ad46c3017eb76b758bc214bae877
7
- data.tar.gz: 2f8bb05d75f2bef9f6b4fb1f308aa7741bba846a6292aeb6be94b2c58e60d9e1199d919dc8afc838d68e179af1ad39795d60ea370c3daf3bd09376e68750a8d5
6
+ metadata.gz: 12d3ceb7bf25ea1040f3ecb0faffbe2dd433a4733c407cb4f86ccb43db471d623759a678089f10f4f45740ec1dc1d9f3e90b1b07dd393aaae684da4944632053
7
+ data.tar.gz: 58e382a519b93419ee86cb8a195c80fed29693bbe2ae0f588355dab9e7f22629be19b6fb1f870b45ca711fc72a7355900951a59b593e357aefb1967084ae976f
data/Gemfile.lock CHANGED
@@ -1,22 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- helios (0.2.1)
4
+ helios (0.2.2)
5
5
  coffee-script (~> 2.2)
6
6
  commander (~> 4.1)
7
7
  compass (~> 0.12)
8
+ core_data
9
+ fog (~> 1.10)
8
10
  foreman (~> 0.63)
9
11
  haml (>= 3.1)
12
+ houston (~> 0.2)
10
13
  json (~> 1.7)
11
14
  rack-contrib (~> 1.1)
12
- rack-core-data (~> 0.3)
13
15
  rack-in-app-purchase (~> 0.1)
16
+ rack-newsstand (~> 0.1)
14
17
  rack-passbook (~> 0.1)
15
18
  rack-push-notification (~> 0.4)
19
+ rack-scaffold
16
20
  rails-database-url (~> 1.0)
17
21
  sinatra (~> 1.3)
18
- sinatra-assetpack (~> 0.1.7)
19
- sinatra-backbone (~> 0.1.0.rc2)
22
+ sinatra-assetpack (~> 0.2.2)
23
+ sinatra-backbone (~> 0.1.1)
20
24
  sinatra-contrib (~> 1.3)
21
25
  sinatra-param (~> 0.1)
22
26
  sinatra-support (~> 1.2)
@@ -29,6 +33,7 @@ GEM
29
33
  i18n (= 0.6.1)
30
34
  multi_json (~> 1.0)
31
35
  backports (3.3.0)
36
+ builder (3.2.0)
32
37
  chunky_png (1.2.8)
33
38
  coffee-script (2.2.0)
34
39
  coffee-script-source
@@ -40,55 +45,77 @@ GEM
40
45
  chunky_png (~> 1.2)
41
46
  fssm (>= 0.2.7)
42
47
  sass (~> 3.1)
48
+ core_data (0.1.1)
49
+ nokogiri (~> 1.4)
43
50
  diff-lcs (1.2.4)
44
51
  dotenv (0.7.0)
45
52
  eventmachine (1.0.3)
53
+ excon (0.21.0)
46
54
  execjs (1.4.0)
47
55
  multi_json (~> 1.0)
56
+ fog (1.11.1)
57
+ builder
58
+ excon (~> 0.20)
59
+ formatador (~> 0.2.0)
60
+ json (~> 1.7)
61
+ mime-types
62
+ net-scp (~> 1.1)
63
+ net-ssh (>= 2.1.3)
64
+ nokogiri (~> 1.5.0)
65
+ ruby-hmac
48
66
  foreman (0.63.0)
49
67
  dotenv (>= 0.7)
50
68
  thor (>= 0.13.6)
69
+ formatador (0.2.4)
51
70
  fssm (0.2.10)
52
71
  haml (4.0.2)
53
72
  tilt
54
73
  highline (1.6.18)
55
- houston (0.1.1)
56
- commander (~> 4.1.2)
57
- json (~> 1.7.3)
74
+ houston (0.2.2)
75
+ commander (~> 4.1)
76
+ json
58
77
  i18n (0.6.1)
59
78
  jsmin (1.0.1)
60
79
  json (1.7.7)
61
- multi_json (1.7.2)
80
+ mime-types (1.23)
81
+ multi_json (1.7.3)
82
+ net-scp (1.1.0)
83
+ net-ssh (>= 2.6.5)
84
+ net-ssh (2.6.7)
62
85
  nokogiri (1.5.9)
86
+ plist (3.1.0)
63
87
  rack (1.5.2)
64
88
  rack-contrib (1.1.0)
65
89
  rack (>= 0.9.1)
66
- rack-core-data (0.3.1)
67
- activesupport (>= 3.0)
68
- nokogiri (~> 1.4)
90
+ rack-in-app-purchase (0.1.0)
69
91
  rack (~> 1.4)
70
- rack-contrib (~> 1.1)
71
92
  sequel (~> 3.37)
72
93
  sinatra (~> 1.3)
73
- sinatra-param (~> 0.1)
74
- rack-in-app-purchase (0.1.0)
94
+ venice
95
+ rack-newsstand (0.1.1)
96
+ builder (>= 3.0)
97
+ plist (~> 3.1)
75
98
  rack (~> 1.4)
76
99
  sequel (~> 3.37)
77
100
  sinatra (~> 1.3)
78
- venice
79
101
  rack-passbook (0.1.1)
80
102
  rack (~> 1.4)
81
103
  sequel (~> 3.37)
82
104
  sinatra (~> 1.3)
83
105
  rack-protection (1.5.0)
84
106
  rack
85
- rack-push-notification (0.4.0)
86
- houston (~> 0.1.1)
107
+ rack-push-notification (0.4.1)
87
108
  rack (~> 1.4)
88
109
  rack-contrib (~> 1.1)
89
110
  sequel (~> 3.37)
90
111
  sinatra (~> 1.3)
91
112
  sinatra-param (~> 0.1)
113
+ rack-scaffold (0.0.1)
114
+ activesupport (>= 3.0)
115
+ rack (~> 1.4)
116
+ rack-contrib (~> 1.1)
117
+ sinatra (~> 1.3)
118
+ sinatra-param (~> 0.1)
92
119
  rack-test (0.6.2)
93
120
  rack (>= 1.0)
94
121
  rails-database-url (1.0.0)
@@ -101,13 +128,15 @@ GEM
101
128
  rspec-expectations (2.13.0)
102
129
  diff-lcs (>= 1.1.3, < 2.0)
103
130
  rspec-mocks (2.13.1)
104
- sass (3.2.8)
105
- sequel (3.46.0)
131
+ ruby-hmac (0.4.0)
132
+ sass (3.2.9)
133
+ sequel (3.47.0)
106
134
  sinatra (1.4.2)
107
135
  rack (~> 1.5, >= 1.5.2)
108
136
  rack-protection (~> 1.4)
109
137
  tilt (~> 1.3, >= 1.3.4)
110
- sinatra-assetpack (0.1.7)
138
+ sinatra-assetpack (0.2.2)
139
+ backports
111
140
  jsmin
112
141
  rack-test
113
142
  sinatra
@@ -127,7 +156,7 @@ GEM
127
156
  sinatra (>= 1.0)
128
157
  terminal-table (1.4.5)
129
158
  thor (0.18.1)
130
- tilt (1.3.7)
159
+ tilt (1.4.1)
131
160
  venice (0.1.0)
132
161
  commander (~> 4.1)
133
162
  json
data/README.md CHANGED
@@ -76,7 +76,7 @@ Bundler.require
76
76
 
77
77
  run Helios::Application.new do
78
78
  service :data, model: 'path/to/DataModel.xcdatamodel'
79
- service :push_notification
79
+ service :push_notification, apn_certificate: 'path/to/apple_push_notification.pem', apn_environment: 'development'
80
80
  service :in_app_purchase
81
81
  service :passbook
82
82
  end
@@ -102,7 +102,7 @@ Helios can be run as Rails middleware by adding this to the configuration block
102
102
  ```ruby
103
103
  config.middleware.use Helios::Application do
104
104
  service :data, model: 'path/to/DataModel.xcdatamodel'
105
- service :push_notification
105
+ service :push_notification, apn_certificate: 'path/to/apple_push_notification.pem', apn_environment: 'development'
106
106
  service :in_app_purchase
107
107
  service :passbook
108
108
  end
@@ -164,6 +164,10 @@ Each entity in the specified data model will have a `Sequel::Model` subclass cre
164
164
  <td><tt>DELETE /devices/:token</tt></td>
165
165
  <td>Unregister a device from receiving push notifications</td>
166
166
  </tr>
167
+ <tr>
168
+ <td><tt>POST /message</tt></td>
169
+ <td>Send out a push notification to some devices</td>
170
+ </tr>
167
171
  </table>
168
172
 
169
173
  ---
@@ -266,6 +270,12 @@ To run Helios in development mode on `localhost`, run the `server` command:
266
270
 
267
271
  $ helios server
268
272
 
273
+ ### Testing Push Notifications
274
+
275
+ Once you have registered a device and set up your certificate, try this:
276
+
277
+ $ curl -X POST -d 'payload={"aps": {"alert":"Blastoff!"}}' http://localhost:5000/message
278
+
269
279
  ### Running the Helios Console
270
280
 
271
281
  You can start an IRB session with the runtime environment of the Helios application with the `console` command:
@@ -311,6 +321,21 @@ didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
311
321
  }
312
322
  ```
313
323
 
324
+ ### Converting Your Push Notification Certificate
325
+
326
+ > These instructions come from the [APN on Rails](https://github.com/PRX/apn_on_rails) project.
327
+
328
+ Once you have the certificate from Apple for your application, export your key
329
+ and the apple certificate as p12 files. Here is a quick walkthrough on how to do this:
330
+
331
+ 1. Click the disclosure arrow next to your certificate in Keychain Access and select the certificate and the key.
332
+ 2. Right click and choose `Export 2 items…`.
333
+ 3. Choose the p12 format from the drop down and name it `cert.p12`.
334
+
335
+ Now covert the p12 file to a pem file:
336
+
337
+ $ openssl pkcs12 -in cert.p12 -out apple_push_notification.pem -nodes -clcerts
338
+
314
339
  ## Coming Attractions
315
340
 
316
341
  There's still a lot to do to make Helios even better. Here are some ideas that are at the top of the list:
data/helios.gemspec CHANGED
@@ -16,27 +16,31 @@ Gem::Specification.new do |s|
16
16
  s.add_dependency "commander", "~> 4.1"
17
17
  s.add_dependency "foreman", "~> 0.63"
18
18
  s.add_dependency "rack-contrib", "~> 1.1"
19
- s.add_dependency "rack-core-data", "~> 0.3"
20
19
  s.add_dependency "rack-push-notification", "~> 0.4"
21
20
  s.add_dependency "rack-in-app-purchase", "~> 0.1"
22
21
  s.add_dependency "rack-passbook", "~> 0.1"
22
+ s.add_dependency "rack-newsstand", "~> 0.1"
23
+ s.add_dependency "rack-scaffold"
24
+ s.add_dependency "core_data"
23
25
  s.add_dependency "json", "~> 1.7"
24
26
  s.add_dependency "coffee-script", "~> 2.2"
25
27
  s.add_dependency "sinatra", "~> 1.3"
26
28
  s.add_dependency "sinatra-contrib", "~> 1.3"
27
- s.add_dependency "sinatra-assetpack", "~> 0.1.7"
28
- s.add_dependency "sinatra-backbone", "~> 0.1.0.rc2"
29
+ s.add_dependency "sinatra-assetpack", "~> 0.2.2"
30
+ s.add_dependency "sinatra-backbone", "~> 0.1.1"
29
31
  s.add_dependency "sinatra-param", "~> 0.1"
30
32
  s.add_dependency "sinatra-support", "~> 1.2"
31
33
  s.add_dependency "haml", ">= 3.1"
32
34
  s.add_dependency "compass", "~> 0.12"
33
35
  s.add_dependency "zurb-foundation", "4.1.2"
34
36
  s.add_dependency "rails-database-url", "~> 1.0"
37
+ s.add_dependency "fog", "~> 1.10"
38
+ s.add_dependency "houston", "~> 0.2"
35
39
 
36
- s.add_development_dependency "rake", ">= 0"
37
- s.add_development_dependency "rspec", ">= 0"
40
+ s.add_development_dependency "rake"
41
+ s.add_development_dependency "rspec"
38
42
 
39
- s.files = Dir["./**/*"].reject{|file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/} + Dir.glob("./lib/helios/templates", File::FNM_DOTMATCH)
43
+ s.files = Dir["./**/*"].reject{|file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/} + Dir.glob("./lib/helios/templates/*", File::FNM_DOTMATCH)
40
44
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
41
45
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
42
46
  s.require_paths = ["lib"]
data/lib/helios.rb CHANGED
@@ -3,11 +3,17 @@ require 'rack'
3
3
  module Helios
4
4
  class Application
5
5
  def initialize(app = nil, options = {}, &block)
6
- map = {}
7
- map['/'] = Rack::Cascade.new([app, Helios::Backend.new(&block)].compact)
8
- map['/admin'] = Helios::Frontend.new if options.fetch(:frontend, true)
6
+ @app = Rack::Builder.new do
7
+ map '/admin' do
8
+ use Rack::Auth::Basic, "Restricted Area" do |username, password|
9
+ username == (ENV['HELIOS_ADMIN_USERNAME'] || "") and password == (ENV['HELIOS_ADMIN_PASSWORD'] || "")
10
+ end if ENV['HELIOS_ADMIN_USERNAME'] or ENV['HELIOS_ADMIN_PASSWORD']
9
11
 
10
- @app = Rack::URLMap.new(map)
12
+ run Helios::Frontend.new
13
+ end
14
+
15
+ run Helios::Backend.new(&block)
16
+ end
11
17
  end
12
18
 
13
19
  def call(env)
@@ -1,22 +1,31 @@
1
1
  require 'rack'
2
2
 
3
3
  module Helios
4
- class Backend < Rack::Cascade
4
+ class Backend < Rack::Builder
5
+ DEFAULT_PATHS = {
6
+ data: '/'
7
+ }
8
+
5
9
  require 'rails-database-url' if const_defined?(:Rails)
6
10
 
7
- def initialize(&block)
8
- @services = []
11
+ def initialize(*args, &block)
12
+ raise ArgumentError, "Missing block" unless block_given?
13
+ super(&nil)
9
14
 
10
- block = lambda { |app|
11
- service :data, model: Dir['**/*.xcdatamodeld'].first rescue false
12
- service :push_notification
13
- service :in_app_purchase
14
- service :passbook
15
- } unless block_given?
15
+ @services = {}
16
16
 
17
17
  instance_eval(&block)
18
+ end
18
19
 
19
- super(@services)
20
+ def call(env)
21
+ return super(env) unless env["REQUEST_METHOD"] == "OPTIONS" and env["REQUEST_PATH"] == "/"
22
+
23
+ links = []
24
+ @services.each do |path, middleware|
25
+ links << %{<#{path}>; rel="#{middleware}"}
26
+ end
27
+
28
+ [206, {"Link" => links.join("\n")}, []]
20
29
  end
21
30
 
22
31
  private
@@ -28,30 +37,28 @@ module Helios
28
37
  begin
29
38
  middleware = Helios::Backend.const_get(constantize(identifier))
30
39
  rescue NameError
31
- raise LoadError, "Could not find matching service for #{identifier.inspect}. You may need to install an additional gem (such as helios-#{identifier})."
40
+ raise LoadError, "Could not find matching service for #{identifier.inspect} (Helios::Backend::#{constantize(identifier)}). You may need to install an additional gem (such as helios-#{identifier})."
32
41
  end
33
42
  end
34
43
 
35
- middleware.instance_eval{ include Helios::Administerable } if options.fetch(:frontend, true)
44
+ path = "/#{(options.delete(:root) || DEFAULT_PATHS[identifier] || identifier)}".squeeze("/")
45
+
46
+ map path do
47
+ instance_eval(&block) if block_given?
48
+ run middleware.new(self, options)
49
+ end
36
50
 
37
- @services << middleware.new(self, options, &block) if middleware
51
+ @services[path] = middleware
38
52
  end
39
53
 
40
54
  def constantize(identifier)
41
55
  identifier.to_s.split(/([[:alpha:]]*)/).select{|c| /[[:alpha:]]/ === c}.map(&:capitalize).join("")
42
56
  end
43
57
  end
44
-
45
- module Administerable
46
- attr_accessor :admin
47
-
48
- def admin?
49
- !!@admin
50
- end
51
- end
52
58
  end
53
59
 
54
60
  require 'helios/backend/data'
55
- require 'helios/backend/push-notification'
56
61
  require 'helios/backend/in-app-purchase'
57
62
  require 'helios/backend/passbook'
63
+ require 'helios/backend/push-notification'
64
+ require 'helios/backend/newsstand'
@@ -1,21 +1,26 @@
1
- require 'rack/core-data'
1
+ require 'core_data'
2
+ require 'sequel'
3
+
4
+ require 'rack/scaffold'
5
+
6
+ require 'sinatra/base'
2
7
  require 'sinatra/param'
3
8
 
9
+
4
10
  class Helios::Backend::Data < Sinatra::Base
5
11
  helpers Sinatra::Param
6
12
 
7
13
  def initialize(app, options = {})
8
- super(Rack::CoreData(options[:model]))
14
+ super(Rack::Scaffold.new(options))
9
15
 
10
- @model = Rack::CoreData::DataModel.new(options[:model])
16
+ @model = CoreData::DataModel.new(options[:model]) rescue nil
11
17
  end
12
18
 
13
19
  before do
14
20
  content_type :json
15
21
  end
16
22
 
17
- options '/' do
18
- pass unless self.class < Helios::Administerable
23
+ options '/resources' do
19
24
 
20
25
  links = []
21
26
  @model.entities.each do |entity|
@@ -1,13 +1,15 @@
1
1
  require 'rack/in-app-purchase'
2
+
3
+ require 'sinatra/base'
2
4
  require 'sinatra/param'
3
5
 
4
6
  class Helios::Backend::InAppPurchase < Sinatra::Base
5
7
  helpers Sinatra::Param
6
8
 
7
- def initialize(app, options = {})
9
+ def initialize(app, options = {}, &block)
8
10
  super(Rack::InAppPurchase.new)
9
11
  end
10
-
12
+
11
13
  before do
12
14
  content_type :json
13
15
  end
@@ -0,0 +1,97 @@
1
+ require 'rack/newsstand'
2
+
3
+ require 'sinatra/base'
4
+ require 'sinatra/param'
5
+
6
+ require 'fog'
7
+
8
+ class Helios::Backend::Newsstand < Sinatra::Base
9
+ helpers Sinatra::Param
10
+
11
+ def initialize(app, options = {}, &block)
12
+ super(Rack::Newsstand.new)
13
+
14
+ @storage = Fog::Storage.new(options[:storage]) if options[:storage]
15
+ end
16
+
17
+ before do
18
+ content_type :json
19
+ end
20
+
21
+ get '/issues/?' do
22
+ pass unless request.accept? 'application/json'
23
+
24
+ param :q, String
25
+
26
+ issues = Rack::Newsstand::Issue.dataset
27
+ issues = issues.filter("tsv @@ to_tsquery('english', ?)", "#{params[:q]}:*") if params[:q] and not params[:q].empty?
28
+
29
+ if params[:page] or params[:per_page]
30
+ param :page, Integer, default: 1, min: 1
31
+ param :per_page, Integer, default: 100, in: (1..100)
32
+
33
+ {
34
+ issues: issues.limit(params[:per_page], (params[:page] - 1) * params[:per_page]).naked.all,
35
+ page: params[:page],
36
+ total: issues.count
37
+ }.to_json
38
+ else
39
+ param :limit, Integer, default: 100, in: (1..100)
40
+ param :offset, Integer, default: 0, min: 0
41
+
42
+ {
43
+ issues: issues.limit(params[:limit], params[:offset]).naked.all
44
+ }.to_json
45
+ end
46
+ end
47
+
48
+ head '/issues/new?' do
49
+ status 503 and return unless @storage
50
+
51
+ status 204
52
+ end
53
+
54
+ post '/issues/?' do
55
+ status 503 and return unless @storage
56
+
57
+ param :name, String, empty: false
58
+ param :summary, String
59
+
60
+ issue = Rack::Newsstand::Issue.new(params)
61
+
62
+ if issue.valid?
63
+ directory = @storage.directories.create(key: "newsstand-issue-#{issue.name}-#{Time.now.to_i}", public: true)
64
+
65
+ covers, assets = {}, []
66
+ [:covers, :assets].each do |attribute|
67
+ (params[attribute] || []).each do |f|
68
+ file = directory.files.create(
69
+ key: File.basename(f[:filename]),
70
+ body: File.open(f[:tempfile]),
71
+ public: true
72
+ )
73
+
74
+ case attribute
75
+ when :covers
76
+ covers["SOURCE"] = file.public_url
77
+ when :assets
78
+ assets << file.public_url
79
+ end
80
+ end
81
+ end
82
+
83
+ issue.set(cover_urls: covers, asset_urls: assets)
84
+
85
+ if issue.save
86
+ status 201
87
+ issue.to_json
88
+ else
89
+ status 400
90
+ {errors: issue.errors}.to_json
91
+ end
92
+ else
93
+ status 400
94
+ {errors: issue.errors}.to_json
95
+ end
96
+ end
97
+ end