adminos 1.0.0.pre.rc.2 → 1.0.0.pre.rc.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -3
  3. data/README.md +26 -0
  4. data/adminos.gemspec +2 -2
  5. data/app/inputs/cropp_input.rb +13 -1
  6. data/app/inputs/filter_inputs/boolean_input.rb +18 -0
  7. data/app/inputs/filter_inputs/date_range_input.rb +36 -0
  8. data/app/inputs/filter_inputs/numeric_input.rb +40 -0
  9. data/app/inputs/filter_inputs/string_input.rb +40 -0
  10. data/lib/adminos.rb +23 -3
  11. data/lib/adminos/configuration.rb +9 -0
  12. data/lib/adminos/controllers/filters.rb +20 -0
  13. data/lib/adminos/controllers/resource.rb +18 -11
  14. data/lib/adminos/extensions/simple_form.rb +12 -0
  15. data/lib/adminos/features/search/elastic.rb +46 -0
  16. data/lib/adminos/features/search/pg_search.rb +49 -0
  17. data/lib/adminos/filters/forms.rb +55 -0
  18. data/lib/adminos/form_builder.rb +11 -0
  19. data/lib/adminos/helpers/models/searchable.rb +9 -0
  20. data/lib/adminos/helpers/view.rb +35 -0
  21. data/lib/generators/adminos/adminos_generator.rb +10 -7
  22. data/lib/generators/adminos/feedback_generator.rb +39 -0
  23. data/lib/generators/adminos/i18n_generator.rb +16 -4
  24. data/lib/generators/adminos/install_generator.rb +22 -0
  25. data/lib/generators/adminos/profile_generator.rb +34 -0
  26. data/lib/generators/adminos/search_generator.rb +38 -0
  27. data/lib/generators/adminos/two_factor_auth_generator.rb +81 -0
  28. data/lib/generators/templates/adminos/fields.slim +1 -1
  29. data/lib/generators/templates/adminos/locales/locale_fields.slim +7 -4
  30. data/lib/generators/templates/adminos/locales/model.rb.erb +2 -3
  31. data/lib/generators/templates/ci/.gitlab-ci.yml +1 -1
  32. data/lib/generators/templates/feedback/auto/app/controllers/admin/feedbacks_controller.rb +17 -0
  33. data/lib/generators/templates/feedback/auto/app/controllers/feedbacks_controller.rb +11 -0
  34. data/lib/generators/templates/feedback/auto/app/models/feedback.rb +6 -0
  35. data/lib/generators/templates/feedback/auto/app/views/admin/feedbacks/_fields.slim +5 -0
  36. data/lib/generators/templates/feedback/auto/app/views/admin/feedbacks/index.slim +28 -0
  37. data/lib/generators/templates/feedback/feedbacks_migration.rb +13 -0
  38. data/lib/generators/templates/field/locales/locale_fields.slim +1 -1
  39. data/lib/generators/templates/field/locales/model.rb.erb +2 -3
  40. data/lib/generators/templates/i18n/Gemfile +6 -1
  41. data/lib/generators/templates/i18n/auto/app/validators/locale_validator.rb +5 -1
  42. data/lib/generators/templates/i18n/auto/app/views/admin/base/_pills.slim +1 -5
  43. data/lib/generators/templates/i18n/auto/app/views/admin/pages/_locale_fields.slim +5 -5
  44. data/lib/generators/templates/i18n/auto/config/initializers/mobility.rb +92 -0
  45. data/lib/generators/templates/i18n/page.rb +2 -2
  46. data/lib/generators/templates/install/Gemfile +3 -5
  47. data/lib/generators/templates/install/README.md +22 -0
  48. data/lib/generators/templates/install/auto/Capfile +12 -2
  49. data/lib/generators/templates/install/auto/app/views/admin/base/_form.slim +1 -1
  50. data/lib/generators/templates/install/auto/app/views/shared/helpers/admin/_collection_header.slim +2 -0
  51. data/lib/generators/templates/install/auto/app/views/shared/helpers/admin/_object_link_new.slim +1 -1
  52. data/lib/generators/templates/install/auto/config/deploy/staging.rb +15 -10
  53. data/lib/generators/templates/install/auto/config/deploy/{shared → templates}/database.yml.erb +1 -1
  54. data/lib/generators/templates/install/auto/config/deploy/templates/nginx_conf.erb +96 -0
  55. data/lib/generators/templates/install/auto/config/deploy/templates/puma.rb.erb +52 -0
  56. data/lib/generators/templates/install/auto/config/environments/staging.rb +1 -1
  57. data/lib/generators/templates/install/auto/config/initializers/adminos.rb +3 -0
  58. data/lib/generators/templates/install/auto/config/locales/adminos.ru.yml +18 -0
  59. data/lib/generators/templates/install/auto/config/schedule.rb +0 -0
  60. data/lib/generators/templates/install/auto/config/systemd/puma.service.erb +19 -0
  61. data/lib/generators/templates/install/auto/config/systemd/sidekiq.service.erb +21 -0
  62. data/lib/generators/templates/install/auto/lib/capistrano/{template.rb → smart_templates.rb} +17 -3
  63. data/lib/generators/templates/install/auto/lib/capistrano/tasks/setup_config.cap +27 -20
  64. data/lib/generators/templates/install/deploy.rb.erb +13 -12
  65. data/lib/generators/templates/profile/auto/app/controllers/admin/profiles_controller.rb +26 -0
  66. data/lib/generators/templates/profile/auto/app/views/admin/profiles/edit.slim +17 -0
  67. data/lib/generators/templates/two_facto_auth/Gemfile +1 -0
  68. data/lib/generators/templates/two_facto_auth/auto/app/controllers/concerns/authenticates_with_two_factor.rb +36 -0
  69. data/lib/generators/templates/two_facto_auth/auto/app/controllers/users/sessions_controller.rb +5 -0
  70. data/lib/generators/templates/two_facto_auth/auto/app/views/admin/profiles/_2fa.slim +24 -0
  71. data/lib/generators/templates/two_facto_auth/auto/app/views/devise/sessions/two_factor.slim +15 -0
  72. data/spec/lib/generators/adminos/feedback_generator_rspec.rb +50 -0
  73. data/spec/lib/generators/adminos/install_generator_rspec.rb +6 -7
  74. data/spec/lib/generators/adminos/profile_generator_rspec.rb +33 -0
  75. data/spec/lib/generators/adminos/search_generator.rb +20 -0
  76. data/spec/lib/generators/adminos/two_factor_auth_generator_rspec.rb +61 -0
  77. data/spec/spec_helper.rb +4 -0
  78. data/spec/support/shared/generator.rb +1 -1
  79. metadata +55 -22
  80. data/bin/rspec +0 -29
  81. data/lib/adminos/extensions/globalize_actiontext.rb +0 -28
  82. data/lib/adminos/extensions/globalize_fields.rb +0 -19
  83. data/lib/generators/templates/adminos/locales/migration.rb.erb +0 -18
  84. data/lib/generators/templates/field/locales/migration.rb.erb +0 -13
  85. data/lib/generators/templates/i18n/add_translation_table_to_page.rb +0 -15
  86. data/lib/generators/templates/i18n/auto/config/initializers/globalize_fields.rb +0 -1
  87. data/lib/generators/templates/install/auto/config/deploy/shared/nginx.conf.erb +0 -28
  88. data/lib/generators/templates/install/auto/config/deploy/shared/unicorn.rb.erb +0 -35
  89. data/lib/generators/templates/install/auto/lib/capistrano/substitute_strings.rb +0 -12
  90. data/package-lock.json +0 -3
@@ -0,0 +1,49 @@
1
+ module Adminos
2
+ module Search
3
+ module PgSearch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ::PgSearch
8
+
9
+ pg_search_scope :search_by, against: :email, using: {
10
+ tsearch: { any_word: true, prefix: true }
11
+ }
12
+ end
13
+
14
+ class_methods do
15
+ def search_fields
16
+ base_search_fields + against
17
+ end
18
+
19
+ def associated_fields
20
+ data = associated_against
21
+ data.merge!(actiontext_attributes)
22
+ data
23
+ end
24
+
25
+ def associated_against
26
+ {}
27
+ end
28
+
29
+ def against
30
+ []
31
+ end
32
+
33
+ def actiontext_attributes
34
+ reflect_has_one = self.reflect_on_all_associations(:has_one)
35
+ rich_text_attributes = reflect_has_one.map(&:name).map { |name| name if name.to_s.include?('rich_text_') }.compact
36
+
37
+ rich_text_attributes.inject({}) do |memo, attr|
38
+ memo[attr] = [:body]
39
+ memo
40
+ end
41
+ end
42
+
43
+ def base_search_fields
44
+ self.columns.map { |c| c.name if [:string, :text].include?(c.type) }.compact
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,55 @@
1
+ module Adminos
2
+ module Filters
3
+ class FormBuilder < ::Adminos::FormBuilder
4
+ map_type :date_range, to: SimpleForm::Inputs::DateTimeInput
5
+ map_type :numeric, to: SimpleForm::Inputs::NumericInput
6
+
7
+ def filter(attribute_name, options = {})
8
+ if attribute_name.present? && options[:as] ||= input_type(attribute_name)
9
+ template.concat input(attribute_name, options)
10
+ end
11
+ end
12
+
13
+ def input_type(attribute_name)
14
+ column = find_attribute_column(attribute_name)
15
+ input_type = default_input_type(attribute_name, column, {})
16
+
17
+ case input_type
18
+ when :date, :datetime
19
+ :date_range
20
+ when :string, :text, :jsonb, :email
21
+ :string
22
+ when :integer, :float, :decimal
23
+ :numeric
24
+ when :boolean
25
+ :boolean
26
+ end
27
+ end
28
+
29
+ def klass
30
+ @object.object
31
+ end
32
+
33
+ def find_attribute_column(attribute_name)
34
+ if klass.respond_to?(:type_for_attribute) && klass.has_attribute?(attribute_name)
35
+ klass.type_for_attribute(attribute_name.to_s)
36
+ elsif klass.respond_to?(:column_for_attribute) && klass.has_attribute?(attribute_name)
37
+ klass.column_for_attribute(attribute_name)
38
+ end
39
+ end
40
+
41
+ def find_mapping(input_type)
42
+ if mapping = self.class.mappings[input_type]
43
+ mapping_override(mapping) || mapping
44
+ camelized = "#{input_type.to_s.camelize}Input"
45
+
46
+ attempt_mapping(camelized, FilterInputs) ||
47
+ attempt_mapping_with_custom_namespace(camelized) ||
48
+ attempt_mapping(camelized, Object) ||
49
+ attempt_mapping(camelized, self.class) ||
50
+ raise("No input found for #{input_type}")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ module Adminos
2
+ class FormBuilder < SimpleForm::FormBuilder
3
+ def input(attribute_name, options = {}, &block)
4
+ locale = options.delete(:locale)
5
+
6
+ attribute_name = [attribute_name, locale].compact.join('_')
7
+
8
+ super(attribute_name, options, &block)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Adminos::Searchable
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def searchable(*args)
6
+ include Adminos.configuration.search_engine
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,39 @@
1
1
  module Adminos::Helpers::View
2
+ def adminos_form_for(object, *args, &block)
3
+ options = args.extract_options!
4
+ simple_form_for(object, *(args << options.merge(builder: Adminos::FormBuilder)), &block)
5
+ end
6
+
7
+ # Helper method to render a filter form
8
+ def adminos_filters_form_for(filters, options = {})
9
+ return if filters.blank?
10
+
11
+ defaults = {
12
+ builder: Adminos::Filters::FormBuilder,
13
+ method: :get,
14
+ url: polymorphic_path([:admin, resource_class]),
15
+ html: { class: 'form-inline filter-form' }
16
+ }
17
+
18
+ form_for resource_class.ransack(params[:q]), defaults do |f|
19
+
20
+ filters.each do |attribute, opts|
21
+ opts.merge!(input_html: {
22
+ class: 'filter_row form-control'
23
+ })
24
+
25
+ f.filter attribute.to_sym, opts
26
+ end
27
+
28
+ buttons = content_tag :div, class: "buttons" do
29
+ f.submit('Filter', class: 'btn btn-primary') +
30
+ link_to('Clear', request.path, class: 'clear_filters_btn btn btn-secondary')
31
+ end
32
+
33
+ f.template.concat buttons
34
+ end
35
+ end
36
+
2
37
  def inside_layout(l, &block)
3
38
  content_for("#{l}_layout".to_sym, capture(&block))
4
39
  render(template: "layouts/#{l}")
@@ -26,6 +26,7 @@ module Adminos::Generators
26
26
  file = Dir.glob('db/migrate/*.rb').grep(/\d+_create_#{table_name}.rb$/).first
27
27
 
28
28
  return unless file
29
+
29
30
  inject_into_file file, file_content('migration.rb', true), after: /create_table.*\n/
30
31
 
31
32
  if drag_type?
@@ -62,6 +63,10 @@ module Adminos::Generators
62
63
  def locale_specific_actions
63
64
  return unless options.locale? || attributes.map(&:locale).any?
64
65
 
66
+ file = Dir.glob('db/migrate/*.rb').grep(/\d+_create_#{table_name}.rb$/).first
67
+ locale_attributes = attributes.map { |attr| attr.name if attr.locale }.compact
68
+ locale_attributes += %w(name nav_name meta_description meta_title)
69
+
65
70
  template 'locales/locale_fields.slim', "app/views/admin/#{table_name}/_locale_fields.slim"
66
71
  template 'locales/general_fields.slim', "app/views/admin/#{table_name}/_general_fields.slim" #unless attributes.map(&:locale).delete_if{|e| e == true}.blank?
67
72
  remove_file "app/views/admin/#{table_name}/_fields.slim"
@@ -69,17 +74,15 @@ module Adminos::Generators
69
74
  if File.exist?("app/models/#{file_name}.rb")
70
75
  inject_into_file "app/models/#{file_name}.rb", erb_file_content('locales/model.rb.erb'), before: /^\Snd\n/
71
76
  end
72
- comment_lines "app/models/#{file_name}.rb", /validates :name/
73
77
 
74
- if File.exist?("app/controllers/admin/#{table_name}_controller.rb")
75
- inject_into_file "app/controllers/admin/#{table_name}_controller.rb", " filter_by_locale: true,\n", before: /.*find_by_slug.*\n/
78
+ locale_attributes.each do |f|
79
+ gsub_file(file, /(t.string|t.text).+:(#{f})/, "t.jsonb :#{f}")
76
80
  end
77
81
 
78
- migration_template 'locales/migration.rb.erb', "db/migrate/add_translation_table_to_#{file_name}.rb"
82
+ comment_lines "app/models/#{file_name}.rb", /validates :name/
79
83
 
80
- unless options.seo?
81
- file = Dir.glob('db/migrate/*.rb').grep(/\d+_add_translation_table_to_#{file_name}.rb$/).first
82
- gsub_file(file, /.+(meta_description|meta_title).+/, '')
84
+ if File.exist?("app/controllers/admin/#{table_name}_controller.rb")
85
+ inject_into_file "app/controllers/admin/#{table_name}_controller.rb", " filter_by_locale: true,\n", before: /.*find_by_slug.*\n/
83
86
  end
84
87
  end
85
88
 
@@ -0,0 +1,39 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module Adminos::Generators
4
+ class FeedbackGenerator < Rails::Generators::Base
5
+ include ActiveRecord::Generators::Migration
6
+
7
+ desc 'Set up feedbacks'
8
+ source_root File.expand_path '../../templates/feedback', __FILE__
9
+
10
+ def auto
11
+ directory 'auto', '.', mode: :preserve
12
+ end
13
+
14
+ def route
15
+ insert_into_file "config/routes.rb", after: "namespace :admin do\n" do
16
+ <<~ROUT.indent(4)
17
+
18
+ resources :feedbacks, except: :show do
19
+ collection { post :batch_action }
20
+ end
21
+
22
+ ROUT
23
+ end
24
+ end
25
+
26
+ def migration
27
+ migration_template 'feedbacks_migration.rb', 'db/migrate/create_feedbacks.rb'
28
+ end
29
+
30
+ def sidebar_link
31
+ insert_into_file "app/views/shared/admin/_sidebar.slim", before: "\n = top_menu_item active: 'admin/settings#' do" do
32
+ <<~SLIM.indent(8)
33
+ = top_menu_item active: 'admin/feedbacks#' do
34
+ = link_to t('admin.feedbacks.actions.index.header'), admin_feedbacks_path, class: 'nav__link'
35
+ SLIM
36
+ end
37
+ end
38
+ end
39
+ end
@@ -53,11 +53,19 @@ module Adminos::Generators
53
53
 
54
54
  def localize_pages
55
55
  return unless options.pages?
56
+ file = Dir.glob('db/migrate/*.rb').grep(/\d+_create_pages.rb$/).first
57
+ model = 'app/models/page.rb'
56
58
 
57
- migration_template 'add_translation_table_to_page.rb', 'db/migrate/add_translation_table_to_page.rb'
58
- inject_into_file 'app/models/page.rb', file_content('page.rb'), before: /.*BEHAVIORS = .*/
59
- comment_lines 'app/models/page.rb', /validates :name/
60
- inject_into_file 'app/models/page.rb', ', :translations_attributes', after: /.*:parent_id, :published/
59
+ %w(name nav_name body meta_description meta_title).each do |f|
60
+ gsub_file(file, /(t.string|t.text).+:#{f}/, "t.jsonb :#{f}")
61
+ end
62
+
63
+ inject_into_file model, file_content('page.rb'), before: /.*BEHAVIORS = .*/
64
+
65
+ gsub_file(model, 'has_rich_text :content', 'I18n.available_locales.each { |locale| has_rich_text(:"content_#{locale}") }')
66
+
67
+ comment_lines model, /validates :name/
68
+ inject_into_file model, ', :translations_attributes', after: /.*:parent_id, :published/
61
69
  inject_into_file 'app/controllers/admin/pages_controller.rb', " filter_by_locale: true,\n", after: /.*[:sorted],\n/
62
70
  inject_into_file 'app/controllers/application_controller.rb', " before_action :check_page_name_locale, unless: -> { controller_path.split('/').first == 'admin' }", after: /.*before_action.*\n/
63
71
  remove_file 'app/views/admin/pages/_fields.slim'
@@ -67,6 +75,10 @@ module Adminos::Generators
67
75
  directory 'auto', '.'
68
76
  end
69
77
 
78
+ def setup_tolk
79
+ rake 'tolk:setup'
80
+ end
81
+
70
82
  private
71
83
 
72
84
  def available_locales
@@ -16,6 +16,7 @@ module Adminos::Generators
16
16
  class_option :locales, type: :string
17
17
  class_option :is_test, type: :boolean, default: false
18
18
  class_option :ci, type: :boolean, default: true, desc: 'Set up Gitlab CI'
19
+ class_option :search, type: :string, enum: %w[elastic pg_search], default: 'pg_search', desc: 'Set up Search'
19
20
 
20
21
  def questions
21
22
  @settings = {}
@@ -53,6 +54,10 @@ module Adminos::Generators
53
54
  append_file 'db/seeds.rb', file_content('prepare_settings.rb')
54
55
  end
55
56
 
57
+ def readme
58
+ append_file 'README.md', file_content('README.md')
59
+ end
60
+
56
61
  def templates
57
62
  template 'deploy.rb.erb', 'config/deploy.rb'
58
63
  template 'database.yml', 'config/database.yml'
@@ -91,10 +96,27 @@ module Adminos::Generators
91
96
  run 'bundle exec rails action_text:copy_migrations'
92
97
  end
93
98
 
99
+ def run_binstubs
100
+ run 'bundle binstubs puma'
101
+ end
102
+
94
103
  def install_adminos_assets
95
104
  run "yarn add adminos"
96
105
  end
97
106
 
107
+ def setup_search
108
+ case options[:search]
109
+ when 'elastic'
110
+ gsub_file 'config/initializers/adminos.rb', /config.search_engine.+/ do
111
+ 'config.search_engine = Adminos::Search::Elastic'
112
+ end
113
+
114
+ gsub_file 'Gemfile', "gem 'pg_search'", ''
115
+
116
+ inject_into_file 'Gemfile', "gem 'searchkick'\n", after: /gem 'adminos'.+\n/
117
+ end
118
+ end
119
+
98
120
  def install_devise
99
121
  return unless @settings[:install_devise]
100
122
  generate 'devise:install'
@@ -0,0 +1,34 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module Adminos::Generators
4
+ class ProfileGenerator < Rails::Generators::Base
5
+ include ActiveRecord::Generators::Migration
6
+
7
+ desc 'Set up admin profile page'
8
+ source_root File.expand_path '../../templates/profile', __FILE__
9
+
10
+ def auto
11
+ directory 'auto', '.', mode: :preserve
12
+ end
13
+
14
+ def route
15
+ insert_into_file "config/routes.rb", after: "resource :settings, only: [:edit, :update]\n" do
16
+ <<~ROUT.indent(6)
17
+ resource :profile, only: [:edit, :update]
18
+ ROUT
19
+ end
20
+ end
21
+
22
+ def sidebar_link
23
+ insert_into_file "app/views/shared/admin/_sidebar.slim", before: "\n = top_menu_item active: 'admin/settings#' do" do
24
+ <<~SLIM.indent(8)
25
+
26
+ = top_menu_item active: 'admin/profiles#' do
27
+ = link_to edit_admin_profile_path, class: 'nav__link' do
28
+ span.link.link--settings
29
+ = t('admin.profile.actions.index.header')
30
+ SLIM
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+
2
+ module Adminos::Generators
3
+ class SearchGenerator < Rails::Generators::NamedBase
4
+ desc 'Helps you setup your search.'
5
+
6
+ def configure_model
7
+ inject_into_class model_path, model do
8
+ <<~MODEL.indent(2)
9
+ include Adminos::Searchable
10
+
11
+ searchable
12
+ MODEL
13
+ end
14
+ end
15
+
16
+ def add_partial
17
+ insert_into_file partial_path, after: /collection_header\n/ do
18
+ <<~SLIM
19
+ = render 'shared/admin/search_form'
20
+ SLIM
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def model_path
27
+ "app/models/#{model}.rb"
28
+ end
29
+
30
+ def partial_path
31
+ "app/views/admin/#{model.pluralize}/index.slim"
32
+ end
33
+
34
+ def model
35
+ "#{file_path.tr('/', '_').singularize.classify}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,81 @@
1
+ require 'rails/generators/active_record'
2
+ require "adminos/generators/utilities"
3
+
4
+ module Adminos::Generators
5
+ class TwoFactorAuthGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+ include Utilities
8
+
9
+ desc 'Set up 2fa for users'
10
+ source_root File.expand_path '../../templates/two_facto_auth', __FILE__
11
+
12
+ def auto
13
+ directory 'auto', '.', mode: :preserve
14
+ end
15
+
16
+ def bundle_dependencies
17
+ merge_gemfile_with('Gemfile')
18
+ install_dependencies
19
+ end
20
+
21
+ def generate_two_factor
22
+ generate 'devise_two_factor user OTP_SECRET_ENCRYPTION_KEY'
23
+ end
24
+
25
+ def routes
26
+ insert_into_file "config/routes.rb", after: "resource :profile, only: [:edit, :update]\n" do
27
+ <<~ROUT.indent(6)
28
+ resource :profile, only: [] do
29
+ member { post :toggle_two_factor }
30
+ end
31
+ ROUT
32
+ end
33
+
34
+ insert_into_file "config/routes.rb", after: "devise_for :users, skip: :omniauth_callbacks" do
35
+ <<~ROUT
36
+ ,
37
+ controllers: { sessions: 'users/sessions'}
38
+ ROUT
39
+ end
40
+ end
41
+
42
+ def add_toggle_two_factor_action
43
+ insert_into_file "app/controllers/admin/profiles_controller.rb", before: "\n private" do
44
+ <<~RUBY.indent(2)
45
+
46
+ def toggle_two_factor
47
+ if resource.otp_required_for_login
48
+ resource.update(otp_required_for_login: false)
49
+ else
50
+ resource.update(otp_required_for_login: true, otp_secret: User.generate_otp_secret)
51
+ end
52
+ redirect_to action: :edit
53
+ end
54
+ RUBY
55
+ end
56
+ end
57
+
58
+ def add_2fa_to_profile_page
59
+ insert_into_file "app/views/admin/profiles/edit.slim", after: "= f.input :current_password, hint: 'Введите текущий пароль'\n" do
60
+ <<~SLIM.indent(6)
61
+ = render '2fa', resource: resource
62
+ SLIM
63
+ end
64
+ end
65
+
66
+ def add_qrcode_helper
67
+ insert_into_file "app/helpers/application_helper.rb", after: "module ApplicationHelper\n" do
68
+ <<~RUBY.indent(2)
69
+ def google_authenticator_qrcode(devise_resource)
70
+ issuer = '' # TODO: issuer name
71
+ label = "\#{issuer}:\#{devise_resource.email}"
72
+ data = devise_resource.otp_provisioning_uri(label, issuer: issuer)
73
+ escaped_data = Rack::Utils.escape(data)
74
+ url = "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=\#{escaped_data}"
75
+ image_tag(url, alt: 'Google Authenticator QRCode')
76
+ end
77
+ RUBY
78
+ end
79
+ end
80
+ end
81
+ end