mobile_workflow 0.7.6 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +83 -0
- data/app/controllers/concerns/mobile_workflow/s3_storable.rb +7 -5
- data/app/controllers/mobile_workflow/sns_notifications_controller.rb +14 -37
- data/app/jobs/mobile_workflow/add_attachment_job.rb +30 -0
- data/app/models/concerns/mobile_workflow/attachable.rb +32 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/form.rb +70 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/list.rb +15 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/map.rb +11 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb +11 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/question.rb +25 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/stack.rb +83 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb +30 -0
- data/app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb +41 -0
- data/app/models/concerns/mobile_workflow/displayable.rb +17 -146
- data/config/initializers/add_frozen_string_literal.rb +19 -0
- data/lib/generators/mobile_workflow/install/install_generator.rb +17 -1
- data/lib/generators/mobile_workflow/install/templates/Gemfile.erb +24 -7
- data/lib/generators/mobile_workflow/install/templates/api_controller.rb.erb +1 -1
- data/lib/generators/mobile_workflow/install/templates/app/helpers/application_helper.rb +1 -2
- data/lib/generators/mobile_workflow/install/templates/{ability.rb → app/models/ability.rb} +1 -1
- data/lib/generators/mobile_workflow/install/templates/app/models/application_record.rb +3 -2
- data/lib/generators/mobile_workflow/install/templates/config/initializers/mobile_workflow_rollbar.rb +7 -0
- data/lib/generators/mobile_workflow/install/templates/deserializer.rb.erb +13 -0
- data/lib/generators/mobile_workflow/install/templates/deserializer_spec.rb.erb +27 -0
- data/lib/generators/mobile_workflow/install/templates/seeds.rb.erb +1 -1
- data/lib/generators/mobile_workflow/install/templates/sessions_controller.rb.erb +3 -4
- data/lib/generators/mobile_workflow/install/templates/user.rb.erb +4 -0
- data/lib/generators/mobile_workflow/model_generator.rb +9 -2
- data/lib/generators/mobile_workflow/templates/controller.rb.erb +7 -5
- data/lib/generators/mobile_workflow/templates/controller_spec.rb.erb +17 -9
- data/lib/generators/mobile_workflow/templates/model.rb.erb +17 -2
- data/lib/mobile_workflow/cli/app_builder.rb +21 -4
- data/lib/mobile_workflow/cli/app_server_generator.rb +3 -1
- data/lib/mobile_workflow/displayable.rb +9 -0
- data/lib/mobile_workflow/engine.rb +7 -5
- data/lib/mobile_workflow/version.rb +1 -1
- data/lib/mobile_workflow.rb +1 -0
- metadata +57 -8
@@ -1,163 +1,34 @@
|
|
1
1
|
module MobileWorkflow
|
2
2
|
module Displayable
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
ON_SUCCESS_OPTIONS = [:none, :reload, :backward, :forward]
|
7
|
-
BUTTON_STYLES = [:primary, :outline, :danger]
|
8
|
-
CONTENT_MODE_OPTIONS = [:scale_aspect_fill, :scale_aspect_fit]
|
9
|
-
QUESTION_STYLES = [:single_choice, :multiple_choice]
|
10
|
-
|
11
|
-
def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, image_attachment: nil)
|
12
|
-
mw_list_item = {id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}
|
13
|
-
mw_list_item[:imageURL] = preview_url(image_attachment, options: { resize_to_fill: [200, 200] }) if image_attachment
|
14
|
-
mw_list_item.compact
|
15
|
-
end
|
16
|
-
|
17
|
-
def mw_list_search_suggestion(id: self.id, text:, section_name:, sf_symbol_name: nil)
|
18
|
-
{id: id.to_s, text: text, sectionName: section_name, sfSymbolName: sf_symbol_name}.compact
|
19
|
-
end
|
20
|
-
|
21
|
-
def mw_map_item(id: self.id, text:, detail_text: nil, latitude:, longitude:)
|
22
|
-
{id: id.to_s, text: text, detailText: detail_text, latitude: latitude, longitude: longitude}.compact
|
23
|
-
end
|
24
|
-
|
25
|
-
def mw_pie_chart_item(id: self.id, label:, value:)
|
26
|
-
{id: id, label: label, value: value}.compact
|
27
|
-
end
|
28
|
-
|
29
|
-
def mw_display_text(text:, label: nil)
|
30
|
-
{type: :text, label: label, text: text.to_s}.compact
|
31
|
-
end
|
32
|
-
|
33
|
-
def mw_display_image(attachment, content_mode: :scale_aspect_fill, options: { resize_to_fill: [1200, 600] })
|
34
|
-
validate_content_mode!(content_mode)
|
35
|
-
|
36
|
-
{type: :image, contentMode: content_mode.to_s.camelize(:lower), previewURL: preview_url(attachment, options: options), url: attachment_url(attachment)}
|
37
|
-
end
|
38
|
-
|
39
|
-
def mw_display_unsplash_image(image_url)
|
40
|
-
if image_url.start_with? "https://unsplash.com/photos"
|
41
|
-
unsplash_id = image_url.split('/').last
|
42
|
-
image_url = "https://source.unsplash.com/#{unsplash_id}/800x600"
|
43
|
-
end
|
44
|
-
|
45
|
-
{type: :image, previewURL: image_url, url: image_url}.compact
|
46
|
-
end
|
47
|
-
|
48
|
-
def mw_display_video(attachment, preview_options: { resize_to_fill: [600, 1200] })
|
49
|
-
{type: :video, previewURL: preview_url(attachment, options: preview_options), url: attachment_url(attachment)}
|
50
|
-
end
|
51
|
-
|
52
|
-
def mw_display_button(label:, style: :primary, on_success: :forward, sf_symbol_name: nil, material_icon_name: nil)
|
53
|
-
validate_on_success!(on_success)
|
54
|
-
validate_button_style!(style)
|
55
|
-
|
56
|
-
{type: :button, label: label, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
|
57
|
-
end
|
58
|
-
|
59
|
-
def mw_display_delete_button(url:, label: "Delete", method: :delete, style: :danger, on_success: :backward)
|
60
|
-
validate_on_success!(on_success)
|
61
|
-
validate_button_style!(style)
|
62
|
-
|
63
|
-
{type: :button, label: label, url: url, method: method, style: style, onSuccess: on_success, sfSymbolName: 'trash', materialIconName: 'delete'}.compact
|
64
|
-
end
|
65
|
-
|
66
|
-
def mw_display_url_button(label:, url:, method: :put, style: :primary, confirm_title: nil, confirm_text: nil, on_success: :reload, sf_symbol_name: nil, material_icon_name: nil)
|
67
|
-
validate_on_success!(on_success)
|
68
|
-
validate_button_style!(style)
|
69
|
-
|
70
|
-
{type: :button, label: label, url: url, method: method, style: style, confirmTitle: confirm_title, confirmText: confirm_text, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
|
71
|
-
end
|
72
|
-
alias_method :mw_display_button_for_url, :mw_display_url_button
|
73
|
-
|
74
|
-
def mw_display_system_url_button(label:, apple_system_url: nil, android_deep_link: nil, style: :primary, sf_symbol_name: nil, material_icon_name: nil)
|
75
|
-
validate_button_style!(style)
|
76
|
-
raise 'Invalid android_deep_link' if android_deep_link && !android_deep_link.start_with?('http')
|
77
|
-
|
78
|
-
{type: :button, label: label, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, style: style, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
|
79
|
-
end
|
80
|
-
alias_method :mw_display_button_for_system_url, :mw_display_system_url_button
|
81
|
-
|
82
|
-
def mw_display_modal_workflow_button(label:, modal_workflow_name:, style: :primary, on_success: :none, sf_symbol_name: nil, material_icon_name: nil)
|
83
|
-
validate_on_success!(on_success)
|
84
|
-
validate_button_style!(style)
|
85
|
-
|
86
|
-
{type: :button, label: label, modalWorkflow: modal_workflow_name, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
|
87
|
-
end
|
88
|
-
alias_method :mw_display_button_for_modal_workflow, :mw_display_modal_workflow_button
|
89
|
-
|
90
|
-
def mw_text_choice_question(question:, style:, text_choices:)
|
91
|
-
raise 'Missing question' if question.blank?
|
92
|
-
raise 'Text Choices should be a hash' unless text_choices.is_a?(Hash)
|
93
|
-
validate_question_style!(style)
|
94
|
-
|
95
|
-
text_choices_a = text_choices.map{|k, v| {_class: "ORKTextChoice", exclusive: false, text: k, value: v} }.to_a
|
96
|
-
{ question: question, answerFormat: { _class: "ORKTextChoiceAnswerFormat", style: style.to_s.camelize(:lower), textChoices: text_choices_a}}
|
97
|
-
end
|
98
|
-
|
99
|
-
def mw_grid_large_section(id:, text:)
|
100
|
-
raise 'Missing id' if id.nil?
|
101
|
-
raise 'Missing text' if text.nil?
|
102
|
-
|
103
|
-
{ id: id, text: text, type: :largeSection }
|
104
|
-
end
|
105
|
-
|
106
|
-
def mw_grid_small_section(id:, text:)
|
107
|
-
raise 'Missing id' if id.nil?
|
108
|
-
raise 'Missing text' if text.nil?
|
109
|
-
|
110
|
-
{ id: id, text: text, type: :smallSection }
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(Steps::Form)
|
111
5
|
end
|
6
|
+
|
7
|
+
include Steps::List
|
8
|
+
include Steps::Map
|
9
|
+
include Steps::PieChart
|
10
|
+
include Steps::Question
|
11
|
+
include Steps::Stack
|
12
|
+
include Steps::StyledContent::Grid
|
13
|
+
include Steps::StyledContent::Stack
|
14
|
+
|
15
|
+
BUTTON_STYLES = [:primary, :outline, :danger, :textOnly]
|
16
|
+
ON_SUCCESS_OPTIONS = [:none, :reload, :backward, :forward]
|
112
17
|
|
113
|
-
def mw_grid_item(id: self.id, text:, image_attachment: nil, options: { resize_to_fill: [1560, 877.5] })
|
114
|
-
raise 'Missing id' if id.nil?
|
115
|
-
raise 'Missing text' if text.nil?
|
116
|
-
|
117
|
-
item = { id: id, text: text, type: :item }
|
118
|
-
item[:imageURL] = preview_url(image_attachment, options: options) if image_attachment
|
119
|
-
item
|
120
|
-
end
|
121
|
-
|
122
18
|
private
|
123
19
|
def validate_on_success!(on_success)
|
124
20
|
raise 'Unknown on_success action' unless ON_SUCCESS_OPTIONS.include?(on_success)
|
125
21
|
end
|
126
22
|
|
127
|
-
def validate_content_mode!(on_success)
|
128
|
-
raise 'Unknown content_mode' unless CONTENT_MODE_OPTIONS.include?(on_success)
|
129
|
-
end
|
130
|
-
|
131
23
|
def validate_button_style!(style)
|
132
24
|
raise 'Unknown style' unless BUTTON_STYLES.include?(style)
|
133
25
|
end
|
134
|
-
|
135
|
-
def validate_question_style!(style)
|
136
|
-
raise 'Unknown style' unless QUESTION_STYLES.include?(style)
|
137
|
-
end
|
138
|
-
|
139
|
-
def preview_url(attachment, options:)
|
140
|
-
return nil unless attachment.attached?
|
141
|
-
|
142
|
-
if attachment.image?
|
143
|
-
Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(options), host: heroku_attachment_host)
|
144
|
-
elsif attachment.previewable?
|
145
|
-
Rails.application.routes.url_helpers.rails_representation_url(attachment.preview(options), host: heroku_attachment_host)
|
146
|
-
else
|
147
|
-
return nil
|
148
|
-
end
|
149
|
-
end
|
150
26
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
Rails.application.routes.url_helpers.rails_blob_url(attachment, host: heroku_attachment_host)
|
155
|
-
end
|
27
|
+
def camelcase_converter(string, first_letter: :upper)
|
28
|
+
string = string.split("_").map(&:capitalize).join
|
29
|
+
return string unless first_letter == :lower
|
156
30
|
|
157
|
-
|
158
|
-
# TODO: MBS - move this to a configuration property
|
159
|
-
app_name = Rails.env.test? ? 'test-app' : ENV.fetch('HEROKU_APP_NAME')
|
160
|
-
"https://#{app_name}.herokuapp.com"
|
31
|
+
string[0].downcase + string[1..-1]
|
161
32
|
end
|
162
33
|
end
|
163
34
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds a `frozen_string_literal` comment to the top of files created by Rails generators.
|
4
|
+
# Taken from https://gist.github.com/thornomad/4e2f0905e2a4a6eefbc4be5772dfd4f7#gistcomment-3533276
|
5
|
+
#
|
6
|
+
# Warning! Doorkeeper auto generated files already include `frozen_string_literal`, so it will be duplicated.
|
7
|
+
|
8
|
+
return unless defined?(::Rails::Generators)
|
9
|
+
|
10
|
+
module RailsGeneratorFrozenStringLiteralPrepend
|
11
|
+
RUBY_EXTENSIONS = %w[.rb .rake]
|
12
|
+
|
13
|
+
def render
|
14
|
+
return super unless RUBY_EXTENSIONS.include? File.extname(self.destination)
|
15
|
+
"# frozen_string_literal: true\n\n" + super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Thor::Actions::CreateFile.prepend RailsGeneratorFrozenStringLiteralPrepend
|
@@ -67,6 +67,22 @@ module MobileWorkflow
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
+
def ability_generator
|
71
|
+
copy_file("app/models/ability.rb")
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_deserializers
|
75
|
+
say "Generating deserializers"
|
76
|
+
|
77
|
+
model_name_to_properties.each_pair do |model_name, properties|
|
78
|
+
@deserializer_class = model_name
|
79
|
+
@deserializer_properties = properties.split(' ').map { |attribute| attribute.split(':').first }
|
80
|
+
|
81
|
+
template("deserializer.rb.erb", "app/services/#{model_name}_deserializer.rb")
|
82
|
+
template("deserializer_spec.rb.erb", "spec/services/#{model_name}_deserializer_spec.rb")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
70
86
|
def generate_controllers_and_routes
|
71
87
|
say "Generating controllers"
|
72
88
|
controller_name_to_actions = open_api_spec.controller_name_to_actions
|
@@ -87,7 +103,7 @@ module MobileWorkflow
|
|
87
103
|
route "resources :#{plural_controller_name}, only: [#{actions.map{|a| ":#{a}"}.join(", ")}]"
|
88
104
|
end
|
89
105
|
end
|
90
|
-
|
106
|
+
|
91
107
|
def generate_seeds
|
92
108
|
template("seeds.rb.erb", "db/seeds.rb", force: true)
|
93
109
|
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
3
5
|
|
4
6
|
ruby '<%= MobileWorkflow::RUBY_VERSION %>'
|
5
7
|
|
6
8
|
# Core Gems
|
7
|
-
gem 'rails', '<%= MobileWorkflow::RAILS_VERSION %>'
|
8
9
|
gem 'puma', '~> 5.0'
|
10
|
+
gem 'rails', '<%= MobileWorkflow::RAILS_VERSION %>'
|
9
11
|
gem 'sass-rails', '>= 6'
|
10
12
|
gem 'turbolinks', '~> 5'
|
11
13
|
|
@@ -14,10 +16,12 @@ gem 'mobile_workflow', '<%= MobileWorkflow::VERSION %>'
|
|
14
16
|
|
15
17
|
# Authorisation / Authentication
|
16
18
|
<%- if options[:doorkeeper_oauth] %>
|
17
|
-
gem 'doorkeeper'
|
18
19
|
gem 'bcrypt'
|
19
20
|
<%- end %>
|
20
21
|
gem 'cancancan', '~> 3.1'
|
22
|
+
<%- if options[:doorkeeper_oauth] %>
|
23
|
+
gem 'doorkeeper'
|
24
|
+
<%- end %>
|
21
25
|
|
22
26
|
# Admin console
|
23
27
|
gem 'administrate', '~> 0.13.0'
|
@@ -26,26 +30,39 @@ gem 'administrate-field-enum'
|
|
26
30
|
|
27
31
|
<%- if options[:s3_storage] %>
|
28
32
|
# Backend storage for S3
|
29
|
-
gem "image_processing"
|
30
33
|
gem 'aws-sdk-s3', '~> 1.60', '>= 1.60.1'
|
31
34
|
gem 'aws-sdk-sns', '~> 1.23'
|
35
|
+
gem "image_processing"
|
32
36
|
<%- end %>
|
33
37
|
|
34
38
|
# FFI for Mac M1
|
35
39
|
gem 'ffi', '~> 1.15.1'
|
36
40
|
|
41
|
+
# Error tracking
|
42
|
+
gem 'rollbar'
|
43
|
+
|
44
|
+
# Data migrations
|
45
|
+
gem 'data_migrate', '~> 7.0.0'
|
46
|
+
|
37
47
|
group :development do
|
38
|
-
gem 'web-console', '>= 3.3.0'
|
39
48
|
gem 'listen', '>= 3.0.5', '< 3.2'
|
49
|
+
gem 'web-console', '>= 3.3.0'
|
40
50
|
end
|
41
51
|
|
42
52
|
group :development, :test do
|
43
|
-
gem '
|
44
|
-
gem 'rspec-rails', '~> 4.0.0'
|
45
|
-
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
|
53
|
+
gem 'byebug', platforms: %i[mri mingw x64_mingw]
|
46
54
|
gem 'dotenv-rails'
|
47
55
|
gem 'factory_bot_rails'
|
56
|
+
gem 'rspec-rails', '~> 4.0.0'
|
57
|
+
gem 'rubocop', '~> 1.16', require: false
|
58
|
+
gem 'rubocop-rails', '~> 2.10.0', require: false
|
59
|
+
gem 'rubocop-rspec', '~> 2.3.0', require: false
|
48
60
|
gem "rufo"
|
61
|
+
gem 'sqlite3'
|
62
|
+
end
|
63
|
+
|
64
|
+
group :test do
|
65
|
+
gem 'simplecov', '~> 0.21.2', require: false
|
49
66
|
end
|
50
67
|
|
51
68
|
group :production do
|
@@ -10,7 +10,7 @@ class ApiController < ActionController::API
|
|
10
10
|
def current_resource_owner
|
11
11
|
@current_resource_owner ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token&.accessible?
|
12
12
|
end
|
13
|
-
|
13
|
+
alias current_user current_resource_owner
|
14
14
|
|
15
15
|
protected
|
16
16
|
def anonymous_action?
|
@@ -1,9 +1,8 @@
|
|
1
1
|
module ApplicationHelper
|
2
|
-
|
3
2
|
def bootstrap_class_for(flash_type)
|
4
3
|
{ success: "alert-success", error: "alert-danger", alert: "alert-warning", notice: "alert-info" }.stringify_keys[flash_type.to_s] || flash_type.to_s
|
5
4
|
end
|
6
|
-
|
5
|
+
|
7
6
|
def flash_messages
|
8
7
|
flash.each do |flash_type, message|
|
9
8
|
concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(flash_type)}", role: 'alert'))
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class <%= @deserializer_class.capitalize %>Deserializer
|
2
|
+
def self.parse(params)
|
3
|
+
<% @deserializer_properties.each do |attribute| -%>
|
4
|
+
<%= attribute %> = params.dig(:payload, :<%= attribute %>)
|
5
|
+
<% end -%>
|
6
|
+
|
7
|
+
{ <%= @deserializer_class %>: {
|
8
|
+
<% @deserializer_properties.each do |attribute| -%>
|
9
|
+
<%= attribute %>: <%= attribute %>,
|
10
|
+
<% end -%>
|
11
|
+
}.compact }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
RSpec.describe <%= @deserializer_class.capitalize %>Deserializer do
|
5
|
+
let(:params) { {} }
|
6
|
+
|
7
|
+
describe 'ok' do
|
8
|
+
let(:params) { { payload: payload } }
|
9
|
+
let(:parsed_params) { { <%= @deserializer_class %>: <%= @deserializer_class %>_params } }
|
10
|
+
let(:payload) {
|
11
|
+
{
|
12
|
+
<% @deserializer_properties.each do |attribute| -%>
|
13
|
+
<%= attribute %>: 'string',
|
14
|
+
<% end -%>
|
15
|
+
}
|
16
|
+
}
|
17
|
+
let(:<%= @deserializer_class %>_params) {
|
18
|
+
{
|
19
|
+
<% @deserializer_properties.each do |attribute| -%>
|
20
|
+
<%= attribute %>: 'string',
|
21
|
+
<% end -%>
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
it { expect(described_class.parse(params)).to eq parsed_params }
|
26
|
+
end
|
27
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
1
|
<%- if options[:doorkeeper_oauth] %>
|
2
2
|
# You can set the OAuth client ID and client secret in your ENV in order to avoid them being reset each time you reset the database.
|
3
3
|
Doorkeeper::Application.create! name: 'Main App', redirect_uri: 'mww://callback', scopes: 'public', uid: ENV['OAUTH_CLIENT_ID'], secret: ENV['OAUTH_CLIENT_SECRET']
|
4
|
-
<%- end %>
|
4
|
+
<%- end %>
|
@@ -1,10 +1,9 @@
|
|
1
1
|
class SessionsController < ApplicationController
|
2
|
-
def new
|
3
|
-
end
|
2
|
+
def new; end
|
4
3
|
|
5
4
|
def create
|
6
5
|
@user = User.find_by("LOWER(email)= ?", params[:email].downcase)
|
7
|
-
if @user
|
6
|
+
if @user&.authenticate(params[:password])
|
8
7
|
session[:user_id] = @user.id
|
9
8
|
redirect_to params[:return_to] || root_url
|
10
9
|
else
|
@@ -12,4 +11,4 @@ class SessionsController < ApplicationController
|
|
12
11
|
render :new
|
13
12
|
end
|
14
13
|
end
|
15
|
-
end
|
14
|
+
end
|
@@ -1,4 +1,8 @@
|
|
1
1
|
class User < ApplicationRecord
|
2
2
|
has_secure_password
|
3
|
+
|
4
|
+
has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :destroy
|
5
|
+
has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :destroy
|
6
|
+
|
3
7
|
validates :email, presence: true, uniqueness: true
|
4
8
|
end
|
@@ -5,11 +5,18 @@ module MobileWorkflow
|
|
5
5
|
|
6
6
|
class ModelGenerator < ActiveRecord::Generators::ModelGenerator
|
7
7
|
source_root File.join(File.dirname(ActiveRecord::Generators::ModelGenerator.instance_method(:create_migration_file).source_location.first), "templates")
|
8
|
-
|
8
|
+
|
9
|
+
class_option :doorkeeper_oauth, type: :boolean, default: false
|
10
|
+
|
9
11
|
def create_model_file
|
10
12
|
template File.join(File.dirname(__FILE__), "templates", "model.rb.erb"), File.join('app/models', class_path, "#{file_name}.rb")
|
11
13
|
end
|
12
|
-
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def doorkeeper_oauth?
|
18
|
+
options[:doorkeeper_oauth]
|
19
|
+
end
|
13
20
|
end
|
14
21
|
end
|
15
22
|
end
|
@@ -4,19 +4,19 @@ class <%= controller_class_name %>Controller < ApiController
|
|
4
4
|
before_action :rewrite_payload, only: :create
|
5
5
|
|
6
6
|
load_and_authorize_resource
|
7
|
-
|
7
|
+
|
8
8
|
<% if index_action? -%>
|
9
9
|
def index
|
10
10
|
render json: @<%= plural_table_name %>.collect(&:list_item_as_json)
|
11
11
|
end
|
12
|
-
|
13
12
|
<% end -%>
|
13
|
+
|
14
14
|
<% if show_action? -%>
|
15
15
|
def show
|
16
16
|
render json: @<%= singular_table_name %>.display_as_json
|
17
17
|
end
|
18
|
-
|
19
18
|
<% end -%>
|
19
|
+
|
20
20
|
<% if create_action? -%>
|
21
21
|
def create
|
22
22
|
<% if doorkeeper_oauth? -%>
|
@@ -41,11 +41,13 @@ class <%= controller_class_name %>Controller < ApiController
|
|
41
41
|
# passport_id = params.dig(:payload, :choose_passport, :selected, :id)
|
42
42
|
|
43
43
|
Rails.logger.debug "Pre-rewrite params: #{params}"
|
44
|
-
|
44
|
+
|
45
|
+
parsed_params = <%= controller_class_name.singularize %>Deserializer.parse(params)
|
46
|
+
params.merge!(parsed_params)
|
45
47
|
end
|
46
48
|
|
47
49
|
def <%= singular_table_name.underscore %>_params
|
48
|
-
params.require(
|
50
|
+
params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
|
49
51
|
end
|
50
52
|
<% end -%>
|
51
53
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
require 'rails_helper'
|
4
2
|
require 'json'
|
5
3
|
|
@@ -11,50 +9,60 @@ RSpec.describe <%= controller_class_name %>Controller do
|
|
11
9
|
let(:token) { instance_double(Doorkeeper::AccessToken, accessible?: true, acceptable?: true, resource_owner_id: user.id) }
|
12
10
|
<% end -%>
|
13
11
|
|
14
|
-
<% if index_action? -%>
|
12
|
+
<% if index_action? -%>
|
15
13
|
describe 'GET #index' do
|
16
14
|
let!(:<%= controller_class_name.singularize.underscore %>) { create(:<%= controller_class_name.singularize.underscore %>) }
|
15
|
+
|
17
16
|
before(:each) do
|
18
17
|
<% if doorkeeper_oauth? -%>
|
19
18
|
allow(subject).to receive(:doorkeeper_token) { token }
|
20
19
|
<% end -%>
|
21
20
|
get :index, params: params
|
22
21
|
end
|
23
|
-
|
22
|
+
|
24
23
|
context 'ok' do
|
25
24
|
it { expect(json_response[0][:id]).to eq <%= controller_class_name.singularize.underscore %>.id }
|
26
25
|
it { expect(response.status).to eq 200 }
|
27
26
|
end
|
28
27
|
end
|
29
28
|
<% end -%>
|
29
|
+
|
30
30
|
<% if show_action? -%>
|
31
31
|
describe 'GET #show' do
|
32
32
|
let(:<%= controller_class_name.singularize.underscore %>) { create(:<%= controller_class_name.singularize.underscore %>) }
|
33
33
|
let(:params) { { id: <%= controller_class_name.singularize.underscore %>.id } }
|
34
|
+
|
34
35
|
before(:each) do
|
35
36
|
<% if doorkeeper_oauth? -%>
|
36
37
|
allow(subject).to receive(:doorkeeper_token) { token }
|
37
38
|
<% end -%>
|
38
39
|
get :show, params: params
|
39
40
|
end
|
40
|
-
|
41
|
-
context 'ok' do
|
41
|
+
|
42
|
+
context 'ok' do
|
42
43
|
it { expect(response.status).to eq 200 }
|
43
44
|
end
|
44
45
|
end
|
45
|
-
|
46
46
|
<% end -%>
|
47
|
+
|
47
48
|
<% if create_action? -%>
|
48
49
|
describe 'POST #create' do
|
49
|
-
let(:payload_params) {
|
50
|
+
let(:payload_params) {
|
51
|
+
{
|
52
|
+
<% attributes_names.each do |attribute| -%>
|
53
|
+
<%= attribute %>: 'string',
|
54
|
+
<% end -%>
|
55
|
+
}
|
56
|
+
}
|
50
57
|
let(:params) { { payload: payload_params, binaries: [{identifier: 'record', mimetype: 'video/mp4'}] } }
|
58
|
+
|
51
59
|
before(:each) do
|
52
60
|
<% if doorkeeper_oauth? -%>
|
53
61
|
allow(subject).to receive(:doorkeeper_token) { token }
|
54
62
|
<% end -%>
|
55
63
|
post :create, params: params
|
56
64
|
end
|
57
|
-
|
65
|
+
|
58
66
|
context 'ok' do
|
59
67
|
it { expect(<%= controller_class_name.singularize %>.count).to eq 1 }
|
60
68
|
it { expect(response.status).to eq 201 }
|
@@ -8,18 +8,33 @@ class <%= class_name %> < <%= parent_class_name.classify %>
|
|
8
8
|
<% end -%>
|
9
9
|
<% attributes.select(&:attachments?).each do |attribute| -%>
|
10
10
|
has_many_attached :<%= attribute.name %>
|
11
|
+
<% end -%>
|
12
|
+
<% if class_name == 'User' -%>
|
13
|
+
<% if doorkeeper_oauth? -%>
|
14
|
+
has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :destroy
|
15
|
+
has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :destroy
|
16
|
+
<% end -%>
|
17
|
+
<% attributes.each do |attribute| -%>
|
18
|
+
<% if attribute.name == 'email' -%>
|
19
|
+
validates :email, presence: true, uniqueness: { case_sensitive: false }
|
20
|
+
before_validation :downcase_email, if: :email_changed?
|
21
|
+
|
22
|
+
def downcase_email
|
23
|
+
self.email = email.downcase
|
24
|
+
end
|
25
|
+
<% end -%>
|
26
|
+
<% end -%>
|
11
27
|
<% end -%>
|
12
28
|
|
13
29
|
def list_item_as_json
|
14
30
|
mw_list_item(text: <%= attributes.first.name %>)
|
15
31
|
end
|
16
|
-
|
32
|
+
|
17
33
|
def display_as_json
|
18
34
|
[
|
19
35
|
mw_display_text(label: 'ID', text: id.to_s),
|
20
36
|
mw_display_text(label: 'Text', text: <%= attributes.first.name %>)
|
21
37
|
]
|
22
38
|
end
|
23
|
-
|
24
39
|
end
|
25
40
|
<% end -%>
|
@@ -48,10 +48,6 @@ CODE
|
|
48
48
|
generate 'administrate:routes'
|
49
49
|
end
|
50
50
|
|
51
|
-
def ability_generator
|
52
|
-
copy_file 'ability.rb', 'app/models/ability.rb'
|
53
|
-
end
|
54
|
-
|
55
51
|
def active_storage
|
56
52
|
rails_command 'active_storage:install'
|
57
53
|
copy_file 'storage.s3.yml', 'config/storage.yml'
|
@@ -78,6 +74,27 @@ ADMIN_USER=#{admin_user}
|
|
78
74
|
ADMIN_PASSWORD=#{admin_password}
|
79
75
|
CODE
|
80
76
|
end
|
77
|
+
|
78
|
+
def rubocop
|
79
|
+
copy_file '.rubocop.yml'
|
80
|
+
command = 'rubocop --auto-gen-config'
|
81
|
+
|
82
|
+
puts "Running: #{command}"
|
83
|
+
output = `#{command}`
|
84
|
+
puts "Output: #{output}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def simplecov
|
88
|
+
append_to_file 'spec/rails_helper.rb', "\n# Config for Test Coverage\nrequire 'simplecov'\nSimpleCov.start\nSimpleCov.minimum_coverage 80\n"
|
89
|
+
append_to_file '.gitignore', "\n# Ignore test coverage reports\n/coverage\n"
|
90
|
+
end
|
91
|
+
|
92
|
+
def rollbar
|
93
|
+
generate 'rollbar'
|
94
|
+
gsub_file 'config/initializers/rollbar.rb', 'if Rails.env.test?', 'if Rails.env.test? || Rails.env.development?'
|
95
|
+
copy_file 'config/initializers/mobile_workflow_rollbar.rb'
|
96
|
+
gsub_file 'app/jobs/application_job.rb', 'class ApplicationJob < ActiveJob::Base', "class ApplicationJob < ActiveJob::Base\n include Rollbar::ActiveJob\n"
|
97
|
+
end
|
81
98
|
|
82
99
|
def git_commit(message = 'Initial commit')
|
83
100
|
git add: "."
|
@@ -34,13 +34,15 @@ module MobileWorkflow::Cli
|
|
34
34
|
build :procfiles
|
35
35
|
build :rspec_generator
|
36
36
|
build :factory_bot
|
37
|
-
build :
|
37
|
+
build :simplecov
|
38
|
+
build :rollbar
|
38
39
|
build :active_storage if options[:s3_storage]
|
39
40
|
build :mobile_workflow_generator, ARGV[1]
|
40
41
|
build :migrate_db
|
41
42
|
build :administrate_generator
|
42
43
|
build :format_source
|
43
44
|
build :generate_dot_env
|
45
|
+
build :rubocop
|
44
46
|
build :git_commit
|
45
47
|
build :heroku if options[:heroku]
|
46
48
|
build :dokku, options[:dokku_host] if options[:dokku]
|