trek 0.1.23 → 0.1.25

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +55 -0
  3. data/CHANGELOG.md +20 -0
  4. data/CLAUDE.md +1 -0
  5. data/Gemfile.lock +6 -6
  6. data/Makefile +1 -0
  7. data/app/components/trek/form/actions_component/actions_component.css +2 -2
  8. data/app/controllers/concerns/trek/authentication.rb +1 -1
  9. data/app/controllers/concerns/trek/filters.rb +52 -0
  10. data/app/controllers/concerns/trek/localization.rb +1 -1
  11. data/app/controllers/concerns/trek/scopes.rb +24 -0
  12. data/app/helpers/trek/theme_helper.rb +2 -2
  13. data/app/models/concerns/trek/users/invitable.rb +4 -3
  14. data/config/locales/trek.en.yml +9 -6
  15. data/config/locales/trek.fr.yml +9 -6
  16. data/docs/.vitepress/config.mjs +5 -0
  17. data/docs/reference/controllers.md +72 -0
  18. data/lib/generators/trek/helpers.rb +15 -0
  19. data/lib/generators/trek/install/admin/user_password_resets_generator.rb +0 -4
  20. data/lib/generators/trek/install/admin/user_sessions_generator.rb +0 -7
  21. data/lib/generators/trek/install/admin/users_generator.rb +5 -1
  22. data/lib/generators/trek/install/agents_generator.rb +26 -0
  23. data/lib/generators/trek/install/attachments_generator.rb +3 -1
  24. data/lib/generators/trek/install/authentication_generator.rb +70 -1
  25. data/lib/generators/trek/install/authorization_generator.rb +4 -2
  26. data/lib/generators/trek/install/brakeman_generator.rb +4 -2
  27. data/lib/generators/trek/install/bundler_audit_generator.rb +4 -2
  28. data/lib/generators/trek/install/config_generator.rb +4 -0
  29. data/lib/generators/trek/install/healthcheck_generator.rb +4 -2
  30. data/lib/generators/trek/install/kaminari_generator.rb +4 -2
  31. data/lib/generators/trek/install/locales_generator.rb +10 -7
  32. data/lib/generators/trek/install/lookbook_generator.rb +3 -1
  33. data/lib/generators/trek/install/mailer_generator.rb +14 -9
  34. data/lib/generators/trek/install/page_generator.rb +4 -2
  35. data/lib/generators/trek/install/postmark_generator.rb +5 -3
  36. data/lib/generators/trek/install/rubocop_generator.rb +4 -2
  37. data/lib/generators/trek/install/scalingo_generator.rb +2 -1
  38. data/lib/generators/trek/install/slim_lint_generator.rb +4 -2
  39. data/lib/generators/trek/install/specs_generator.rb +3 -2
  40. data/lib/generators/trek/install/strip_attributes_generator.rb +4 -2
  41. data/lib/generators/trek/install/translations_generator.rb +3 -1
  42. data/lib/generators/trek/install/user_generator.rb +27 -1
  43. data/lib/generators/trek/install/yarn_generator.rb +8 -0
  44. data/lib/generators/trek/install_generator.rb +11 -4
  45. data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
  46. data/lib/generators/trek/templates/AGENTS.md +36 -0
  47. data/lib/generators/trek/templates/controllers/admin/base_controller.rb +1 -0
  48. data/lib/generators/trek/templates/controllers/admin/user_password_resets_controller.rb +26 -27
  49. data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +13 -20
  50. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +13 -0
  51. data/lib/generators/trek/templates/controllers/concerns/trek/authentication.rb +62 -0
  52. data/lib/generators/trek/templates/layouts/admin.html.slim +1 -1
  53. data/lib/generators/trek/templates/mailers/admin/passwords_mailer.rb +13 -0
  54. data/lib/generators/trek/templates/mailers/admin_mailer.rb +13 -0
  55. data/lib/generators/trek/templates/migrations/create_users.rb.erb +11 -0
  56. data/lib/generators/trek/templates/migrations/user_sessions.rb.erb +11 -0
  57. data/lib/generators/trek/templates/migrations/users.rb.erb +2 -0
  58. data/lib/generators/trek/templates/models/current.rb +3 -0
  59. data/lib/generators/trek/templates/models/user.rb +2 -0
  60. data/lib/generators/trek/templates/models/user_session.rb +3 -0
  61. data/lib/generators/trek/templates/views/admin/dashboards/index.html.slim +2 -1
  62. data/lib/generators/trek/templates/views/admin/pages/_form.html.slim +0 -14
  63. data/lib/generators/trek/templates/views/admin/passwords_mailer/reset.en.html.slim +10 -0
  64. data/lib/generators/trek/templates/views/{user_mailer/reset_password_email.en.text.erb → admin/passwords_mailer/reset.en.text.erb} +3 -1
  65. data/lib/generators/trek/templates/views/admin/passwords_mailer/reset.fr.html.slim +10 -0
  66. data/lib/generators/trek/templates/views/admin/passwords_mailer/reset.fr.text.erb +11 -0
  67. data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +7 -7
  68. data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +5 -2
  69. data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +12 -3
  70. data/lib/generators/trek/templates/views/admin/users/show.html.slim +1 -1
  71. data/lib/generators/trek/templates/views/admin_mailer/invite.en.html.slim +9 -0
  72. data/lib/generators/trek/templates/views/{user_mailer/invite_email.en.text.erb → admin_mailer/invite.en.text.erb} +3 -1
  73. data/lib/generators/trek/templates/views/admin_mailer/invite.fr.html.slim +9 -0
  74. data/lib/generators/trek/templates/views/{user_mailer/reset_password_email.fr.text.erb → admin_mailer/invite.fr.text.erb} +3 -1
  75. data/lib/trek/version.rb +1 -1
  76. data/package.json +3 -2
  77. data/yarn.lock +1807 -2486
  78. metadata +22 -12
  79. data/app/models/trek/user_password_reset.rb +0 -12
  80. data/lib/generators/trek/templates/mailers/user_mailer.rb +0 -21
  81. data/lib/generators/trek/templates/views/user_mailer/invite_email.en.html.slim +0 -29
  82. data/lib/generators/trek/templates/views/user_mailer/invite_email.fr.html.slim +0 -29
  83. data/lib/generators/trek/templates/views/user_mailer/invite_email.fr.text.erb +0 -9
  84. data/lib/generators/trek/templates/views/user_mailer/reset_password_email.en.html.slim +0 -7
  85. data/lib/generators/trek/templates/views/user_mailer/reset_password_email.fr.html.slim +0 -7
@@ -6,13 +6,82 @@ module Trek
6
6
  module Generators
7
7
  module Install
8
8
  class AuthenticationGenerator < Rails::Generators::Base
9
+ USER_MODEL_CLASS_NAME = "User".freeze
10
+
11
+ include Rails::Generators::Migration
9
12
  include Trek::Generators::Helpers
10
13
 
11
14
  desc "Setup authentication."
12
15
 
13
16
  source_root File.expand_path("../templates", __dir__)
14
17
 
15
- # TODO
18
+ def add_bcrypt_gem
19
+ return if in_gemfile?("bcrypt")
20
+
21
+ gem "bcrypt", "~> 3.1.7"
22
+ bundle_install
23
+ end
24
+
25
+ def copy_admin_authentication_concern
26
+ template "controllers/concerns/trek/authentication.rb",
27
+ "app/controllers/concerns/trek/authentication.rb"
28
+ end
29
+
30
+ def copy_user_session_model
31
+ template "models/user_session.rb",
32
+ "app/models/user_session.rb"
33
+ end
34
+
35
+ def copy_user_sessions_migration
36
+ return unless defined?(ActiveRecord)
37
+
38
+ migration_template("migrations/user_sessions.rb.erb",
39
+ "db/migrate/trek_user_sessions.rb",
40
+ migration_class_name:)
41
+ end
42
+
43
+ def inject_authentication_into_user_model
44
+ inject_into_class user_model_path,
45
+ USER_MODEL_CLASS_NAME,
46
+ user_model_injection
47
+ end
48
+
49
+ def add_admin_auth_routes
50
+ route "resource :user_session, only: %i[new create destroy]", namespace: :admin
51
+ route "resources :user_password_resets, param: :token, only: %i[new create edit update]", namespace: :admin
52
+ route 'get :login, to: "user_sessions#new", as: :login', namespace: :admin
53
+ route 'delete :logout, to: "user_sessions#destroy", as: :logout', namespace: :admin
54
+ end
55
+
56
+ def autocorrect_user_model
57
+ run "rubocop --autocorrect #{user_model_path}"
58
+ end
59
+
60
+ private
61
+
62
+ def user_model_path
63
+ @user_model_path ||= File.join("app", "models", "#{USER_MODEL_CLASS_NAME.underscore}.rb")
64
+ end
65
+
66
+ def user_model_injection
67
+ <<-RUBY
68
+ has_secure_password
69
+ has_many :user_sessions, dependent: :destroy
70
+
71
+ # Both tokens invalidate once the password changes (password_salt). Invites
72
+ # get a longer window than a password reset, so they are a distinct purpose.
73
+ generates_token_for :password_reset, expires_in: 15.minutes do
74
+ password_salt&.last(10)
75
+ end
76
+
77
+ generates_token_for :invite, expires_in: 1.week do
78
+ password_salt&.last(10)
79
+ end
80
+
81
+ scope :privileged, -> { where(role: Trek::Users::Roles::PRIVILEGED_ROLES) }
82
+
83
+ RUBY
84
+ end
16
85
  end
17
86
  end
18
87
  end
@@ -13,8 +13,10 @@ module Trek
13
13
  source_root File.expand_path("../templates", __dir__)
14
14
 
15
15
  def add_gem
16
- gem "action_policy" unless in_gemfile?("action_policy")
17
- run "bundle install --quiet", abort_on_failure: true
16
+ return if in_gemfile?("action_policy")
17
+
18
+ gem "action_policy"
19
+ bundle_install
18
20
  end
19
21
 
20
22
  def run_install
@@ -12,8 +12,10 @@ module Trek
12
12
  source_root File.expand_path("../../../../", __dir__)
13
13
 
14
14
  def add_gem
15
- gem "brakeman", group: [:development, :test] unless in_gemfile?("brakeman")
16
- run "bundle install --quiet", abort_on_failure: true
15
+ return if in_gemfile?("brakeman")
16
+
17
+ gem "brakeman", group: [:development, :test]
18
+ bundle_install
17
19
  end
18
20
  end
19
21
  end
@@ -12,8 +12,10 @@ module Trek
12
12
  source_root File.expand_path("../../../../", __dir__)
13
13
 
14
14
  def add_gem
15
- gem "bundler-audit", group: [:development, :test] unless in_gemfile?("bundler-audit")
16
- run "bundle install --quiet", abort_on_failure: true
15
+ return if in_gemfile?("bundler-audit")
16
+
17
+ gem "bundler-audit", group: [:development, :test]
18
+ bundle_install
17
19
  end
18
20
  end
19
21
  end
@@ -22,6 +22,10 @@ module Trek
22
22
  Rails.application.routes.default_url_options[:protocol] = Rails.application.config.force_ssl ? "https" : "http"
23
23
  Rails.application.routes.default_url_options[:host] = ENV.fetch("DEFAULT_HOSTNAME", "localhost:3000")
24
24
  Rails.application.config.canonical_url = "\#{Rails.application.routes.default_url_options[:protocol]}://\#{Rails.application.routes.default_url_options[:host]}"
25
+
26
+ # Mailers run outside a request, so in production they need the same host for
27
+ # link helpers. Development and test keep the host from their environment files.
28
+ ActionMailer::Base.default_url_options = Rails.application.routes.default_url_options if Rails.env.production?
25
29
  RUBY
26
30
  end
27
31
  end
@@ -12,8 +12,10 @@ module Trek
12
12
  source_root File.expand_path("../templates", __dir__)
13
13
 
14
14
  def add_gem
15
- gem "rails-healthcheck" unless in_gemfile?("rails-healthcheck")
16
- run "bundle install --quiet", abort_on_failure: true
15
+ return if in_gemfile?("rails-healthcheck")
16
+
17
+ gem "rails-healthcheck"
18
+ bundle_install
17
19
  end
18
20
 
19
21
  def add_initializer
@@ -10,8 +10,10 @@ module Trek
10
10
  desc "Install Kaminari."
11
11
 
12
12
  def add_gem
13
- gem "kaminari" unless in_gemfile?("kaminari")
14
- run "bundle install --quiet", abort_on_failure: true
13
+ return if in_gemfile?("kaminari")
14
+
15
+ gem "kaminari"
16
+ bundle_install
15
17
  end
16
18
  end
17
19
  end
@@ -12,16 +12,19 @@ module Trek
12
12
 
13
13
  source_root File.expand_path("../templates", __dir__)
14
14
 
15
- def ask_locales
16
- loop do
17
- @locales = ENV["TREK_LOCALES"] || ask("What locales do you want to use? (e.g. en, fr)")
15
+ class_option :locales, type: :string,
16
+ desc: "Comma-separated locales to enable, e.g. en,fr (default: en)"
18
17
 
19
- break if valid_locales?(@locales)
18
+ def set_locales
19
+ raw = options[:locales] || ENV["TREK_LOCALES"] || "en"
20
+ @locales = raw.split(",").map(&:strip)
20
21
 
21
- Rails.logger.debug "Invalid locales entered."
22
- end
22
+ invalid = @locales.reject { |locale| valid_locales?(locale) }
23
+ return if invalid.empty?
23
24
 
24
- @locales = @locales.split(",").map(&:strip)
25
+ say "Ignoring unavailable locales: #{invalid.join(", ")}", :yellow
26
+ @locales -= invalid
27
+ @locales = ["en"] if @locales.empty?
25
28
  end
26
29
 
27
30
  def create_locales_initializer
@@ -13,11 +13,13 @@ module Trek
13
13
  source_root File.expand_path("../../../../", __dir__)
14
14
 
15
15
  def add_gem
16
+ install_needed = %w[lookbook listen actioncable].any? { |name| !in_gemfile?(name) }
17
+
16
18
  gem "lookbook", ">= 2.3.13", group: :development unless in_gemfile?("lookbook")
17
19
  gem "listen", group: :development unless in_gemfile?("listen")
18
20
  gem "actioncable", group: :development unless in_gemfile?("actioncable")
19
21
 
20
- run "bundle install --quiet", abort_on_failure: true
22
+ bundle_install if install_needed
21
23
  end
22
24
  end
23
25
  end
@@ -19,25 +19,30 @@ module Trek
19
19
  "app/views/layouts/mailer.html.slim"
20
20
  end
21
21
 
22
- def user_mailer
23
- template "mailers/user_mailer.rb",
24
- "app/mailers/user_mailer.rb"
22
+ def admin_mailer
23
+ template "mailers/admin_mailer.rb",
24
+ "app/mailers/admin_mailer.rb"
25
25
  end
26
26
 
27
- def copy_views_invite_email_user_mailer
27
+ def passwords_mailer
28
+ template "mailers/admin/passwords_mailer.rb",
29
+ "app/mailers/admin/passwords_mailer.rb"
30
+ end
31
+
32
+ def copy_views_admin_mailer_invite
28
33
  extensions.each do |extension|
29
34
  locales.each do |lang|
30
- template "views/user_mailer/invite_email.#{lang}#{extension}",
31
- "app/views/user_mailer/invite_email.#{lang}#{extension}"
35
+ template "views/admin_mailer/invite.#{lang}#{extension}",
36
+ "app/views/admin_mailer/invite.#{lang}#{extension}"
32
37
  end
33
38
  end
34
39
  end
35
40
 
36
- def copy_views_reset_password_user_mailer
41
+ def copy_views_passwords_mailer_reset
37
42
  extensions.each do |extension|
38
43
  locales.each do |lang|
39
- template "views/user_mailer/reset_password_email.#{lang}#{extension}",
40
- "app/views/user_mailer/reset_password_email.#{lang}#{extension}"
44
+ template "views/admin/passwords_mailer/reset.#{lang}#{extension}",
45
+ "app/views/admin/passwords_mailer/reset.#{lang}#{extension}"
41
46
  end
42
47
  end
43
48
  end
@@ -55,8 +55,10 @@ module Trek
55
55
  end
56
56
 
57
57
  def install_closure_tree
58
- gem "closure_tree", "~> 9.0" unless in_gemfile?("closure_tree")
59
- run "bundle install --quiet", abort_on_failure: true
58
+ unless in_gemfile?("closure_tree")
59
+ gem "closure_tree", "~> 9.0"
60
+ bundle_install
61
+ end
60
62
  rails_command "db:migrate" # `pages` table must exist
61
63
  generate "closure_tree:migration Page"
62
64
  end
@@ -13,8 +13,10 @@ module Trek
13
13
  source_root File.expand_path("../templates", __dir__)
14
14
 
15
15
  def add_gem
16
- gem "postmark-rails" unless in_gemfile?("postmark-rails")
17
- run "bundle install --quiet", abort_on_failure: true
16
+ return if in_gemfile?("postmark-rails")
17
+
18
+ gem "postmark-rails"
19
+ bundle_install
18
20
  end
19
21
 
20
22
  def inject_postmark_to_application_mailer
@@ -28,7 +30,7 @@ module Trek
28
30
  def inject_postmark_to_config
29
31
  inject_into_file "config/environments/production.rb",
30
32
  config_injection,
31
- after: "# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n"
33
+ after: %r{# Specify outgoing SMTP server\. Remember to add smtp/\* credentials via (?:bin/)?rails credentials:edit\.\n}
32
34
  run "rubocop --autocorrect config/environments/production.rb", abort_on_failure: true
33
35
  end
34
36
 
@@ -12,8 +12,10 @@ module Trek
12
12
  source_root File.expand_path("../../../../", __dir__)
13
13
 
14
14
  def add_gem
15
- gem "rubocop-rspec", group: [:development, :test], require: false unless in_gemfile?("rubocop-rspec")
16
- run "bundle install --quiet", abort_on_failure: true
15
+ return if in_gemfile?("rubocop-rspec")
16
+
17
+ gem "rubocop-rspec", group: [:development, :test], require: false
18
+ bundle_install
17
19
  end
18
20
 
19
21
  def copy_rubocop_config
@@ -21,8 +21,9 @@ module Trek
21
21
  def create_aptfile
22
22
  create_file "Aptfile" do
23
23
  <<~TEXT
24
- libwebp-dev
24
+ libjemalloc2
25
25
  libvips-tools --no-install-recommends
26
+ libwebp-dev
26
27
  TEXT
27
28
  end
28
29
  end
@@ -12,8 +12,10 @@ module Trek
12
12
  source_root File.expand_path("../../../../", __dir__)
13
13
 
14
14
  def add_gem
15
- gem "slim_lint", group: [:development, :test] unless in_gemfile?("slim_lint")
16
- run "bundle install --quiet", abort_on_failure: true
15
+ return if in_gemfile?("slim_lint")
16
+
17
+ gem "slim_lint", group: [:development, :test]
18
+ bundle_install
17
19
  end
18
20
 
19
21
  def copy_slim_lint_config
@@ -13,13 +13,14 @@ module Trek
13
13
  source_root File.expand_path("../templates", __dir__)
14
14
 
15
15
  def add_gems
16
+ install_needed = %w[rspec-rails capybara rspec_junit_formatter rspec-html-matchers simplecov].any? { |name| !in_gemfile?(name) }
17
+
16
18
  gem "rspec-rails", "~> 8.0", group: [:development, :test] unless in_gemfile?("rspec-rails")
17
19
  gem "capybara", "~> 3.38", group: [:test] unless in_gemfile?("capybara")
18
20
  gem "rspec_junit_formatter", "~> 0.6.0", group: [:test] unless in_gemfile?("rspec_junit_formatter")
19
21
  gem "rspec-html-matchers", "~> 0.10.0", group: [:test] unless in_gemfile?("rspec-html-matchers")
20
22
  gem "simplecov", "~> 0.22.0", group: [:test] unless in_gemfile?("simplecov")
21
-
22
- run "bundle install --quiet", abort_on_failure: true
23
+ bundle_install if install_needed
23
24
  end
24
25
 
25
26
  def run_install
@@ -12,8 +12,10 @@ module Trek
12
12
  source_root File.expand_path("../templates", __dir__)
13
13
 
14
14
  def add_gem
15
- gem "strip_attributes" unless in_gemfile?("strip_attributes")
16
- run "bundle install --quiet", abort_on_failure: true
15
+ return if in_gemfile?("strip_attributes")
16
+
17
+ gem "strip_attributes"
18
+ bundle_install
17
19
  end
18
20
  end
19
21
  end
@@ -13,11 +13,13 @@ module Trek
13
13
  source_root File.expand_path("../templates", __dir__)
14
14
 
15
15
  def add_gem
16
+ install_needed = %w[rails-i18n i18n-tasks deepl-rb mobility].any? { |name| !in_gemfile?(name) }
17
+
16
18
  gem "rails-i18n", ">= 6.0.0" unless in_gemfile?("rails-i18n")
17
19
  gem "i18n-tasks", "~> 1.0" unless in_gemfile?("i18n-tasks")
18
20
  gem "deepl-rb", "~> 3.0" unless in_gemfile?("deepl-rb")
19
21
  gem "mobility", "~> 1.3.2" unless in_gemfile?("mobility")
20
- run "bundle install --quiet", abort_on_failure: true
22
+ bundle_install if install_needed
21
23
  end
22
24
 
23
25
  def run_install
@@ -15,11 +15,25 @@ module Trek
15
15
 
16
16
  source_root File.expand_path("../templates", __dir__)
17
17
 
18
+ def create_user_model
19
+ return if user_model_existed?
20
+
21
+ template "models/user.rb", user_model_path
22
+ end
23
+
24
+ def create_users_migration
25
+ return unless defined?(ActiveRecord)
26
+ return if user_model_existed?
27
+
28
+ migration_template("migrations/create_users.rb.erb",
29
+ "db/migrate/trek_create_users.rb",
30
+ migration_class_name:)
31
+ end
32
+
18
33
  def inject_trek_to_user_model
19
34
  inject_into_class user_model_path,
20
35
  USER_MODEL_CLASS_NAME,
21
36
  user_model_injection
22
- run "rubocop --autocorrect #{user_model_path}", abort_on_failure: true
23
37
  end
24
38
 
25
39
  def copy_migration_files
@@ -30,12 +44,24 @@ module Trek
30
44
  migration_class_name:)
31
45
  end
32
46
 
47
+ def autocorrect_user_model
48
+ run "rubocop --autocorrect #{user_model_path}"
49
+ end
50
+
33
51
  private
34
52
 
35
53
  def tableized_model_class
36
54
  USER_MODEL_CLASS_NAME.gsub("::", "").tableize
37
55
  end
38
56
 
57
+ # Memoizes whether the User model existed *before* this generator ran, so the
58
+ # value stays stable after `create_user_model` writes the file.
59
+ def user_model_existed?
60
+ return @user_model_existed unless @user_model_existed.nil?
61
+
62
+ @user_model_existed = File.exist?(user_model_path)
63
+ end
64
+
39
65
  def user_model_path
40
66
  @user_model_path ||= File.join("app", "models", "#{user_file_path}.rb")
41
67
  end
@@ -45,6 +45,14 @@ module Trek
45
45
  run "yarn plugin import interactive-tools"
46
46
  end
47
47
 
48
+ # The default bin/setup (generated by `rails new -j esbuild`) runs
49
+ # `yarn install --check-files`, but `--check-files` is not a valid flag
50
+ # under Yarn berry (the version we pin above), so it breaks `bin/setup`
51
+ # in CI. Drop the flag.
52
+ def fix_bin_setup
53
+ gsub_file "bin/setup", "yarn install --check-files", "yarn install"
54
+ end
55
+
48
56
  private
49
57
 
50
58
  def yarnrc_content
@@ -52,6 +52,9 @@ module Trek
52
52
  <<~GITIGNORE
53
53
 
54
54
  .DS_Store
55
+
56
+ # Keep the .env template tracked (Rails ignores all /.env* files).
57
+ !/.env.example
55
58
  GITIGNORE
56
59
  end
57
60
 
@@ -72,6 +75,10 @@ module Trek
72
75
  generate "trek:install:mailer"
73
76
  end
74
77
 
78
+ def install_user
79
+ generate "trek:install:user"
80
+ end
81
+
75
82
  def install_authentication
76
83
  generate "trek:install:authentication"
77
84
  end
@@ -84,10 +91,6 @@ module Trek
84
91
  generate "trek:install:healthcheck"
85
92
  end
86
93
 
87
- def install_user
88
- generate "trek:install:user"
89
- end
90
-
91
94
  def install_translations
92
95
  generate "trek:install:translations"
93
96
  end
@@ -121,6 +124,10 @@ module Trek
121
124
  generate "trek:install:readme"
122
125
  end
123
126
 
127
+ def install_agents
128
+ generate "trek:install:agents"
129
+ end
130
+
124
131
  def install_admin
125
132
  generate "trek:install:admin"
126
133
  generate "trek:install:admin:routes"
@@ -3,7 +3,7 @@
3
3
 
4
4
  # Variables
5
5
  variables:
6
- POSTGRES_DB: "project_test"
6
+ POSTGRES_DB: "<%= application_name %>_test"
7
7
  POSTGRES_HOST_AUTH_METHOD: "trust"
8
8
  DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
9
9
  RAILS_ENV: "test"
@@ -0,0 +1,36 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents working in this repository.
4
+
5
+ ## About this project
6
+
7
+ This is a Ruby on Rails application whose back-office/admin is built with **Trek**, a Rails CMS engine. Trek was installed via `rails g trek:install` and provides the admin UI, authentication, authorization, attachments, and base content models. Project-specific work lives in `app/` — admin controllers under `app/controllers/admin/`, plus your own models, policies, and components. Prefer Trek's generators (`rails g trek:scaffold`, `rails g trek:admin:user`, …) over hand-writing admin plumbing.
8
+
9
+ ## Commands
10
+
11
+ make setup # install dependencies (yarn + bin/setup)
12
+ make dev # start the server (overmind)
13
+ make admin # create an admin user
14
+ make seed # seed the database
15
+ make test # run the RSpec suite (alias: make spec)
16
+ make lint # rubocop + slim-lint + eslint + stylelint + prettier
17
+ make fix # auto-correct lint/format issues
18
+ make scan # brakeman + bundle-audit
19
+
20
+ ## Conventions
21
+
22
+ - **Ruby**: RuboCop with rubocop-rails-omakase — double-quoted strings, 120-col lines.
23
+ - **Views**: Slim templates + ViewComponents (linted with slim-lint).
24
+ - **Assets**: esbuild + PostCSS; Stimulus/Turbo (Hotwired).
25
+ - **i18n**: keep every locale file in `config/locales/` in sync across languages.
26
+ - **Git hooks**: lefthook auto-formats staged files on pre-commit — let it run.
27
+
28
+ ## Upgrading Trek
29
+
30
+ **Whenever you bump the `trek` gem — and its companion `@etaminstudio/trek` npm package — always:**
31
+
32
+ 1. **Read Trek's `CHANGELOG.md`** for every version between the installed and target version, not just the latest.
33
+ 2. **Apply every "Upgrade note".** These are the manual steps host apps must perform (config tweaks, new initializers, regenerated files, data migrations). Do not skip them — the upgrade is not complete until each is applied.
34
+ 3. **Remove now-redundant local code.** Where the changelog says a Trek change lets host apps drop or simplify a local patch, monkey-patch, or custom implementation, delete that code as part of the upgrade.
35
+ 4. **Keep versions aligned**: the `trek` gem version and the `@etaminstudio/trek` npm version must match.
36
+ 5. **Verify**: run `make lint` and `make test` before committing the upgrade.
@@ -6,6 +6,7 @@ module Admin
6
6
 
7
7
  helper Trek::I18nHelper
8
8
  helper Trek::ThemeHelper
9
+ helper ApplicationHelper
9
10
 
10
11
  layout "admin"
11
12
 
@@ -2,9 +2,10 @@ module Admin
2
2
  class UserPasswordResetsController < ApplicationController
3
3
  include Trek::Authentication
4
4
 
5
- skip_before_action :require_login
6
- before_action :set_user, only: %i[edit update]
7
- before_action :redirect_if_logged_in
5
+ allow_unauthenticated_access
6
+ before_action :set_token, only: %i[edit update]
7
+ before_action :set_user_by_token, only: %i[edit update]
8
+ rate_limit to: 5, within: 3.minutes, only: :create, with: -> { redirect_to new_admin_user_session_path, alert: t("notices.rate_limited") }
8
9
 
9
10
  helper Trek::I18nHelper
10
11
  helper Trek::ThemeHelper
@@ -14,44 +15,42 @@ module Admin
14
15
  default_form_builder Trek::FormBuilder
15
16
 
16
17
  def new
17
- @user_password_reset = Trek::UserPasswordReset.new
18
- end
19
-
20
- def edit
21
18
  end
22
19
 
23
20
  def create
24
- @user_password_reset = Trek::UserPasswordReset.new(user_password_reset_params)
21
+ if user = User.privileged.find_by(email: params[:email])
22
+ Admin::PasswordsMailer.reset(user).deliver_later
23
+ end
25
24
 
26
- @user = @user_password_reset.user
27
- @user&.deliver_reset_password_instructions!
25
+ redirect_to new_admin_user_session_path, notice: t(".success")
26
+ end
28
27
 
29
- redirect_to(%i[admin login], notice: t(".success"))
28
+ def edit
30
29
  end
31
30
 
32
31
  def update
33
- if @user.change_password(params[:user][:new_password])
34
- redirect_to(%i[admin login], notice: t(".success"))
35
- else
36
- render action: "edit"
37
- end
32
+ @user.update!(params.permit(:password))
33
+ @user.user_sessions.destroy_all
34
+ redirect_to new_admin_user_session_path, notice: t(".success")
35
+ rescue ActiveRecord::RecordInvalid
36
+ redirect_to edit_admin_user_password_reset_path(@token), alert: @user.errors.full_messages.join(", ")
38
37
  end
39
38
 
40
39
  private
41
40
 
42
- def set_user
43
- @token = params[:id]
44
- @user = User.load_from_reset_password_token(params[:id])
45
-
46
- not_authenticated if @user.blank?
47
- end
48
-
49
- def user_password_reset_params
50
- params.require(:user_password_reset).permit(:email)
41
+ def set_token
42
+ @token = params[:token]
51
43
  end
52
44
 
53
- def redirect_if_logged_in
54
- redirect_to %i[admin root] if logged_in?
45
+ # The same edit/update flow is reached from a password-reset email and from
46
+ # an invitation email, which carry different token purposes (and expiries).
47
+ def set_user_by_token
48
+ @user = User.privileged.find_by_token_for(:invite, @token) ||
49
+ User.privileged.find_by_token_for!(:password_reset, @token)
50
+ rescue ActiveRecord::RecordNotFound
51
+ redirect_to new_admin_user_session_path, alert: t("admin.user_password_resets.not_found")
52
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
53
+ redirect_to new_admin_user_password_reset_path, alert: t("admin.user_password_resets.not_found")
55
54
  end
56
55
  end
57
56
  end