mobile_workflow_cli 0.1.3 → 0.1.4

This diff has not been reviewed by any users.
Log in in order to be able to vote.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26d89f9b3fbeb44e3217e8df7ae498dccf67153683c9ae28feace983ba68531d
4
- data.tar.gz: c5527c6ef4c1eb74a2f6a85cdb8fabd54133afa9203342005e0ba2715e4404dc
3
+ metadata.gz: fe2845048e277dc7fb21d3c93e50fd8dadbe7bbde5461a832217dfb704df5a34
4
+ data.tar.gz: 626ada7f57b9ba66807908c9374c4ce123cf311f0abd389a7211e6d0c6174849
5
5
  SHA512:
6
- metadata.gz: c31f5c0633b8c690f60d11b99f89faa4eb0d53c80a2c197888eafdf0a4d57307f2f6b75028d92aa4ec293fe3aaddc2c05b9ef958849cf4bb54f72e5bef996c0e
7
- data.tar.gz: bd4245fdd9e1f4f85a16735cb9dc1ea8ad1cbdb4c89522ff66518af82ab4a972fc674b2b68fabeaf42e02ff44a0b6a56e32734a838ef282f7d1d01bd9bb8789c
6
+ metadata.gz: 9610ea1cc8e3d868912b3a53632900b8588d7008534255679d0e8821f82672a6eb8287cfa06a7a1cc66d8d5d42b0714b5475e7dc38c4a697e3cd1420c1fdd127
7
+ data.tar.gz: 78cb840acb378734538c2aac586bdd219d3487c37a9a9a4ae977469139d0852a5295423117335be2030b262adf4e592004719406e3e4df4e99efaf4d179b4755
@@ -1,63 +1,63 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mobile_workflow_cli (0.1.3)
4
+ mobile_workflow_cli (0.1.4)
5
5
  administrate
6
6
  rails (>= 6.0.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actioncable (6.0.3)
12
- actionpack (= 6.0.3)
11
+ actioncable (6.0.3.1)
12
+ actionpack (= 6.0.3.1)
13
13
  nio4r (~> 2.0)
14
14
  websocket-driver (>= 0.6.1)
15
- actionmailbox (6.0.3)
16
- actionpack (= 6.0.3)
17
- activejob (= 6.0.3)
18
- activerecord (= 6.0.3)
19
- activestorage (= 6.0.3)
20
- activesupport (= 6.0.3)
15
+ actionmailbox (6.0.3.1)
16
+ actionpack (= 6.0.3.1)
17
+ activejob (= 6.0.3.1)
18
+ activerecord (= 6.0.3.1)
19
+ activestorage (= 6.0.3.1)
20
+ activesupport (= 6.0.3.1)
21
21
  mail (>= 2.7.1)
22
- actionmailer (6.0.3)
23
- actionpack (= 6.0.3)
24
- actionview (= 6.0.3)
25
- activejob (= 6.0.3)
22
+ actionmailer (6.0.3.1)
23
+ actionpack (= 6.0.3.1)
24
+ actionview (= 6.0.3.1)
25
+ activejob (= 6.0.3.1)
26
26
  mail (~> 2.5, >= 2.5.4)
27
27
  rails-dom-testing (~> 2.0)
28
- actionpack (6.0.3)
29
- actionview (= 6.0.3)
30
- activesupport (= 6.0.3)
28
+ actionpack (6.0.3.1)
29
+ actionview (= 6.0.3.1)
30
+ activesupport (= 6.0.3.1)
31
31
  rack (~> 2.0, >= 2.0.8)
32
32
  rack-test (>= 0.6.3)
33
33
  rails-dom-testing (~> 2.0)
34
34
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
35
- actiontext (6.0.3)
36
- actionpack (= 6.0.3)
37
- activerecord (= 6.0.3)
38
- activestorage (= 6.0.3)
39
- activesupport (= 6.0.3)
35
+ actiontext (6.0.3.1)
36
+ actionpack (= 6.0.3.1)
37
+ activerecord (= 6.0.3.1)
38
+ activestorage (= 6.0.3.1)
39
+ activesupport (= 6.0.3.1)
40
40
  nokogiri (>= 1.8.5)
41
- actionview (6.0.3)
42
- activesupport (= 6.0.3)
41
+ actionview (6.0.3.1)
42
+ activesupport (= 6.0.3.1)
43
43
  builder (~> 3.1)
44
44
  erubi (~> 1.4)
45
45
  rails-dom-testing (~> 2.0)
46
46
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
47
- activejob (6.0.3)
48
- activesupport (= 6.0.3)
47
+ activejob (6.0.3.1)
48
+ activesupport (= 6.0.3.1)
49
49
  globalid (>= 0.3.6)
50
- activemodel (6.0.3)
51
- activesupport (= 6.0.3)
52
- activerecord (6.0.3)
53
- activemodel (= 6.0.3)
54
- activesupport (= 6.0.3)
55
- activestorage (6.0.3)
56
- actionpack (= 6.0.3)
57
- activejob (= 6.0.3)
58
- activerecord (= 6.0.3)
50
+ activemodel (6.0.3.1)
51
+ activesupport (= 6.0.3.1)
52
+ activerecord (6.0.3.1)
53
+ activemodel (= 6.0.3.1)
54
+ activesupport (= 6.0.3.1)
55
+ activestorage (6.0.3.1)
56
+ actionpack (= 6.0.3.1)
57
+ activejob (= 6.0.3.1)
58
+ activerecord (= 6.0.3.1)
59
59
  marcel (~> 0.3.1)
60
- activesupport (6.0.3)
60
+ activesupport (6.0.3.1)
61
61
  concurrent-ruby (~> 1.0, >= 1.0.2)
62
62
  i18n (>= 0.7, < 2)
63
63
  minitest (~> 5.1)
@@ -85,27 +85,27 @@ GEM
85
85
  diff-lcs (1.3)
86
86
  erubi (1.9.0)
87
87
  execjs (2.7.0)
88
- ffi (1.12.2)
88
+ ffi (1.13.1)
89
89
  globalid (0.4.2)
90
90
  activesupport (>= 4.2.0)
91
- i18n (1.8.2)
91
+ i18n (1.8.3)
92
92
  concurrent-ruby (~> 1.0)
93
93
  jquery-rails (4.4.0)
94
94
  rails-dom-testing (>= 1, < 3)
95
95
  railties (>= 4.2.0)
96
96
  thor (>= 0.14, < 2.0)
97
- kaminari (1.2.0)
97
+ kaminari (1.2.1)
98
98
  activesupport (>= 4.1.0)
99
- kaminari-actionview (= 1.2.0)
100
- kaminari-activerecord (= 1.2.0)
101
- kaminari-core (= 1.2.0)
102
- kaminari-actionview (1.2.0)
99
+ kaminari-actionview (= 1.2.1)
100
+ kaminari-activerecord (= 1.2.1)
101
+ kaminari-core (= 1.2.1)
102
+ kaminari-actionview (1.2.1)
103
103
  actionview
104
- kaminari-core (= 1.2.0)
105
- kaminari-activerecord (1.2.0)
104
+ kaminari-core (= 1.2.1)
105
+ kaminari-activerecord (1.2.1)
106
106
  activerecord
107
- kaminari-core (= 1.2.0)
108
- kaminari-core (1.2.0)
107
+ kaminari-core (= 1.2.1)
108
+ kaminari-core (1.2.1)
109
109
  loofah (2.5.0)
110
110
  crass (~> 1.0.2)
111
111
  nokogiri (>= 1.5.9)
@@ -126,29 +126,29 @@ GEM
126
126
  rack (2.2.2)
127
127
  rack-test (1.1.0)
128
128
  rack (>= 1.0, < 3)
129
- rails (6.0.3)
130
- actioncable (= 6.0.3)
131
- actionmailbox (= 6.0.3)
132
- actionmailer (= 6.0.3)
133
- actionpack (= 6.0.3)
134
- actiontext (= 6.0.3)
135
- actionview (= 6.0.3)
136
- activejob (= 6.0.3)
137
- activemodel (= 6.0.3)
138
- activerecord (= 6.0.3)
139
- activestorage (= 6.0.3)
140
- activesupport (= 6.0.3)
129
+ rails (6.0.3.1)
130
+ actioncable (= 6.0.3.1)
131
+ actionmailbox (= 6.0.3.1)
132
+ actionmailer (= 6.0.3.1)
133
+ actionpack (= 6.0.3.1)
134
+ actiontext (= 6.0.3.1)
135
+ actionview (= 6.0.3.1)
136
+ activejob (= 6.0.3.1)
137
+ activemodel (= 6.0.3.1)
138
+ activerecord (= 6.0.3.1)
139
+ activestorage (= 6.0.3.1)
140
+ activesupport (= 6.0.3.1)
141
141
  bundler (>= 1.3.0)
142
- railties (= 6.0.3)
142
+ railties (= 6.0.3.1)
143
143
  sprockets-rails (>= 2.0.0)
144
144
  rails-dom-testing (2.0.3)
145
145
  activesupport (>= 4.2.0)
146
146
  nokogiri (>= 1.6)
147
147
  rails-html-sanitizer (1.3.0)
148
148
  loofah (~> 2.3)
149
- railties (6.0.3)
150
- actionpack (= 6.0.3)
151
- activesupport (= 6.0.3)
149
+ railties (6.0.3.1)
150
+ actionpack (= 6.0.3.1)
151
+ activesupport (= 6.0.3.1)
152
152
  method_source
153
153
  rake (>= 0.8.7)
154
154
  thor (>= 0.20.3, < 2.0)
@@ -166,7 +166,7 @@ GEM
166
166
  diff-lcs (>= 1.2.0, < 2.0)
167
167
  rspec-support (~> 3.9.0)
168
168
  rspec-support (3.9.3)
169
- sassc (2.3.0)
169
+ sassc (2.4.0)
170
170
  ffi (~> 1.9)
171
171
  sassc-rails (2.1.2)
172
172
  railties (>= 4.0.0)
@@ -175,7 +175,7 @@ GEM
175
175
  sprockets-rails
176
176
  tilt
177
177
  selectize-rails (0.12.6)
178
- sprockets (4.0.0)
178
+ sprockets (4.0.2)
179
179
  concurrent-ruby (~> 1.0)
180
180
  rack (> 1, < 3)
181
181
  sprockets-rails (3.2.1)
@@ -187,9 +187,9 @@ GEM
187
187
  tilt (2.0.10)
188
188
  tzinfo (1.2.7)
189
189
  thread_safe (~> 0.1)
190
- websocket-driver (0.7.1)
190
+ websocket-driver (0.7.2)
191
191
  websocket-extensions (>= 0.1.0)
192
- websocket-extensions (0.1.4)
192
+ websocket-extensions (0.1.5)
193
193
  zeitwerk (2.3.0)
194
194
 
195
195
  PLATFORMS
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+ require 'byebug'
4
+
5
+ source_path = (Pathname.new(__FILE__).dirname + '../lib').expand_path
6
+ $LOAD_PATH << source_path
7
+
8
+ require 'mobile_workflow_cli'
9
+
10
+ if ARGV.empty?
11
+ puts "USAGE: mwf-delete <directory>"
12
+ puts "See --help for more info"
13
+ exit 0
14
+ elsif ['-v', '--version'].include? ARGV[0]
15
+ puts MobileWorkflowCli::VERSION
16
+ exit 0
17
+ end
18
+
19
+ MobileWorkflowCli::AppCleaner.start
@@ -2,6 +2,11 @@ require "mobile_workflow_cli/version"
2
2
 
3
3
  require "mobile_workflow_cli/app_generator"
4
4
  require "mobile_workflow_cli/app_builder"
5
+ require "mobile_workflow_cli/app_cleaner"
6
+
7
+ require "mobile_workflow_cli/aws_backend"
8
+ require "mobile_workflow_cli/heroku_backend"
9
+ require "mobile_workflow_cli/dokku_backend"
5
10
 
6
11
  module MobileWorkflowCli
7
12
  class Error < StandardError; end
@@ -9,8 +9,96 @@ module MobileWorkflowCli
9
9
  end
10
10
 
11
11
  def procfiles
12
- template 'Procfile', 'Procfile'
13
- template 'Procfile.dev', 'Procfile.dev'
12
+ copy_file 'Procfile', 'Procfile'
13
+ copy_file 'Procfile.dev', 'Procfile.dev'
14
+ end
15
+
16
+ def rspec_generator
17
+ generate 'rspec:install'
18
+ end
19
+
20
+ def controller_generator
21
+ copy_file 'controller_generator.rb', 'lib/generators/mobile_workflow/controller_generator.rb'
22
+ copy_file 'controller.rb', 'lib/generators/mobile_workflow/templates/controller.rb'
23
+ copy_file 'controller_spec.rb', 'lib/generators/mobile_workflow/templates/controller_spec.rb'
24
+ end
25
+
26
+ def ability_generator
27
+ copy_file 'ability.rb', 'app/models/ability.rb'
28
+ end
29
+
30
+ def application_controller
31
+ copy_file 'application_controller.rb', 'app/controllers/application_controller.rb'
32
+ end
33
+
34
+ def api_controller
35
+ copy_file 'api_controller.rb', 'app/controllers/api_controller.rb'
36
+ end
37
+
38
+ def model_template
39
+ copy_file 'model.rb', 'lib/templates/active_record/model/model.rb'
40
+ end
41
+
42
+ def active_storage
43
+ rails_command 'active_storage:install'
44
+ copy_file 'storage.s3.yml', 'config/storage.yml'
45
+ gsub_file 'config/environments/production.rb', 'config.active_storage.service = :local', 'config.active_storage.service = :amazon'
46
+ end
47
+
48
+ def notifications_controller
49
+ copy_file 'notifications_controller.rb', 'app/controllers/notifications_controller.rb'
50
+ route 'resources :notifications, only: :create'
51
+ end
52
+
53
+ def s3_backend(region)
54
+ @region = region
55
+ aws_backend.create
56
+ say "AWS Access ID: #{aws_backend.access_id}"
57
+ say "AWS Secret Key: #{aws_backend.secret_key}"
58
+
59
+ open('.env', 'a') { |f|
60
+ f.puts "AWS_ACCESS_ID=#{aws_backend.access_id}"
61
+ f.puts "AWS_SECRET_KEY=#{aws_backend.secret_key}"
62
+ f.puts "AWS_REGION=#{aws_backend.region}"
63
+ f.puts "AWS_BUCKET_NAME=#{aws_backend.bucket_name}"
64
+ }
65
+
66
+ if options[:heroku]
67
+ heroku_backend.sync_dotenv
68
+ aws_backend.create_topic_subscription(heroku_backend.notifications_endpoint)
69
+ elsif options[:dokku]
70
+ dokku_backend.sync_dotenv
71
+ aws_backend.create_topic_subscription(dokku_backend.notifications_endpoint)
72
+ end
73
+
74
+ end
75
+
76
+ def heroku
77
+ heroku_backend.create
78
+ heroku_backend.configure_activestorage if options[:s3_storage]
79
+ heroku_backend.deploy
80
+ heroku_backend.sync_dotenv
81
+ end
82
+
83
+ def dokku(dokku_host)
84
+ @dokku_host = dokku_host
85
+ dokku_backend.create
86
+ dokku_backend.configure_activestorage if options[:s3_storage]
87
+ dokku_backend.deploy
88
+ dokku_backend.sync_dotenv
89
+ end
90
+
91
+ private
92
+ def aws_backend
93
+ @aws_backend ||= AwsBackend.new(app_name: app_name, region: @region)
94
+ end
95
+
96
+ def dokku_backend
97
+ @dokku_backend ||= DokkuBackend.new(app_name: app_name, dokku_host: @dokku_host)
98
+ end
99
+
100
+ def heroku_backend
101
+ @heroku_backend ||= HerokuBackend.new(app_name: app_name)
14
102
  end
15
103
  end
16
104
  end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/rails/app/app_generator'
3
+ require 'json'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+
6
+ module MobileWorkflowCli
7
+ class AppCleaner < Thor
8
+ class_option :version, type: :boolean, aliases: "-v", desc: "Show version number and quit"
9
+ class_option :help, type: :boolean, aliases: '-h', desc: "Show this help message and quit"
10
+
11
+ class_option :deploy_heroku, type: :boolean, aliases: "-H", default: false, desc: "Create Heroku app"
12
+ class_option :s3_storage, type: :boolean, default: false, desc: "Create an s3 backend for image upload and storage"
13
+ class_option :aws_region, type: :string, default: 'us-east-1', desc: "Specify a region to create AWS resources in"
14
+
15
+ desc "clean APP_NAME", "clean the app"
16
+ def clean(app_name)
17
+ `rm -rf #{app_name}`
18
+ AwsBackend.new(app_name: app_name, region: options[:aws_region]).destroy
19
+ HerokuBackend.new(app_name: app_name).destroy
20
+ end
21
+
22
+
23
+ end
24
+ end
@@ -5,16 +5,20 @@ require 'active_support/core_ext/hash/indifferent_access'
5
5
 
6
6
  module MobileWorkflowCli
7
7
  class AppGenerator < Rails::Generators::AppGenerator
8
- hide!
9
-
10
- class_option :database, type: :string, aliases: "-d", default: "postgresql", desc: "Configure for selected database (options: #{DATABASES.join("/")})"
8
+ hide!
11
9
  class_option :skip_test, type: :boolean, default: true, desc: "Skip Test Unit"
12
10
  class_option :force, type: :boolean, default: true, desc: "Force overwrite"
13
11
 
14
12
  class_option :version, type: :boolean, aliases: "-v", desc: "Show version number and quit"
15
13
  class_option :help, type: :boolean, aliases: '-h', desc: "Show this help message and quit"
16
14
 
17
- class_option :heroku, type: :boolean, aliases: "-H", default: false, desc: "Create Heroku app"
15
+ class_option :heroku, type: :boolean, default: false, desc: "Create Heroku app"
16
+
17
+ class_option :dokku, type: :boolean, default: false, desc: "Create Dokku app"
18
+ class_option :dokku_host, type: :string, desc: "Specify the Dokku host machine e.g. 18.131.127.164"
19
+
20
+ class_option :s3_storage, type: :boolean, default: false, desc: "Create an s3 backend for image upload and storage"
21
+ class_option :aws_region, type: :string, default: 'us-east-1', desc: "Specify a region to create AWS resources in"
18
22
 
19
23
  def self.banner
20
24
  "mwf <directory> <OpenAPI Spec file path> [options]"
@@ -22,15 +26,24 @@ module MobileWorkflowCli
22
26
 
23
27
  def finish_template
24
28
  run "spring stop"
25
- build :procfiles
29
+
26
30
  super
27
31
  after_bundle do
32
+ build :application_controller
33
+ build :api_controller
34
+ build :notifications_controller if options[:s3_storage]
35
+ build :model_template
36
+ build :procfiles
37
+ build :rspec_generator
38
+ build :controller_generator
39
+ build :ability_generator
40
+ build :active_storage if options[:s3_storage]
41
+
28
42
  open_api_spec = read_openapi_spec
29
43
  generate_models(open_api_spec)
30
44
  setup_db
31
45
 
32
46
  generate_administrate
33
- generate_base_api_controller(open_api_spec)
34
47
  generate_controllers_and_routes(open_api_spec)
35
48
 
36
49
  admin_user = 'admin'
@@ -38,7 +51,9 @@ module MobileWorkflowCli
38
51
  generate_dot_env(admin_user, admin_password)
39
52
  initial_git_commit
40
53
 
41
- deploy_heroku(admin_user, admin_password) if options[:heroku]
54
+ build :heroku if options[:heroku]
55
+ build :dokku, options[:dokku_host] if options[:dokku]
56
+ build :s3_backend, options[:aws_region] if options[:s3_storage]
42
57
  end
43
58
  end
44
59
 
@@ -60,61 +75,43 @@ module MobileWorkflowCli
60
75
  def generate_administrate
61
76
  generate 'administrate:install'
62
77
  file 'app/assets/config/manifest.js', <<-CODE
63
- //= link administrate/application.css
64
- //= link administrate/application.js
78
+ //= link administrate/application.css
79
+ //= link administrate/application.js
65
80
  CODE
66
81
 
67
82
  file 'app/controllers/admin/application_controller.rb', <<-CODE
68
- module Admin
69
- class ApplicationController < Administrate::ApplicationController
70
- before_action :authenticate_admin
83
+ module Admin
84
+ class ApplicationController < Administrate::ApplicationController
85
+ before_action :authenticate_admin
71
86
 
72
- def authenticate_admin
73
- self.class.http_basic_authenticate_with(name: ENV["ADMIN_USER"], password: ENV["ADMIN_PASSWORD"])
74
- end
75
- end
87
+ def authenticate_admin
88
+ self.class.http_basic_authenticate_with(name: ENV["ADMIN_USER"], password: ENV["ADMIN_PASSWORD"])
76
89
  end
77
- CODE
90
+ end
91
+ end
92
+ CODE
78
93
  end
79
94
 
80
95
  def generate_models(openapi_spec)
81
96
  say "Generating models"
82
97
  openapi_spec[:components][:schemas].each_pair do |name, schema|
83
- properties_args = schema['properties'].keys.reject{|key| key == 'id'}.collect{|key| "#{key}:#{schema['properties'][key]['type']}" }.join(' ')
84
- generate(:model, "#{name.underscore} #{properties_args}")
98
+ next if ["answer", "attachment"].include? name # Don't generate schemas for MW schemas
99
+
100
+ generated_properties_args = schema["properties"].keys.collect{|key| "#{key}:string" }.join(" ")
101
+ if yes? "Use generated schema #{name}(#{generated_properties_args})[yn]?"
102
+ generate(:model, "#{name.underscore} #{generated_properties_args}")
103
+ else
104
+ properties_args = ask "Specify schema for #{name}: (e.g. text:string image:attachment region:reference)"
105
+ generate(:model, "#{name.underscore} #{properties_args}")
106
+ end
85
107
  end
86
108
  end
87
109
 
88
- def generate_base_api_controller(openapi_spec)
89
- file 'app/controllers/api_controller.rb', <<-CODE
90
- class ApiController < ActionController::API
91
- end
92
- CODE
93
- end
94
-
95
110
  def generate_controllers_and_routes(openapi_spec)
96
111
  say "Generating controllers"
97
- controller_names = []
98
- openapi_spec[:paths].each_pair do |url_path, path|
99
- controller_name = url_path.split('/').last.pluralize
100
- model = path[:get][:responses]["200"][:content]['application/json'][:schema][:items]['$ref'].split('/').last
101
- file "app/controllers/#{controller_name}_controller.rb", <<-CODE
102
- class #{controller_name.titleize}Controller < ApiController
103
- def index
104
- render json: #{model}.where(filter_params)
105
- end
106
-
107
- private
108
- def filter_params
109
- params.permit() # Add any filter properties here
110
- end
111
- end
112
- CODE
113
- controller_names << controller_name
114
- end
115
- controller_names.each {|n| route "resources :#{n}, only: :index" }
116
-
117
- # Root route
112
+ controller_names = openapi_spec[:paths].keys.collect{|url_path| url_path.split('/').last.pluralize }
113
+ controller_names.each {|n| generate "mobile_workflow:controller #{n.singularize}" }
114
+ controller_names.each {|n| route "resources :#{n}, only: [:index, :show, :create]" }
118
115
  route "root to: 'admin/#{controller_names.first}#index'"
119
116
  end
120
117
 
@@ -131,16 +128,9 @@ module MobileWorkflowCli
131
128
 
132
129
  def generate_dot_env(admin_user, admin_password)
133
130
  file '.env', <<-CODE
134
- ADMIN_USER=#{admin_user}
135
- ADMIN_PASSWORD=#{admin_password}
136
- CODE
137
- end
138
-
139
- def deploy_heroku(admin_user, admin_password)
140
- heroku_output = `heroku create`
141
- `git push heroku master`
142
- `heroku config:set ADMIN_USER=#{admin_user} ADMIN_PASSWORD=#{admin_password}`
143
- say "Server created: #{heroku_output}"
131
+ ADMIN_USER=#{admin_user}
132
+ ADMIN_PASSWORD=#{admin_password}
133
+ CODE
144
134
  end
145
135
  end
146
136
  end
@@ -0,0 +1,57 @@
1
+ class AwsBackend
2
+
3
+ attr_accessor :access_id, :secret_key, :region, :bucket_name
4
+
5
+ def initialize(app_name:, region:)
6
+ @app_name = app_name
7
+ @aws_name = @app_name.gsub("_", "-")
8
+ @region = region
9
+ end
10
+
11
+ def bucket_name
12
+ @aws_name
13
+ end
14
+
15
+ def create
16
+ aws_command "aws s3api create-bucket --bucket #{@aws_name} --acl private --region #{@region} --create-bucket-configuration LocationConstraint=#{@region}"
17
+ @topic_arn = aws_command("aws sns create-topic --name #{@aws_name} --region #{@region}")["TopicArn"]
18
+ aws_command "aws iam create-user --user-name #{@aws_name}"
19
+ aws_command "aws iam put-user-policy --user-name #{@aws_name} --policy-name s3 --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:PutObject\",\"s3:PutObjectAcl\",\"s3:GetObject\", \"s3:DeleteObject\"],\"Resource\":[\"arn:aws:s3:::#{@aws_name}/*\"]}, {\"Effect\": \"Allow\", \"Action\": \"s3:ListBucket\", \"Resource\": \"arn:aws:s3:::#{@aws_name}\"}]}'"
20
+ aws_command "aws iam put-user-policy --user-name #{@aws_name} --policy-name sns --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"sns:ConfirmSubscription\"],\"Resource\":[\"#{@topic_arn}\"]}]}'"
21
+ aws_command "aws sns set-topic-attributes --topic-arn #{@topic_arn} --region #{@region} --attribute-name Policy --attribute-value '{\"Version\": \"2012-10-17\", \"Id\": \"s3\", \"Statement\": [{\"Sid\": \"#{@aws_name}-s3-sid\", \"Effect\": \"Allow\", \"Principal\": {\"AWS\": \"*\"}, \"Action\": \"SNS:Publish\", \"Resource\": \"#{@topic_arn}\", \"Condition\": {\"ArnEquals\": {\"aws:SourceArn\": \"arn:aws:s3:::#{@aws_name}\"}}}]}'"
22
+ aws_command "aws s3api put-bucket-notification-configuration --bucket #{@aws_name} --notification-configuration '{\"TopicConfigurations\": [{\"TopicArn\": \"#{@topic_arn}\", \"Events\": [\"s3:ObjectCreated:*\"]}]}'"
23
+ aws_credentials_json = aws_command("aws iam create-access-key --user-name #{@aws_name}")["AccessKey"]
24
+ @access_id, @secret_key = aws_credentials_json["AccessKeyId"], aws_credentials_json["SecretAccessKey"]
25
+
26
+ return @access_id, @secret_key
27
+ end
28
+
29
+ def create_topic_subscription(endpoint)
30
+ aws_command "aws sns subscribe --topic-arn #{@topic_arn} --region #{@region} --protocol https --notification-endpoint #{endpoint}"
31
+ end
32
+
33
+ def destroy
34
+ aws_command "aws s3api delete-bucket --bucket #{@aws_name} --region #{@region}"
35
+
36
+ aws_command("aws sns list-topics")["Topics"].each do |topic|
37
+ topic_arn = topic["TopicArn"]
38
+ aws_command "aws sns delete-topic --topic-arn '#{topic_arn}'" if topic_arn.end_with?(@aws_name)
39
+ end
40
+
41
+ aws_command "aws iam delete-user-policy --user-name #{@aws_name} --policy-name s3"
42
+ aws_command "aws iam delete-user-policy --user-name #{@aws_name} --policy-name sns"
43
+ aws_command("aws iam list-access-keys --user-name #{@aws_name}")["AccessKeyMetadata"].each do |accessKeyMetadata|
44
+ aws_command "aws iam delete-access-key --user-name #{@aws_name} --access-key #{accessKeyMetadata["AccessKeyId"]}"
45
+ end
46
+ aws_command "aws iam delete-user --user-name #{@aws_name}"
47
+ end
48
+
49
+ private
50
+ def aws_command(command)
51
+ puts "Running: #{command}"
52
+ output = `#{command}`
53
+ puts "Output: #{output}" unless output.blank?
54
+ return !output.blank? ? JSON.parse(output) : nil
55
+ end
56
+
57
+ end
@@ -0,0 +1,53 @@
1
+ class DokkuBackend
2
+ def initialize(dokku_host:, app_name:)
3
+ @dokku_host = dokku_host
4
+ @dokku_app_name = app_name.gsub("_", "-")
5
+ end
6
+
7
+ def create
8
+ remote_command "dokku apps:create #{@dokku_app_name}"
9
+ remote_command "dokku postgres:create #{@dokku_app_name}"
10
+ remote_command "dokku postgres:link #{@dokku_app_name} #{@dokku_app_name}"
11
+
12
+ local_command "git remote add dokku dokku@#{@dokku_host}:#{@dokku_app_name}"
13
+ end
14
+
15
+ def configure_activestorage
16
+
17
+ end
18
+
19
+ def deploy
20
+ local_command "git push dokku master"
21
+ end
22
+
23
+ def sync_dotenv
24
+ env = File.read(".env").split.join(" ")
25
+ puts "Setting env: #{env}"
26
+ local_command "dokku config:set #{env}"
27
+ end
28
+
29
+ def destroy
30
+ remote_command "dokku apps:destroy #{@dokku_app_name}"
31
+ end
32
+
33
+ def dokku_app_host
34
+ remote_command "dokku url #{@dokku_app_name}"
35
+ end
36
+
37
+ def notifications_endpoint
38
+ "https://#{dokku_app_host}/notifications"
39
+ end
40
+
41
+ private
42
+ def remote_command(command)
43
+ command = "ssh -t ubuntu@#{@dokku_host} '#{command}'"
44
+ local_command(command)
45
+ end
46
+
47
+ def local_command(command)
48
+ puts "Running: #{command}"
49
+ output = `#{command}`
50
+ puts "Output: #{output}" unless output.blank?
51
+ return output
52
+ end
53
+ end
@@ -0,0 +1,45 @@
1
+ class HerokuBackend
2
+ def initialize(app_name:)
3
+ @heroku_app_name = app_name.gsub("_", "-")
4
+ end
5
+
6
+ def create
7
+ heroku_command "heroku create #{@heroku_app_name}"
8
+ heroku_command "git push --set-upstream heroku master"
9
+ end
10
+
11
+ def configure_activestorage
12
+ heroku_command "heroku buildpacks:add -i 1 https://github.com/heroku/heroku-buildpack-activestorage-preview --app #{@heroku_app_name}"
13
+ heroku_command "heroku labs:enable runtime-dyno-metadata --app #{@heroku_app_name}" # Gives access to heroku variables which can be used to construct URLs
14
+
15
+ # Force recompile after buildpacks change
16
+ heroku_command "git commit --allow-empty -m 'empty commit'"
17
+ deploy
18
+ end
19
+
20
+ def deploy
21
+ heroku_command "git push"
22
+ end
23
+
24
+ def sync_dotenv
25
+ env = File.read(".env").split.join(" ")
26
+ puts "Setting env: #{env}"
27
+ heroku_command "heroku config:set #{env} --app #{@heroku_app_name}"
28
+ end
29
+
30
+ def destroy
31
+ heroku_command "heroku destroy #{@heroku_app_name} --confirm #{@heroku_app_name}"
32
+ end
33
+
34
+ def notifications_endpoint
35
+ "https://#{@heroku_app_name}.herokuapp.com/notifications"
36
+ end
37
+
38
+ private
39
+ def heroku_command(command)
40
+ puts "Running: #{command}"
41
+ output = `#{command}`
42
+ puts "Output: #{output}" unless output.blank?
43
+ return output
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  module MobileWorkflowCli
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  RUBY_VERSION = '2.5.5'
4
4
  RAILS_VERSION = '6.0.0'
5
5
  end
@@ -3,45 +3,42 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
3
 
4
4
  ruby '2.6.6'
5
5
 
6
- # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
7
- gem 'rails', '~> 6.0.3'
8
- # Use postgresql as the database for Active Record
9
- gem 'pg', '>= 0.18', '< 2.0'
10
- # Use Puma as the app server
6
+ # Core Gems
7
+ gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
8
+
11
9
  gem 'puma', '~> 4.1'
12
- # Use SCSS for stylesheets
13
10
  gem 'sass-rails', '>= 6'
14
- # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
15
11
  gem 'webpacker', '~> 4.0'
16
- # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
17
12
  gem 'turbolinks', '~> 5'
18
- # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
19
- gem 'jbuilder', '~> 2.7'
20
- # Use Redis adapter to run Action Cable in production
21
- # gem 'redis', '~> 4.0'
22
- # Use Active Model has_secure_password
23
- # gem 'bcrypt', '~> 3.1.7'
13
+ gem 'bootsnap', '>= 1.4.2', require: false
24
14
 
25
- # Use Active Storage variant
26
- # gem 'image_processing', '~> 1.2'
15
+ # Authorisation / Authentication
16
+ gem 'cancancan', '~> 3.1'
27
17
 
28
18
  # Admin console
29
- gem "administrate"
30
-
31
- # Reduces boot times through caching; required in config/boot.rb
32
- gem 'bootsnap', '>= 1.4.2', require: false
19
+ gem 'administrate', '~> 0.13.0'
20
+ gem 'administrate-field-active_storage'
21
+ gem 'administrate-field-enum'
33
22
 
34
- group :development, :test do
35
- # Call 'byebug' anywhere in the code to stop execution and get a debugger console
36
- gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
37
- gem 'dotenv-rails'
38
- end
23
+ # Backend storage for S3
24
+ gem "image_processing"
25
+ gem 'aws-sdk-s3', '~> 1.60', '>= 1.60.1'
26
+ gem 'aws-sdk-sns', '~> 1.23'
39
27
 
40
28
  group :development do
41
- # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
42
29
  gem 'web-console', '>= 3.3.0'
43
- gem 'listen', '~> 3.2'
44
- # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
30
+ gem 'listen', '>= 3.0.5', '< 3.2'
45
31
  gem 'spring'
46
32
  gem 'spring-watcher-listen', '~> 2.0.0'
47
33
  end
34
+
35
+ group :development, :test do
36
+ gem 'sqlite3'
37
+ gem 'rspec-rails', '~> 4.0.0'
38
+ gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
39
+ gem 'dotenv-rails'
40
+ end
41
+
42
+ group :production do
43
+ gem 'pg', '>= 0.18', '< 2.0'
44
+ end
@@ -0,0 +1,7 @@
1
+ class Ability
2
+ include CanCan::Ability
3
+
4
+ def initialize(user)
5
+ can :manage, :all
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ class ApiController < ActionController::API
2
+ def current_user
3
+ nil
4
+ end
5
+
6
+ def binary_urls(object)
7
+ return unless params["binaries"]
8
+
9
+ params["binaries"].collect do |binary|
10
+ property = binary["identifier"].split('/')[0] # i.e. image/jpg --> image, video/mp4 --> video
11
+
12
+ {
13
+ "identifier" => binary["identifier"],
14
+ "url" => presigned_url("#{object.class.name.underscore}/#{object.id}/#{property}"),
15
+ "method" => "PUT"
16
+ }
17
+ end
18
+ end
19
+
20
+ private
21
+ def presigned_url(key)
22
+ presigner.presigned_url(:put_object, bucket: ENV['AWS_BUCKET_NAME'], key: key, metadata: {})
23
+ end
24
+
25
+ def presigner
26
+ Aws::S3::Presigner.new(client: s3_client)
27
+ end
28
+
29
+ def s3_client
30
+ Aws::S3::Client.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY'])
31
+ end
32
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,39 @@
1
+ class <%= controller_class_name %>Controller < ApiController
2
+ before_action :rewrite_payload, only: :create
3
+
4
+ load_and_authorize_resource
5
+
6
+ def index
7
+ render json: @<%= controller_class_name.underscore %>.collect(&:list_item_as_json)
8
+ end
9
+
10
+ def show
11
+ render json: @<%= controller_class_name.singularize.underscore %>.display_as_json
12
+ end
13
+
14
+ def create
15
+ if @<%= controller_class_name.singularize.underscore %>.save
16
+ render json: { binary_urls: binary_urls(@<%= controller_class_name.singularize.underscore %>), response: @<%= controller_class_name.singularize.underscore %> }, status: :created
17
+ else
18
+ head :unprocessable_entity
19
+ end
20
+ end
21
+
22
+ private
23
+ def rewrite_payload
24
+ Rails.logger.debug "Pre-rewrite params: #{params}"
25
+
26
+ # Use this method to make any changes to parameters
27
+
28
+ # Example to get properties from question
29
+ # params[:payload][:name] = params.dig(:payload, :name, :answer)
30
+
31
+ # Example to get properties from previous list
32
+ # passport_id = params.dig(:payload, :choose_passport, :selected, :id)
33
+ end
34
+
35
+ def <%= controller_class_name.singularize.underscore %>_params
36
+ # Permit any fields you want to save
37
+ params.require(:payload).permit(:name)
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module MobileWorkflow
2
+ module Generators
3
+ # Custom scaffolding generator
4
+ class ControllerGenerator < Rails::Generators::NamedBase
5
+ include Rails::Generators::ResourceHelpers
6
+
7
+ def self.source_root
8
+ Rails.root
9
+ end
10
+
11
+ def copy_controller_and_spec_files
12
+ template "lib/generators/mobile_workflow/templates/controller.rb", File.join("app/controllers", "#{controller_file_name}_controller.rb")
13
+ template "lib/generators/mobile_workflow/templates/controller_spec.rb", File.join("spec/controllers", "#{controller_file_name}_controller_spec.rb")
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+ require 'json'
5
+
6
+ RSpec.describe <%= controller_class_name %>Controller do
7
+ let(:params) { {} }
8
+ let(:json_response) { JSON.parse(response.body, symbolize_names: true) }
9
+
10
+ describe 'GET #index' do
11
+ let!(:<%= controller_class_name.singularize.underscore %>) { <%= controller_class_name.singularize %>.create }
12
+ before(:each) { get :index, params: params }
13
+
14
+ context 'ok' do
15
+ it { expect(json_response[0][:id]).to eq <%= controller_class_name.singularize.underscore %>.id }
16
+ it { expect(response.status).to eq 200 }
17
+ end
18
+ end
19
+
20
+ describe 'GET #show' do
21
+ let(:<%= controller_class_name.singularize.underscore %>) { <%= controller_class_name.singularize %>.create }
22
+ let(:params) { { id: <%= controller_class_name.singularize.underscore %>.id } }
23
+ before(:each) { get :show, params: params }
24
+
25
+ context 'ok' do
26
+ it { expect(response.status).to eq 200 }
27
+ end
28
+ end
29
+
30
+ describe 'POST #create' do
31
+ let(:payload_params) { {text: 'OK'} }
32
+ let(:params) { { payload: payload_params, binaries: [{identifier: 'record', mimetype: 'video/mp4'}] } }
33
+ before(:each) { post :create, params: params }
34
+
35
+ context 'ok' do
36
+ it { expect(<%= controller_class_name.singularize %>.count).to eq 1 }
37
+ it { expect(response.status).to eq 201 }
38
+ it { expect(json_response[:binary_urls]).to_not be_nil }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,56 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %> < <%= parent_class_name.classify %>
3
+ # Enable if you need to generate attachment URLs
4
+ # include Rails.application.routes.url_helpers
5
+
6
+ <% attributes.select(&:reference?).each do |attribute| -%>
7
+ belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %>
8
+ <% end -%>
9
+ <% attributes.select(&:attachment?).each do |attribute| -%>
10
+ has_one_attached :<%= attribute.name %>
11
+ <% end -%>
12
+ <% attributes.select(&:attachments?).each do |attribute| -%>
13
+ has_many_attached :<%= attribute.name %>
14
+ <% end -%>
15
+
16
+ def list_item_as_json
17
+ {
18
+ id: id,
19
+ text: text,
20
+ # detailText: nil,
21
+ # sfSymbolName: nil,
22
+ # imageURL: preview_url(image, height: 100, width: 100)
23
+ }
24
+ end
25
+
26
+ def display_as_json
27
+ [
28
+ {label: "ID", text: text, mimeType: 'text/plain'},
29
+ {label: "Text", text: text, mimeType: 'text/plain'}
30
+ ]
31
+ end
32
+
33
+ # private
34
+ # include Rails.application.routes.url_helpers # for attachment URLs
35
+ # def preview_url(attachment, height:, width:, options: { resize_to_fill: [height, width]} )
36
+ # return nil unless attachment.attached?
37
+ #
38
+ # if attachment.image?
39
+ # Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(combine_options: options), host: attachment_host)
40
+ # elsif attachment.previewable?
41
+ # Rails.application.routes.url_helpers.rails_representation_url(attachment.preview(options), host: attachment_host)
42
+ # else
43
+ # return nil
44
+ # end
45
+ # end
46
+ #
47
+ # def attachment_url(attachment)
48
+ # Rails.application.routes.url_helpers.rails_blob_url(attachment, host: attachment_host)
49
+ # end
50
+ #
51
+ # def attachment_host
52
+ # "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com"
53
+ # end
54
+
55
+ end
56
+ <% end -%>
@@ -0,0 +1,93 @@
1
+ class NotificationsController < ApiController
2
+ before_action :verify_request_authenticity
3
+
4
+ def create
5
+ Rails.logger.info("Message body: #{message_body}")
6
+
7
+ case message_body['Type']
8
+ when 'SubscriptionConfirmation'
9
+ confirm_subscription ? (head :ok) : (head :bad_request)
10
+ else
11
+ @object = find_object
12
+ @object.send("#{attribute_name}=",active_record_blob)
13
+ if @object.save
14
+ head :ok
15
+ else
16
+ Rails.logger.warn "Error saving object: #{@object} #{object.errors.full_messages}"
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+ def verify_request_authenticity
23
+ head :unauthorized if raw_post.blank?
24
+
25
+ #head :unauthorized if raw_post.blank? || !message_verifier.authentic?(raw_post) # Not working
26
+ end
27
+
28
+ def active_record_blob
29
+ s3_object = s3_bucket.object(object_key)
30
+ checksum_base64 = checksum_base64(object_key, s3_object)
31
+ ActiveStorage::Blob.create! key: s3_object.key, filename: s3_object.key, byte_size: s3_object.size, checksum: checksum_base64, content_type: s3_object.content_type
32
+ end
33
+
34
+ def checksum_base64(object_key, s3_object)
35
+ path = Tempfile.new(object_key).path
36
+ s3_object.download_file(path)
37
+ file = File.new(path)
38
+ Digest::MD5.file(file).base64digest
39
+ end
40
+
41
+ def find_object
42
+ object_class_name, object_id = key_identifiers
43
+ object_class_name.camelcase.constantize.find(object_id.to_i)
44
+ end
45
+
46
+ def attribute_name
47
+ key_identifiers[2]
48
+ end
49
+
50
+ def key_identifiers
51
+ object_class_name, object_id, attribute_name = object_key.split("/")
52
+ return object_class_name, object_id, attribute_name
53
+ end
54
+
55
+ def object_key
56
+ @object_key ||= message['Records'][0]['s3']['object']['key']
57
+ end
58
+
59
+ def message
60
+ message = JSON.parse(message_body['Message'])
61
+ end
62
+
63
+ def message_body
64
+ @message_body ||= JSON.parse(raw_post)
65
+ end
66
+
67
+ def raw_post
68
+ @raw_post ||= request.raw_post
69
+ end
70
+
71
+ def message_verifier
72
+ @message_verifier ||= Aws::SNS::MessageVerifier.new
73
+ end
74
+
75
+ def confirm_subscription
76
+ sns_client.confirm_subscription(
77
+ topic_arn: message_body['TopicArn'],
78
+ token: message_body['Token']
79
+ )
80
+ return true
81
+ rescue Aws::SNS::Errors::ServiceError => e
82
+ Rails.logger.warn(e.message)
83
+ return false
84
+ end
85
+
86
+ def s3_bucket
87
+ Aws::S3::Resource.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY']).bucket(ENV['AWS_BUCKET_NAME'])
88
+ end
89
+
90
+ def sns_client
91
+ Aws::SNS::Client.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY'])
92
+ end
93
+ end
@@ -0,0 +1,14 @@
1
+ test:
2
+ service: Disk
3
+ root: <%= Rails.root.join("tmp/storage") %>
4
+
5
+ local:
6
+ service: Disk
7
+ root: <%= Rails.root.join("storage") %>
8
+
9
+ amazon:
10
+ service: S3
11
+ access_key_id: <%= ENV['AWS_ACCESS_ID'] %>
12
+ secret_access_key: <%= ENV['AWS_SECRET_KEY'] %>
13
+ region: <%= ENV['AWS_REGION'] %>
14
+ bucket: <%= ENV['AWS_BUCKET_NAME'] %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobile_workflow_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brooke-Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-16 00:00:00.000000000 Z
11
+ date: 2020-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: administrate
@@ -71,10 +71,15 @@ files:
71
71
  - Rakefile
72
72
  - USAGE
73
73
  - bin/mwf
74
+ - bin/mwf-delete
74
75
  - bin/setup
75
76
  - lib/mobile_workflow_cli.rb
76
77
  - lib/mobile_workflow_cli/app_builder.rb
78
+ - lib/mobile_workflow_cli/app_cleaner.rb
77
79
  - lib/mobile_workflow_cli/app_generator.rb
80
+ - lib/mobile_workflow_cli/aws_backend.rb
81
+ - lib/mobile_workflow_cli/dokku_backend.rb
82
+ - lib/mobile_workflow_cli/heroku_backend.rb
78
83
  - lib/mobile_workflow_cli/version.rb
79
84
  - mobile_workflow_cli.gemspec
80
85
  - spec/mobile_workflow_cli_spec.rb
@@ -83,6 +88,15 @@ files:
83
88
  - templates/Procfile
84
89
  - templates/Procfile.dev
85
90
  - templates/README.md.erb
91
+ - templates/ability.rb
92
+ - templates/api_controller.rb
93
+ - templates/application_controller.rb
94
+ - templates/controller.rb
95
+ - templates/controller_generator.rb
96
+ - templates/controller_spec.rb
97
+ - templates/model.rb
98
+ - templates/notifications_controller.rb
99
+ - templates/storage.s3.yml
86
100
  homepage: https://github.com/FutureWorkshops/MobileWorkflowCli
87
101
  licenses:
88
102
  - MIT