thredded_create_app 0.1.0

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +185 -0
  4. data/exe/thredded_create_app +8 -0
  5. data/lib/thredded_create_app.rb +5 -0
  6. data/lib/thredded_create_app/cli.rb +182 -0
  7. data/lib/thredded_create_app/command_error.rb +14 -0
  8. data/lib/thredded_create_app/generator.rb +91 -0
  9. data/lib/thredded_create_app/logging.rb +36 -0
  10. data/lib/thredded_create_app/tasks/add_devise.rb +63 -0
  11. data/lib/thredded_create_app/tasks/add_display_name_to_users.rb +126 -0
  12. data/lib/thredded_create_app/tasks/add_display_name_to_users/add_display_name_to_users.rb +12 -0
  13. data/lib/thredded_create_app/tasks/add_simple_form.rb +20 -0
  14. data/lib/thredded_create_app/tasks/add_thredded.rb +79 -0
  15. data/lib/thredded_create_app/tasks/add_thredded/_myapp-thredded.scss +18 -0
  16. data/lib/thredded_create_app/tasks/add_thredded/add_admin_to_users.rb +10 -0
  17. data/lib/thredded_create_app/tasks/add_thredded/myapp_thredded.js +1 -0
  18. data/lib/thredded_create_app/tasks/add_thredded/spec/features/thredded_spec.rb +9 -0
  19. data/lib/thredded_create_app/tasks/add_thredded/thredded.en.yml +8 -0
  20. data/lib/thredded_create_app/tasks/add_thredded/thredded_initializer_controller.rb +9 -0
  21. data/lib/thredded_create_app/tasks/base.rb +101 -0
  22. data/lib/thredded_create_app/tasks/create_rails_app.rb +36 -0
  23. data/lib/thredded_create_app/tasks/docker.rb +22 -0
  24. data/lib/thredded_create_app/tasks/docker/Dockerfile.erb +22 -0
  25. data/lib/thredded_create_app/tasks/docker/docker-compose.yml.erb +22 -0
  26. data/lib/thredded_create_app/tasks/docker/wait-for-tcp +30 -0
  27. data/lib/thredded_create_app/tasks/production_configs.rb +21 -0
  28. data/lib/thredded_create_app/tasks/production_configs/Procfile +1 -0
  29. data/lib/thredded_create_app/tasks/production_configs/puma.production.rb +18 -0
  30. data/lib/thredded_create_app/tasks/setup_app_skeleton.rb +160 -0
  31. data/lib/thredded_create_app/tasks/setup_app_skeleton/_flash-messages.scss +19 -0
  32. data/lib/thredded_create_app/tasks/setup_app_skeleton/_flash_messages.html.erb +7 -0
  33. data/lib/thredded_create_app/tasks/setup_app_skeleton/_header.html.erb +13 -0
  34. data/lib/thredded_create_app/tasks/setup_app_skeleton/_variables.scss.erb +6 -0
  35. data/lib/thredded_create_app/tasks/setup_app_skeleton/application.body.html.erb +7 -0
  36. data/lib/thredded_create_app/tasks/setup_app_skeleton/application.scss +83 -0
  37. data/lib/thredded_create_app/tasks/setup_app_skeleton/application_helper_methods.rb +17 -0
  38. data/lib/thredded_create_app/tasks/setup_app_skeleton/en.yml.erb +7 -0
  39. data/lib/thredded_create_app/tasks/setup_app_skeleton/home_show.html.erb.erb +47 -0
  40. data/lib/thredded_create_app/tasks/setup_app_skeleton/seeds.rb.erb +52 -0
  41. data/lib/thredded_create_app/tasks/setup_app_skeleton/spec/controllers/users_controller_spec.rb +21 -0
  42. data/lib/thredded_create_app/tasks/setup_app_skeleton/spec/features/homepage_spec.rb +9 -0
  43. data/lib/thredded_create_app/tasks/setup_app_skeleton/users_show.html.erb +26 -0
  44. data/lib/thredded_create_app/tasks/setup_database.rb +42 -0
  45. data/lib/thredded_create_app/tasks/setup_database/create_postgresql_user.sh +25 -0
  46. data/lib/thredded_create_app/tasks/setup_database/database.yml.erb +36 -0
  47. data/lib/thredded_create_app/version.rb +4 -0
  48. metadata +175 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require 'term/ansicolor'
3
+ module ThreddedCreateApp
4
+ module Logging
5
+ def log_verbose(message = nil)
6
+ return unless verbose?
7
+ log_stderr Term::ANSIColor.bright_magenta(message || yield)
8
+ end
9
+
10
+ def log_command(message)
11
+ log_stderr Term::ANSIColor.bold message
12
+ end
13
+
14
+ def log_info(message)
15
+ log_stderr Term::ANSIColor.bright_blue message
16
+ end
17
+
18
+ def log_warn(message)
19
+ log_stderr Term::ANSIColor.yellow("#{program_name}: [WARN] #{message}")
20
+ end
21
+
22
+ def log_error(message)
23
+ log_stderr Term::ANSIColor.red Term::ANSIColor.bold(
24
+ "#{program_name}: #{message}"
25
+ )
26
+ end
27
+
28
+ def log_stderr(*args)
29
+ STDERR.puts(*args)
30
+ end
31
+
32
+ def program_name
33
+ @program_name ||= File.basename($PROGRAM_NAME)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require 'thredded_create_app/tasks/base'
3
+ module ThreddedCreateApp
4
+ module Tasks
5
+ class AddDevise < Base
6
+ def summary
7
+ 'Add devise with I18n and configure a User model'
8
+ end
9
+
10
+ def before_bundle
11
+ add_gem 'devise'
12
+ add_gem 'devise-i18n'
13
+ end
14
+
15
+ def after_bundle
16
+ replace 'config/initializers/filter_parameter_logging.rb',
17
+ ':password',
18
+ ':password, :password_confirmation'
19
+ run_generator 'devise:install'
20
+ run_generator 'devise User'
21
+ setup_views
22
+ setup_after_sign_in_behaviour
23
+ git_commit 'Setup Devise'
24
+ end
25
+
26
+ private
27
+
28
+ def setup_after_sign_in_behaviour
29
+ inject_into_file 'app/controllers/application_controller.rb',
30
+ after: /protect_from_forgery.*\n/,
31
+ content: <<-'RUBY'
32
+
33
+ before_action :store_current_location, unless: :devise_controller?
34
+
35
+ private
36
+
37
+ def store_current_location
38
+ store_location_for(:user, request.url)
39
+ end
40
+
41
+ def after_sign_out_path_for(resource)
42
+ request.referrer || root_path
43
+ end
44
+ RUBY
45
+ end
46
+
47
+ def setup_views
48
+ run_generator 'devise:i18n:views -v sessions registrations'
49
+ # Make the views render-able outside Devise controllers
50
+ %w(app/views/devise/registrations/new.html.erb
51
+ app/views/devise/registrations/edit.html.erb
52
+ app/views/devise/sessions/new.html.erb
53
+ app/views/devise/shared/_links.html.erb).each do |path|
54
+ replace path, 'resource_name', ':user'
55
+ replace path, 'resource', ':user'
56
+ replace path, 'resource_class', 'User', optional: true
57
+ replace path, 'devise_mapping', 'Devise.mappings[:user]',
58
+ optional: true
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+ require 'thredded_create_app/tasks/base'
3
+ module ThreddedCreateApp
4
+ module Tasks
5
+ class AddDisplayNameToUsers < Base
6
+ def initialize(simple_form: true, **args)
7
+ super
8
+ @simple_form = simple_form
9
+ end
10
+
11
+ def summary
12
+ 'Add display_name to the Devise User model'
13
+ end
14
+
15
+ def after_bundle
16
+ add_display_name
17
+ configure_devise
18
+ configure_thredded
19
+ end
20
+
21
+ private
22
+
23
+ def add_display_name
24
+ run_generator 'migration add_display_name_to_users'
25
+ copy 'add_display_name_to_users/add_display_name_to_users.rb',
26
+ Dir['db/migrate/*_add_display_name_to_users.rb'][0]
27
+ model_path = 'app/models/user.rb'
28
+ inject_into_file model_path,
29
+ after: "ApplicationRecord\n",
30
+ content: display_name_model_rb
31
+ inject_into_file model_path,
32
+ before: /end\n\z/,
33
+ content: uniq_display_name_method_rb
34
+ git_commit 'Add a unique display_name User attribute'
35
+ end
36
+
37
+ def configure_devise
38
+ inject_into_file 'app/controllers/application_controller.rb',
39
+ after: /protect_from_forgery.*\n/,
40
+ content: devise_permitted_params_rb
41
+ %w(app/views/devise/registrations/new.html.erb
42
+ app/views/devise/registrations/edit.html.erb).each do |path|
43
+ autofocus = File.read(path).include?(', autofocus: true')
44
+ replace path, ', autofocus: true', '' if autofocus
45
+ if @simple_form
46
+ inject_into_file path,
47
+ after: %(<div class="form-inputs">\n),
48
+ content: simple_form_input_html(autofocus)
49
+ else
50
+ inject_into_file path,
51
+ after: /error_messages! %>\n\n/,
52
+ content: actionview_input_html(autofocus)
53
+ end
54
+ end
55
+ git_commit 'Configure Devise to support display_name in forms'
56
+ end
57
+
58
+ def configure_thredded
59
+ replace 'config/initializers/thredded.rb',
60
+ 'Thredded.user_name_column = :name',
61
+ 'Thredded.user_name_column = :display_name'
62
+ git_commit 'Configure Thredded to use display_name as the user name'
63
+ end
64
+
65
+ def display_name_model_rb
66
+ <<-'RUBY'
67
+ validates :display_name, presence: true, uniqueness: true
68
+ before_validation :uniq_display_name!, on: :create
69
+
70
+ def display_name=(value)
71
+ super(value&.strip)
72
+ end
73
+
74
+ RUBY
75
+ end
76
+
77
+ def uniq_display_name_method_rb
78
+ <<-'RUBY'
79
+ private
80
+
81
+ # Makes the display_name unique by appending a number to it if necessary.
82
+ # "Gleb" => Gleb 1"
83
+ def uniq_display_name!
84
+ if display_name.present?
85
+ new_display_name = display_name
86
+ i = 0
87
+ while User.exists?(display_name: new_display_name)
88
+ new_display_name = "#{display_name} #{i += 1}"
89
+ end
90
+ self.display_name = new_display_name
91
+ end
92
+ end
93
+ RUBY
94
+ end
95
+
96
+ def devise_permitted_params_rb
97
+ <<-'RUBY'
98
+ before_action :configure_permitted_parameters, if: :devise_controller?
99
+
100
+ protected
101
+
102
+ def configure_permitted_parameters
103
+ devise_parameter_sanitizer.permit(:sign_up, keys: %i(display_name))
104
+ devise_parameter_sanitizer.permit(:account_update, keys: %i(display_name))
105
+ end
106
+ RUBY
107
+ end
108
+
109
+ def simple_form_input_html(autofocus)
110
+ <<-HTML
111
+ <%= f.input :display_name, required: true#{', autofocus: true' if autofocus} %>
112
+ HTML
113
+ end
114
+
115
+ def actionview_input_html(autofocus)
116
+ <<-HTML
117
+ <div class="field">
118
+ <%= f.label :display_name %><br />
119
+ <%= f.text_field :display_name#{', autofocus: true' if autofocus} %>
120
+ </div>
121
+
122
+ HTML
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ class AddDisplayNameToUsers < ActiveRecord::Migration[5.0]
3
+ def up
4
+ add_column :users, :display_name, :text, null: false
5
+ DbTextSearch::CaseInsensitive.add_index connection, :users, :display_name,
6
+ unique: true
7
+ end
8
+
9
+ def down
10
+ remove_column :users, :display_name
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ require 'thredded_create_app/tasks/base'
3
+ module ThreddedCreateApp
4
+ module Tasks
5
+ class AddSimpleForm < Base
6
+ def summary
7
+ 'Add the simple_form gem'
8
+ end
9
+
10
+ def before_bundle
11
+ add_gem 'simple_form'
12
+ end
13
+
14
+ def after_bundle
15
+ run_generator 'simple_form:install'
16
+ git_commit 'Install SimpleForm (rails g simple_form:install)'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ require 'thredded_create_app/tasks/base'
3
+ module ThreddedCreateApp
4
+ module Tasks
5
+ class AddThredded < Base
6
+ def summary
7
+ 'Add and setup Thredded with a User model'
8
+ end
9
+
10
+ def before_bundle
11
+ add_gem 'thredded'
12
+ end
13
+
14
+ def after_bundle
15
+ install_thredded
16
+ git_commit 'Install thredded (rails g thredded:install)'
17
+ add_thredded_routes
18
+ copy 'add_thredded/thredded.en.yml', 'config/locales/thredded.en.yml'
19
+ set_thredded_layout
20
+ configure_thredded_controller
21
+ add_thredded_styles
22
+ add_thredded_javascripts
23
+ copy 'add_thredded/spec/features/thredded_spec.rb',
24
+ 'spec/features/thredded_spec.rb'
25
+ git_commit 'Configure Thredded (routes, assets, behaviour, tests)'
26
+ add_admin_column_to_users
27
+ git_commit 'Add the admin column to users'
28
+ run 'bundle exec rails thredded:install:emoji'
29
+ git_commit 'Copied emoji to public/emoji'
30
+ end
31
+
32
+ private
33
+
34
+ def install_thredded
35
+ run_generator 'thredded:install'
36
+ run 'bundle exec rails thredded:install:migrations' \
37
+ "#{' --quiet' unless verbose?}"
38
+ end
39
+
40
+ def add_thredded_routes
41
+ add_route "mount Thredded::Engine => '/forum'"
42
+ end
43
+
44
+ def set_thredded_layout
45
+ replace 'config/initializers/thredded.rb',
46
+ "Thredded.layout = 'thredded/application'",
47
+ "Thredded.layout = 'application'"
48
+ end
49
+
50
+ def configure_thredded_controller
51
+ copy 'add_thredded/thredded_initializer_controller.rb',
52
+ 'config/initializers/thredded.rb',
53
+ mode: 'a'
54
+ end
55
+
56
+ def add_thredded_styles
57
+ copy 'add_thredded/_myapp-thredded.scss',
58
+ "app/assets/stylesheets/_#{app_name}-thredded.scss"
59
+ if File.file? 'app/assets/stylesheets/application.css'
60
+ File.delete 'app/assets/stylesheets/application.css'
61
+ end
62
+ File.write 'app/assets/stylesheets/application.scss',
63
+ "@import \"#{app_name}-thredded\";\n",
64
+ mode: 'a'
65
+ end
66
+
67
+ def add_thredded_javascripts
68
+ copy 'add_thredded/myapp_thredded.js',
69
+ "app/assets/javascripts/#{app_name}_thredded.js"
70
+ end
71
+
72
+ def add_admin_column_to_users
73
+ run_generator 'migration add_admin_to_users'
74
+ copy 'add_thredded/add_admin_to_users.rb',
75
+ Dir['db/migrate/*_add_admin_to_users.rb'][0]
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,18 @@
1
+ @import "variables";
2
+
3
+ $thredded-brand: $brand-primary;
4
+ $thredded-text-color: $text-color;
5
+ $thredded-base-font-family: $base-font-family;
6
+ $thredded-base-font-size: $base-font-size;
7
+ $thredded-base-line-height: $base-line-height;
8
+ $thredded-grid-container-max-width: $grid-container-max-width;
9
+ @import "thredded";
10
+
11
+ .thredded--main-container {
12
+ // The padding and max-width are handled by the app's container.
13
+ max-width: none;
14
+ padding: 0;
15
+ @include thredded-media-tablet-and-up {
16
+ padding: 0;
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ class AddAdminToUsers < ActiveRecord::Migration[5.0]
3
+ def up
4
+ add_column :users, :admin, :boolean, null: false, default: false
5
+ end
6
+
7
+ def down
8
+ remove_column :users, :admin
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ //= require thredded
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ require 'rails_helper'
3
+
4
+ RSpec.feature 'Thredded', type: :feature do
5
+ it 'root path responds with 200' do
6
+ visit thredded.root_path
7
+ expect(page.status_code).to be 200
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ en:
2
+ thredded:
3
+ messageboard:
4
+ index:
5
+ page_title: Forums
6
+ nav:
7
+ all_messageboards: Forums
8
+
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ Rails.application.config.to_prepare do
3
+ Thredded::ApplicationController.module_eval do
4
+ rescue_from Thredded::Errors::LoginRequired do |exception|
5
+ flash.now[:notice] = exception.message
6
+ render template: 'devise/sessions/new', status: :forbidden
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ require 'shellwords'
3
+ require 'fileutils'
4
+ require 'erb'
5
+ require 'thredded_create_app/logging'
6
+ require 'thredded_create_app/command_error'
7
+ module ThreddedCreateApp
8
+ module Tasks
9
+ # @abstract
10
+ class Base
11
+ include ThreddedCreateApp::Logging
12
+
13
+ def initialize(app_path:, verbose: false, **_args)
14
+ @app_path = app_path
15
+ @app_name = File.basename(File.expand_path(app_path))
16
+ @verbose = verbose
17
+ @gems = []
18
+ end
19
+
20
+ def summary
21
+ self.class.name
22
+ end
23
+
24
+ def before_bundle
25
+ end
26
+
27
+ def after_bundle
28
+ end
29
+
30
+ protected
31
+
32
+ def add_gem(gem_name, version: nil, groups: nil)
33
+ log_verbose "+ gem #{gem_name}"
34
+ @gems << [gem_name, version, groups]
35
+ end
36
+
37
+ def git_commit(message)
38
+ log_info "Commiting: #{message}"
39
+ system 'git add -A .'
40
+ system(*['git', 'commit', '-m', "[thredded_create_app] #{message}",
41
+ ('--quiet' unless verbose?)].compact)
42
+ end
43
+
44
+ attr_reader :app_name, :app_path, :gems
45
+
46
+ def copy(src_path, target_path, mode: 'w')
47
+ copy_template src_path, target_path, process_erb: false, mode: mode
48
+ end
49
+
50
+ def copy_template(src_path, target_path, process_erb: true, mode: 'w')
51
+ src = File.read(File.expand_path(src_path, File.dirname(__FILE__)))
52
+ src = ERB.new(src).result(binding) if process_erb
53
+ FileUtils.mkdir_p(File.dirname(target_path))
54
+ File.write target_path, src, mode: mode
55
+ end
56
+
57
+ def replace(path, pattern, replacement = nil, optional: false, &block)
58
+ src = File.read(path)
59
+ unless src.gsub!(pattern, replacement, &block) || optional
60
+ raise ThreddedCreateApp::CommandError,
61
+ "No match found for #{pattern} in #{path}"
62
+ end
63
+ File.write path, src
64
+ end
65
+
66
+ def add_route(route_str, append: false)
67
+ log_verbose "Add route: #{route_str}"
68
+ inject_into_file 'config/routes.rb',
69
+ content: " #{route_str}\n",
70
+ **(if append
71
+ { after: /\.routes\.draw do\s*\n/m }
72
+ else
73
+ { before: /end\n\z/ }
74
+ end)
75
+ end
76
+
77
+ def inject_into_file(path, content:, after: nil, before: nil)
78
+ replace path, (after || before), after ? '\0' + content : content + '\0'
79
+ end
80
+
81
+ def indent(n, s)
82
+ s.gsub(/^/, ' ' * n)
83
+ end
84
+
85
+ def run_generator(generate_args)
86
+ run "bundle exec rails g #{generate_args}#{' --quiet' unless verbose?}"
87
+ end
88
+
89
+ def run(*args, log: true)
90
+ if log
91
+ log_command args.length == 1 ? args[0] : Shellwords.shelljoin(args)
92
+ end
93
+ exit 1 unless system(*args)
94
+ end
95
+
96
+ def verbose?
97
+ @verbose
98
+ end
99
+ end
100
+ end
101
+ end