trek 2.5.0 → 2.6.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.node-version +1 -1
  3. data/AGENTS.md +7 -4
  4. data/CHANGELOG.md +37 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +16 -3
  7. data/Makefile +2 -0
  8. data/app/components/trek/dashboard/tile_component/tile_component.css +9 -0
  9. data/app/components/trek/dashboard/tile_component/tile_component.html.slim +4 -2
  10. data/app/components/trek/dashboard/tile_component.rb +12 -2
  11. data/app/components/trek/form/content_editor_component/content_editor_component.css +5 -0
  12. data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +19 -93
  13. data/app/components/trek/form/content_editor_component/content_editor_component.js +94 -251
  14. data/app/components/trek/form/content_editor_component/extensions/block_view.js +40 -0
  15. data/app/components/trek/form/content_editor_component/extensions/image_block.js +28 -26
  16. data/app/components/trek/form/content_editor_component/extensions/prompts_block.js +15 -26
  17. data/app/components/trek/form/content_editor_component/extensions/table_of_contents.js +1 -3
  18. data/app/components/trek/form/content_editor_component.rb +15 -0
  19. data/app/components/trek/form/image_field_component/image_field_component.css +40 -1
  20. data/app/components/trek/form/image_field_component/image_field_component.html.slim +0 -9
  21. data/app/components/trek/form/image_field_component.rb +36 -9
  22. data/app/content/trek/content/cleaner.rb +53 -0
  23. data/app/content/trek/content/document.rb +2 -2
  24. data/app/content/trek/content/prompts_block.rb +1 -2
  25. data/app/content/trek/content.rb +6 -0
  26. data/app/controllers/concerns/trek/reorderable.rb +24 -6
  27. data/app/controllers/trek/csp_reports_controller.rb +14 -1
  28. data/app/controllers/trek/panels/prompts_controller.rb +1 -1
  29. data/app/controllers/trek/resource_controller.rb +5 -5
  30. data/app/jobs/trek/purge_expired_sessions_job.rb +4 -3
  31. data/app/models/concerns/trek/contentable.rb +15 -1
  32. data/app/models/concerns/trek/users/sign_in_code.rb +70 -0
  33. data/app/models/concerns/trek/users/sign_in_lockout.rb +4 -2
  34. data/app/views/trek/panels/links/_form.html.slim +2 -4
  35. data/app/views/trek/panels/prompts/create.turbo_stream.slim +0 -1
  36. data/config/locales/trek.en.yml +26 -11
  37. data/config/locales/trek.fr.yml +26 -11
  38. data/docs/.vitepress/config.mjs +4 -1
  39. data/docs/background-jobs.md +69 -0
  40. data/docs/content-editor.md +219 -0
  41. data/docs/customize.md +2 -0
  42. data/docs/deploy.md +2 -2
  43. data/docs/getting-started.md +1 -1
  44. data/docs/index.md +1 -1
  45. data/docs/reference/components.md +2 -2
  46. data/docs/reference/concerns/contentable.md +10 -4
  47. data/docs/reference/concerns/formattable.md +1 -1
  48. data/docs/reference/environment-variables.md +1 -0
  49. data/docs/reference/formatters.md +2 -30
  50. data/docs/reference/forms.md +1 -1
  51. data/docs/reference/generators/audit-log.md +2 -2
  52. data/docs/reference/generators/install.md +3 -2
  53. data/docs/reference/generators/public-accounts.md +141 -0
  54. data/docs/reference/generators/security-headers.md +1 -1
  55. data/docs/reference/index.md +2 -1
  56. data/docs/reference/models/user.md +3 -1
  57. data/docs/what-is-trek.md +1 -1
  58. data/lib/generators/trek/audit_log_generator.rb +54 -0
  59. data/lib/generators/trek/install/admin/users_generator.rb +6 -1
  60. data/lib/generators/trek/install/audit_log_generator.rb +9 -45
  61. data/lib/generators/trek/install/good_job_generator.rb +161 -0
  62. data/lib/generators/trek/install/model_specs_generator.rb +3 -3
  63. data/lib/generators/trek/install/ruby_generator.rb +1 -1
  64. data/lib/generators/trek/install/scalingo_generator.rb +7 -45
  65. data/lib/generators/trek/install_generator.rb +15 -0
  66. data/lib/generators/trek/public_accounts_generator.rb +267 -0
  67. data/lib/generators/trek/scalingo_generator.rb +55 -0
  68. data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
  69. data/lib/generators/trek/templates/controllers/admin/password_renewals_controller.rb +11 -0
  70. data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +1 -1
  71. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +0 -8
  72. data/lib/generators/trek/templates/initializers/content_security_policy.rb +3 -2
  73. data/lib/generators/trek/templates/initializers/good_job.rb.tt +38 -0
  74. data/lib/generators/trek/templates/jobs/mail_delivery_job.rb +6 -0
  75. data/lib/generators/trek/templates/layouts/admin.html.slim +5 -0
  76. data/lib/generators/trek/templates/mailers/admin_mailer.rb +4 -2
  77. data/lib/generators/trek/templates/package.json.tt +1 -1
  78. data/lib/generators/trek/templates/public_accounts/api/authentication.rb.tt +76 -0
  79. data/lib/generators/trek/templates/public_accounts/api/controllers/accounts_controller.rb.tt +22 -0
  80. data/lib/generators/trek/templates/public_accounts/api/controllers/active_sessions_controller.rb.tt +21 -0
  81. data/lib/generators/trek/templates/public_accounts/api/controllers/base_controller.rb.tt +71 -0
  82. data/lib/generators/trek/templates/public_accounts/api/controllers/email_changes_controller.rb.tt +55 -0
  83. data/lib/generators/trek/templates/public_accounts/api/controllers/email_confirmations_controller.rb.tt +19 -0
  84. data/lib/generators/trek/templates/public_accounts/api/controllers/password_resets_controller.rb.tt +28 -0
  85. data/lib/generators/trek/templates/public_accounts/api/controllers/passwords_controller.rb.tt +17 -0
  86. data/lib/generators/trek/templates/public_accounts/api/controllers/registrations_controller.rb.tt +17 -0
  87. data/lib/generators/trek/templates/public_accounts/api/controllers/sessions_controller.rb.tt +45 -0
  88. data/lib/generators/trek/templates/public_accounts/api/controllers/sign_in_codes_controller.rb.tt +15 -0
  89. data/lib/generators/trek/templates/public_accounts/jobs/sign_in_code_job.rb.tt +13 -0
  90. data/lib/generators/trek/templates/public_accounts/locales/accounts.en.yml.tt +205 -0
  91. data/lib/generators/trek/templates/public_accounts/locales/accounts.fr.yml.tt +205 -0
  92. data/lib/generators/trek/templates/public_accounts/mailers/account_mailer.rb.tt +85 -0
  93. data/lib/generators/trek/templates/public_accounts/migrations/create_accounts.rb.tt +46 -0
  94. data/lib/generators/trek/templates/public_accounts/models/account/session.rb.tt +47 -0
  95. data/lib/generators/trek/templates/public_accounts/models/account/sign_in_code.rb.tt +8 -0
  96. data/lib/generators/trek/templates/public_accounts/models/account.rb.tt +73 -0
  97. data/lib/generators/trek/templates/public_accounts/spec/api/accounts_spec.rb.tt +90 -0
  98. data/lib/generators/trek/templates/public_accounts/spec/api/sign_ins_spec.rb.tt +158 -0
  99. data/lib/generators/trek/templates/public_accounts/spec/models/account/sign_in_code_spec.rb.tt +56 -0
  100. data/lib/generators/trek/templates/public_accounts/spec/models/account_spec.rb.tt +109 -0
  101. data/lib/generators/trek/templates/public_accounts/spec/web/accounts_spec.rb.tt +126 -0
  102. data/lib/generators/trek/templates/public_accounts/spec/web/authentication_spec.rb.tt +43 -0
  103. data/lib/generators/trek/templates/public_accounts/spec/web/sign_ins_spec.rb.tt +215 -0
  104. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.html.slim.tt +10 -0
  105. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.text.erb.tt +8 -0
  106. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.html.slim.tt +13 -0
  107. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.text.erb.tt +8 -0
  108. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.html.slim.tt +9 -0
  109. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.text.erb.tt +11 -0
  110. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.html.slim.tt +11 -0
  111. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.text.erb.tt +11 -0
  112. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.html.slim.tt +10 -0
  113. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.text.erb.tt +8 -0
  114. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.html.slim.tt +10 -0
  115. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.text.erb.tt +8 -0
  116. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.html.slim.tt +10 -0
  117. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.text.erb.tt +8 -0
  118. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.html.slim.tt +13 -0
  119. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.text.erb.tt +8 -0
  120. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.html.slim.tt +9 -0
  121. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.text.erb.tt +11 -0
  122. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.html.slim.tt +9 -0
  123. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.text.erb.tt +11 -0
  124. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.html.slim.tt +23 -0
  125. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.text.erb.tt +14 -0
  126. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.html.slim.tt +24 -0
  127. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.text.erb.tt +14 -0
  128. data/lib/generators/trek/templates/public_accounts/web/authentication.rb.tt +97 -0
  129. data/lib/generators/trek/templates/public_accounts/web/controllers/accounts_controller.rb.tt +33 -0
  130. data/lib/generators/trek/templates/public_accounts/web/controllers/active_sessions_controller.rb.tt +15 -0
  131. data/lib/generators/trek/templates/public_accounts/web/controllers/base_controller.rb.tt +52 -0
  132. data/lib/generators/trek/templates/public_accounts/web/controllers/email_changes_controller.rb.tt +85 -0
  133. data/lib/generators/trek/templates/public_accounts/web/controllers/email_confirmations_controller.rb.tt +19 -0
  134. data/lib/generators/trek/templates/public_accounts/web/controllers/password_resets_controller.rb.tt +41 -0
  135. data/lib/generators/trek/templates/public_accounts/web/controllers/passwords_controller.rb.tt +23 -0
  136. data/lib/generators/trek/templates/public_accounts/web/controllers/registrations_controller.rb.tt +24 -0
  137. data/lib/generators/trek/templates/public_accounts/web/controllers/sessions_controller.rb.tt +78 -0
  138. data/lib/generators/trek/templates/public_accounts/web/controllers/sign_in_codes_controller.rb.tt +27 -0
  139. data/lib/generators/trek/templates/public_accounts/web/views/_flash.html.slim.tt +5 -0
  140. data/lib/generators/trek/templates/public_accounts/web/views/accounts/show.html.slim.tt +52 -0
  141. data/lib/generators/trek/templates/public_accounts/web/views/email_changes/edit.html.slim.tt +17 -0
  142. data/lib/generators/trek/templates/public_accounts/web/views/email_changes/new.html.slim.tt +15 -0
  143. data/lib/generators/trek/templates/public_accounts/web/views/password_resets/edit.html.slim.tt +26 -0
  144. data/lib/generators/trek/templates/public_accounts/web/views/password_resets/new.html.slim.tt +15 -0
  145. data/lib/generators/trek/templates/public_accounts/web/views/passwords/edit.html.slim.tt +25 -0
  146. data/lib/generators/trek/templates/public_accounts/web/views/registrations/new.html.slim.tt +28 -0
  147. data/lib/generators/trek/templates/public_accounts/web/views/sessions/new.html.slim.tt +38 -0
  148. data/lib/generators/trek/templates/public_accounts/web/views/sign_in_codes/new.html.slim.tt +17 -0
  149. data/lib/generators/trek/templates/spec/jobs/mail_delivery_job_spec.rb +11 -0
  150. data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +10 -0
  151. data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +1 -1
  152. data/lib/generators/trek/templates/spec/requests/admin/good_job_spec.rb +34 -0
  153. data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +1 -1
  154. data/lib/generators/trek/templates/spec/requests/admin/panels_spec.rb +16 -0
  155. data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +2 -2
  156. data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +1 -1
  157. data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +2 -2
  158. data/lib/generators/trek/templates/views/admin/users/index.html.slim +1 -1
  159. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +1 -1
  160. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +1 -1
  161. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +1 -1
  162. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +1 -1
  163. data/lib/trek/csp_reports.rb +22 -4
  164. data/lib/trek/typography_formatter.rb +35 -4
  165. data/lib/trek/version.rb +1 -1
  166. data/package.json +12 -8
  167. data/trek.gemspec +1 -1
  168. data/vitest.config.mjs +24 -0
  169. data/yarn.lock +1004 -288
  170. metadata +90 -3
@@ -0,0 +1,141 @@
1
+ # Public accounts
2
+
3
+ ```sh
4
+ rails g trek:public_accounts Customer [--sign-in=code|password] [--api] [--path=customers] [--model-only]
5
+ bin/rails db:migrate
6
+ ```
7
+
8
+ Accounts for the people using the site or the mobile app (customers, members, readers…), apart from the admin's [`User`](/reference/models/user). They have their own model, table, sessions and sign-in, so nothing public can reach a staff account: signing up, deleting inactive accounts or merging duplicates only ever sees public accounts, and a public session never opens the admin.
9
+
10
+ The name is required, and is the app's own: `User` is the admin's account, so the generator refuses it, a namespaced name, and the names of Trek's models. Pick the word of the app's domain (`Customer`, `Member`, `Reader`…).
11
+
12
+ | Option | Effect |
13
+ | --- | --- |
14
+ | `--sign-in=code` (default) | Accounts sign in with a code emailed to them, which creates the account the first time: there is no password, nor sign-up form |
15
+ | `--sign-in=password` | Accounts sign up with an email and a password, confirm the email by a link, and reset a forgotten password by one: there are no codes |
16
+ | `--api` | A mobile app: JSON endpoints under `/api/v1`, and a session held through a JWT, with no pages. Without it, a website: pages, and a session kept in a cookie |
17
+ | `--path=customers` | Puts the routes under a path, e.g. `/customers/sign-in`: for an app with two kinds of public accounts |
18
+ | `--model-only` | Neither pages nor endpoints: see [Model only](#model-only). Can't be combined with `--api` or `--path` |
19
+
20
+ ## Model only
21
+
22
+ With `--model-only`, the generator adds the models, their migration, `Current.customer_session` and `Current.customer`, the mailer, its job for codes, the locales and the model specs, but no controllers, pages, routes nor JWT. It's for an app whose sign-in is its own, e.g. an API ported from another app that its mobile app already calls: the app keeps its endpoints and tokens, on top of `Customer` and its rules (passwords, lockout, sessions, inactive accounts).
23
+
24
+ The app then creates and finds the `Customer::Session` rows itself, or ignores them, and sets `Current.customer_session`. With passwords, the emailed links point at the app's domain (`https://<host>/account/confirm?token=…`, `/password-reset?token=…`, `/account/email?token=…`), or at `CUSTOMERS_APP_LINKS_URL`, as for the API: the app's own pages or mobile app handle them.
25
+
26
+ ## Signing in with an emailed code
27
+
28
+ 1. The person types their email; the app emails them a 6-digit code. It answers the same whether or not the email has an account (ANSSI guide on passwords, R14).
29
+ 2. They type the code: it signs them in to the email's account, created the first time. Signing up is signing in.
30
+
31
+ A code is valid for 10 minutes, for 5 tries (the right one included), and once. A new code replaces the earlier one. Only an HMAC of the code is stored, keyed on the app's `secret_key_base`, and the code is created in the job sending the email, so it's never in the job queue either. Change `validity` and `max_attempts` on `Customer::SignInCode`.
32
+
33
+ A new email address is confirmed the same way: a code sent to it.
34
+
35
+ ## Signing in with a password
36
+
37
+ - **Signing up** takes an email and a password, following the same rules as the admin's (`Trek::Users::PasswordPolicy`: 12 characters, a digit and a symbol below 20…, configurable), and signs in at once. A link confirming the email is sent, valid a day; following it sets `confirmed_at`. The app decides what an unconfirmed account may do (`customer.confirmed?`), and the account page sends the link again.
38
+ - **Failed sign-ins** lock signing in as for the admin (`Trek::Users::SignInLockout`), and `CustomerMailer#sign_in_locked` tells the account.
39
+ - **A forgotten password** is reset by a link, valid 15 minutes and used up by the new password (`has_secure_password`'s `password_reset_token`), which signs out every session. Asking for one answers the same whether or not the email has an account.
40
+ - **Changing the password** needs a recent sign-in (15 minutes, `Customer::Session::RECENT`), and signs out the other sessions.
41
+ - **A new email address** is kept in `unconfirmed_email` until the link sent to it, valid an hour, is followed.
42
+
43
+ ## Rate limits
44
+
45
+ Against guessing and flooding, each IP address may try to sign in 10 times in 3 minutes (5 with a password) and fail 20 times an hour, and ask for 10 emails (codes, links) in 15 minutes; each address gets at most 5 emails an hour. The limits are counted in `Rails.cache`, which must be shared between servers (see [Failed sign-ins](/reference/models/user#failed-sign-ins)).
46
+
47
+ ## Sessions
48
+
49
+ | | Website | API (`--api`) |
50
+ | --- | --- | --- |
51
+ | Held by | a signed `customer_session_id` cookie (`HttpOnly`, `SameSite=Lax`) | a JWT the app sends as `Authorization: Bearer …` |
52
+ | Expires | after 30 days without a request, and a year after signing in | the same, checked on the session row: the JWT only names it |
53
+ | Signing out | deletes the session and the cookie | deletes the session, and the JWT stops working |
54
+
55
+ Both are `Customer::Session` rows (`customer_sessions`), listed on the account, where the person can sign out the others. Change `idle_timeout` and `lifetime` on `Customer::Session` ([Trek::Users::SessionLifetime](/reference/models/user#sessions)). The JWT is signed with HS256 and a key derived from `secret_key_base`, and carries the session's id (`sub`) and the end of its lifetime (`exp`): unlike a self-contained token, it can be revoked.
56
+
57
+ Deleting the account, and changing the password, need a sign-in less than 15 minutes old; otherwise the website sends the person to sign in again, and the API answers 403 with `recent_sign_in_needed`.
58
+
59
+ Schedule the purge of expired sessions daily, as for the admin's: `Trek::PurgeExpiredSessionsJob.perform_later("Customer::Session")`.
60
+
61
+ The admin's own session (`session_id`, `User::Session`) is untouched: someone may be signed in to both, as two accounts that can share an email address.
62
+
63
+ ## What the generator adds
64
+
65
+ - `Customer` and `Customer::Session`, `Customer::SignInCode` for codes, and a migration creating their tables
66
+ - `Current.customer_session`, and `Current.customer`
67
+ - `Customers::Authentication`, and the controllers: under `Customers::` with pages for a website, under `Api::V1::Customers::` for the API
68
+ - `CustomerMailer` with its views in English and French, and `Customers::SignInCodeJob` for codes
69
+ - `config/locales/customers.{en,fr}.yml`
70
+ - the routes, on paths of their own (below), so renaming the model keeps them
71
+ - specs, when the app has a `spec` directory
72
+
73
+ ### Website
74
+
75
+ `Customers::Authentication` is included in `ApplicationController`, so every page may use:
76
+
77
+ | Helper | Returns |
78
+ | --- | --- |
79
+ | `current_customer`, `customer_signed_in?` | the signed-in customer, if any |
80
+ | `staff_user` | the admin's user signed in in this browser, when privileged, for previews and links to the admin on the site. Read only: it neither extends nor ends their admin session, and never signs a customer in |
81
+
82
+ A page for customers only calls `before_action :require_customer`, which sends the others to sign in and brings them back after.
83
+
84
+ | Path | Page |
85
+ | --- | --- |
86
+ | `GET /sign-in` | the sign-in form (`customer_sign_in_path`): the email for a code, or the email and password |
87
+ | `GET /sign-in/code` | the code form (codes) |
88
+ | `GET /sign-up` | the sign-up form (passwords) |
89
+ | `GET /password-reset/new` | asking for a link to choose a new password (passwords) |
90
+ | `GET /account/confirm?token=…` | the link confirming the email (passwords) |
91
+ | `DELETE /sign-out` | signs out |
92
+ | `GET /account` | the account: name, email, password, signed-in browsers, deletion (`customer_account_path`) |
93
+ | `GET /account/email/new` | changing the email, confirmed by a code or a link sent to the new address |
94
+ | `GET /account/password/edit` | changing the password (passwords) |
95
+
96
+ The pages render in the site's layout, unstyled, with their notices in `app/views/customers/_flash.html.slim`: move those to the layout if it shows flash messages.
97
+
98
+ ### API
99
+
100
+ The endpoints are the API's first version, under `/api/v1`, so that a later version can be served next to them while older releases of the mobile app still call these. Every endpoint answers JSON; an error comes as `{ "error": "invalid_code", "message": "…" }`, the message in the request's locale.
101
+
102
+ | Endpoint | Body | Answer |
103
+ | --- | --- | --- |
104
+ | `POST /api/v1/sign-in` | codes: `email`; passwords: `email`, `password` | codes: 202, a code is emailed; passwords: 201, `{ token, customer }` |
105
+ | `POST /api/v1/sign-in/code` | `email`, `otp` | 201, `{ token, customer }` (codes) |
106
+ | `POST /api/v1/sign-up` | `email`, `password`, `name` | 201, `{ token, customer }` (passwords) |
107
+ | `POST /api/v1/password-reset` | `email` | 202, a link is emailed if the email has an account (passwords) |
108
+ | `PATCH /api/v1/password-reset` | `token`, `password` | 204 (passwords) |
109
+ | `POST`, `PATCH /api/v1/account/confirmation` | `token` for `PATCH` | sends the confirmation link again; confirms the email (passwords) |
110
+ | `DELETE /api/v1/session` | | 204, signs out |
111
+ | `GET`, `PATCH`, `DELETE /api/v1/account` | `name` | the account; deleting needs a recent sign-in |
112
+ | `POST /api/v1/account/email` | `email` | 202, a code or a link is emailed to the new address |
113
+ | `PATCH /api/v1/account/email` | codes: `email`, `otp`; passwords: `token` | the account, with its new email; 409 when another account has it |
114
+ | `PATCH /api/v1/account/password` | `password` | 204 (passwords); needs a recent sign-in |
115
+ | `GET /api/v1/account/sessions` | | the signed-in devices, `current` for this one |
116
+ | `DELETE /api/v1/account/sessions/:id`, `/others` | | 204 |
117
+
118
+ With passwords, the emailed links point at the app's domain (`https://<host>/account/confirm?token=…`, `/password-reset?token=…`, `/account/email?token=…`), or at `CUSTOMERS_APP_LINKS_URL`: the mobile app opens them (iOS universal links, Android app links) and sends their `token` to the endpoints above.
119
+
120
+ ## Audit log
121
+
122
+ When the app has the [audit log](/reference/generators/audit-log), the account controllers write to it too: sign-ins, failed or not, sign-ups, code and link requests (with no actor), and account changes, the customer being the actor.
123
+
124
+ ## Inactive accounts
125
+
126
+ `Customer.inactive_since(time)` lists the accounts with no request since `time` (`last_activity_at`, written at most every 5 minutes), e.g. for a job deleting them. It can only ever list customers.
127
+
128
+ ## Upgrading Trek
129
+
130
+ The generated files are the app's own: upgrading Trek never changes them. On each upgrade, check:
131
+
132
+ - **The CHANGELOG,** for entries on what the generated code relies on in the gem: `Trek::Users::SessionLifetime`, `Trek::Users::SignInCode` (codes), `Trek::Users::SignInLockout` and `Trek::Users::PasswordPolicy` (passwords), `Trek::PurgeExpiredSessionsJob`, `Trek::ResolveSessionHostnameJob` and `Trek::Auditable`.
133
+ - **The generator's own changes,** a security fix among them, which reach the app only by running it again. On a clean working tree, run it with the same name and options, then review `git diff` and restore the app's own changes:
134
+
135
+ ```sh
136
+ rails g trek:public_accounts Customer --force # plus the options it was generated with
137
+ git diff
138
+ ```
139
+
140
+ Running it again adds nothing already there: no second migration, route or `Current` attribute.
141
+ - **The admin's session,** which `staff_user` reads: its `session_id` cookie and `User::Session`, in `Customers::Authentication#staff_user`. An upgrade note says when they change.
@@ -41,7 +41,7 @@ The policy only **reports** violations at first (`config.content_security_policy
41
41
 
42
42
  ### Violation reports
43
43
 
44
- Browsers send each violation to the policy's `report-uri`: `/csp-reports`, which `Trek::CspReportsController` answers (the generator adds `post "csp-reports"` to the routes). It reports each violation to **AppSignal**, when the app uses it, in a `csp` namespace: one incident per directive and blocked origin (e.g. `script-src-elem https://cdn.example.net`), tagged with the page and whether the policy was enforced, so a new violation is notified once and closed once fixed. It logs each one too (`[CSP] script-src-elem blocked https://cdn.example.net on /pages/team (report)`), which is all an app without AppSignal gets.
44
+ Browsers send each violation to the policy's `report-uri`: `/csp-reports`, which `Trek::CspReportsController` answers (the generator adds `post "csp-reports"` to the routes). It reports each violation to **AppSignal**, when the app uses it, in a `csp` namespace: one incident per directive and blocked origin (e.g. `script-src-elem https://cdn.example.net`), tagged with the page and whether the policy was enforced, so a new violation is notified once and closed once fixed. To tell what was blocked, each report is also tagged with the browser's user agent and, when the browser gives them, where it happened (`source`: the file's URL, without its query, and the line) and the `sample`: the first 40 characters of an inline script, which the policy asks for with `'report-sample'` (`policy.script_src :self, :report_sample`). Tags are cut to 255 characters, as AppSignal drops longer ones. It logs each one too (`[CSP] script-src-elem blocked inline on /pages/team (report) at https://www.example.com/pages/team:12: "alert(1)"`), which is all an app without AppSignal gets.
45
45
 
46
46
  It keeps the reports about the app's own pages only, and drops what browser extensions load or inject, which makes most reports and says nothing about the app. It keeps the page's path, not its query, which may carry a token, and the blocked resource's origin, not its URL. Browsers send no CSRF token nor cookie: the endpoint needs neither, and answers `204`.
47
47
 
@@ -8,7 +8,8 @@ A comprehensive list of features and settings included in Trek.
8
8
  - [Admin user](/reference/generators/admin-user) — `rails g trek:admin:user`
9
9
  - [Scaffold](/reference/generators/scaffold) — `rails g trek:scaffold`
10
10
  - [Taxonomies](/reference/generators/taxonomies) — `rails g trek:taxonomies`
11
- - [Audit log](/reference/generators/audit-log) — `rails g trek:install:audit_log`
11
+ - [Audit log](/reference/generators/audit-log) — `rails g trek:audit_log`
12
+ - [Public accounts](/reference/generators/public-accounts) — `rails g trek:public_accounts`
12
13
  - [Security headers](/reference/generators/security-headers) — `rails g trek:install:security_headers`
13
14
 
14
15
  ## Models
@@ -2,6 +2,8 @@
2
2
 
3
3
  The host application's `User` model, into which Trek injects authentication (`has_secure_password` and database-backed sessions), roles, theme & locale preferences and the invitation workflow.
4
4
 
5
+ It is the admin's account. The site's or the mobile app's users (customers, members…) get a model of their own, apart from it: see [Public accounts](/reference/generators/public-accounts).
6
+
5
7
  ## Columns added by Trek
6
8
 
7
9
  | Column | Type | Notes |
@@ -68,7 +70,7 @@ Users see their **active sessions** in a tab of their own profile: browser and s
68
70
  `Trek::Users::SignIns` helps users spot a sign-in that wasn't theirs:
69
71
 
70
72
  - the **Active sessions** tab of their profile shows their **previous sign-in**: its date, browser and IP address. It's kept on the user (`last_sign_in_at`, `last_sign_in_ip`, `last_sign_in_user_agent`, with their `current_sign_in_*` counterparts), as sessions are deleted when they end;
71
- - a sign-in from a **browser that never signed in as the user** emails them (`AdminMailer#new_sign_in`): date, browser, IP address, with links to change their password and to their active sessions. A browser is recognized by a signed cookie, kept for years, listing the users who signed in from it. Their very first sign-in sends nothing.
73
+ - a sign-in from a **browser that never signed in as the user** emails them (`AdminMailer#new_sign_in`): date, browser, IP address and its host name (as on the active sessions), with links to change their password and to their active sessions. A browser is recognized by a signed cookie, kept for years, listing the users who signed in from it. Their very first sign-in sends nothing.
72
74
 
73
75
  Set `self.new_sign_in_email = false` in the `User` model to turn the email off.
74
76
 
data/docs/what-is-trek.md CHANGED
@@ -9,7 +9,7 @@ Trek ships as a Rails engine (a gem plus an NPM package) that integrates into yo
9
9
  - **A complete admin panel** — layout, navigation, search, pagination, light & dark themes, all built with [ViewComponent](https://viewcomponent.org), [Stimulus](https://stimulus.hotwired.dev) and [Turbo](https://turbo.hotwired.dev).
10
10
  - **Pages** — hierarchical, translatable pages with version history, SEO metadata and stable, redirect-aware URL paths.
11
11
  - **Fragments** — reusable chunks of content (a footer text, a hero tagline…) editable from the admin and usable anywhere in your views.
12
- - **A rich text editor** — based on [Tiptap](https://tiptap.dev), with image uploads (Uppy), link insertion and content stored as structured ProseMirror JSON.
12
+ - **A content editor** — based on [Tiptap](https://tiptap.dev), with image uploads (Uppy), link insertion and content stored as structured ProseMirror JSON and rendered safely to HTML (see [Content editor](/content-editor)).
13
13
  - **Authentication & authorization** — sign in built on Rails' [`has_secure_password`](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) with database-backed sessions, password resets, invitations, and [ActionPolicy](https://actionpolicy.evilmartians.io) policies with `admin`, `editor`, `user`, `reader` and `guest` roles.
14
14
  - **Internationalization** — content translations with [Mobility](https://github.com/shioyama/mobility), locale management with [i18n-tasks](https://github.com/glebm/i18n-tasks), and optional automatic translation through DeepL.
15
15
  - **File uploads** — [Shrine](https://shrinerb.com)-backed attachments with S3 or local storage, image processing via libvips, and blurhash placeholders.
@@ -0,0 +1,54 @@
1
+ require "rails/generators/actions"
2
+ require "rails/generators/migration"
3
+ require "generators/trek/helpers"
4
+
5
+ module Trek
6
+ module Generators
7
+ class AuditLogGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+ include Trek::Generators::Helpers
10
+
11
+ desc "Create the AuditLog model, record the admin's requests, and list them in the admin."
12
+
13
+ source_root File.expand_path("templates", __dir__)
14
+
15
+ def copy_model
16
+ template "models/audit_log.rb", "app/models/audit_log.rb"
17
+ end
18
+
19
+ def copy_migration_file
20
+ return unless defined?(ActiveRecord)
21
+
22
+ migration_template("migrations/audit_logs.rb.erb",
23
+ "db/migrate/trek_audit_logs.rb",
24
+ migration_class_name:)
25
+ end
26
+
27
+ def copy_initializer
28
+ template "initializers/audit_log.rb", "config/initializers/audit_log.rb"
29
+ end
30
+
31
+ def copy_controller
32
+ template "controllers/admin/audit_logs_controller.rb",
33
+ "app/controllers/admin/audit_logs_controller.rb"
34
+ end
35
+
36
+ def copy_views
37
+ directory "views/admin/audit_logs", "app/views/admin/audit_logs"
38
+ end
39
+
40
+ def copy_policy
41
+ template "policies/audit_log.rb", "app/policies/admin/audit_log_policy.rb"
42
+ end
43
+
44
+ def add_routes
45
+ route "resources :audit_logs, only: %i[index show]", namespace: :admin
46
+ end
47
+
48
+ def copy_specs
49
+ template "spec/models/audit_log_spec.rb", "spec/models/audit_log_spec.rb"
50
+ template "spec/requests/admin/audit_logs_spec.rb", "spec/requests/admin/audit_logs_spec.rb"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -25,10 +25,10 @@ module Trek
25
25
  get :options, on: :collection
26
26
  end
27
27
  RUBY
28
+ route "resource :password_renewal, only: :create", namespace: :admin
28
29
  route <<~RUBY, namespace: :admin
29
30
  resources :users do
30
31
  patch :theme, on: :collection
31
- post :require_password_renewal, on: :collection
32
32
  end
33
33
  RUBY
34
34
  end
@@ -52,6 +52,11 @@ module Trek
52
52
  template "controllers/admin/passkeys_controller.rb",
53
53
  "app/controllers/admin/passkeys_controller.rb"
54
54
  end
55
+
56
+ def copy_password_renewals
57
+ template "controllers/admin/password_renewals_controller.rb",
58
+ "app/controllers/admin/password_renewals_controller.rb"
59
+ end
55
60
  end
56
61
  end
57
62
  end
@@ -1,54 +1,18 @@
1
- require "rails/generators/actions"
2
- require "rails/generators/migration"
3
- require "generators/trek/helpers"
1
+ require "generators/trek/audit_log_generator"
4
2
 
5
3
  module Trek
6
4
  module Generators
7
5
  module Install
8
- class AuditLogGenerator < Rails::Generators::Base
9
- include Rails::Generators::Migration
10
- include Trek::Generators::Helpers
6
+ # The audit log's generator under its former name: `trek:install` doesn't run it, so it's `trek:audit_log` now.
7
+ # Removed in Trek 3.
8
+ class AuditLogGenerator < Trek::Generators::AuditLogGenerator
9
+ hide!
11
10
 
12
- desc "Create the AuditLog model, record the admin's requests, and list them in the admin."
11
+ # Not inherited: Rails derives it from the generator's own path.
12
+ source_root Trek::Generators::AuditLogGenerator.source_root
13
13
 
14
- source_root File.expand_path("../templates", __dir__)
15
-
16
- def copy_model
17
- template "models/audit_log.rb", "app/models/audit_log.rb"
18
- end
19
-
20
- def copy_migration_file
21
- return unless defined?(ActiveRecord)
22
-
23
- migration_template("migrations/audit_logs.rb.erb",
24
- "db/migrate/trek_audit_logs.rb",
25
- migration_class_name:)
26
- end
27
-
28
- def copy_initializer
29
- template "initializers/audit_log.rb", "config/initializers/audit_log.rb"
30
- end
31
-
32
- def copy_controller
33
- template "controllers/admin/audit_logs_controller.rb",
34
- "app/controllers/admin/audit_logs_controller.rb"
35
- end
36
-
37
- def copy_views
38
- directory "views/admin/audit_logs", "app/views/admin/audit_logs"
39
- end
40
-
41
- def copy_policy
42
- template "policies/audit_log.rb", "app/policies/admin/audit_log_policy.rb"
43
- end
44
-
45
- def add_routes
46
- route "resources :audit_logs, only: %i[index show]", namespace: :admin
47
- end
48
-
49
- def copy_specs
50
- template "spec/models/audit_log_spec.rb", "spec/models/audit_log_spec.rb"
51
- template "spec/requests/admin/audit_logs_spec.rb", "spec/requests/admin/audit_logs_spec.rb"
14
+ def deprecation_warning
15
+ say "`trek:install:audit_log` is deprecated and will be removed in Trek 3: run `trek:audit_log`.", :yellow
52
16
  end
53
17
  end
54
18
  end
@@ -0,0 +1,161 @@
1
+ require "rails/generators/actions"
2
+ require "generators/trek/helpers"
3
+
4
+ module Trek
5
+ module Generators
6
+ module Install
7
+ # Replaces the Solid Queue backend `rails new` sets up with GoodJob: one PostgreSQL
8
+ # database instead of two, jobs running inside Puma, and a dashboard in the admin.
9
+ class GoodJobGenerator < Rails::Generators::Base
10
+ GOOD_JOB_VERSION = "~> 4.13".freeze
11
+ GEM_COMMENT = "Active Job backend on PostgreSQL, with a dashboard [https://github.com/bensheldon/good_job]".freeze
12
+
13
+ # The line `rails new` writes to size the connection pool: `max_connections` since
14
+ # Rails 8.1, `pool` before it. Whichever it is, it keeps its name.
15
+ POOL_LINE = /^(\s*)(max_connections|pool): <%= ENV\.fetch\("RAILS_MAX_THREADS"\)[^\n]*%>\n/
16
+
17
+ include Trek::Generators::Helpers
18
+
19
+ desc "Install GoodJob as the Active Job backend."
20
+
21
+ source_root File.expand_path("../templates", __dir__)
22
+
23
+ def swap_gems
24
+ return if in_gemfile?("good_job")
25
+
26
+ # Solid Queue wants a second database, which a Trek app does not provision (Solid
27
+ # Cache stays: its table lives in the primary database). GoodJob takes over its line,
28
+ # under the Active Job adapter comment it belongs to.
29
+ if in_gemfile?("solid_queue")
30
+ gsub_file "Gemfile", /^gem "solid_queue".*\n/,
31
+ %(# #{GEM_COMMENT}\ngem "good_job", "#{GOOD_JOB_VERSION}"\n)
32
+ else
33
+ gem "good_job", GOOD_JOB_VERSION, comment: GEM_COMMENT
34
+ end
35
+ bundle_install
36
+ end
37
+
38
+ def remove_solid_queue_files
39
+ %w[config/queue.yml config/recurring.yml db/queue_schema.rb bin/jobs].each do |file|
40
+ remove_file file
41
+ end
42
+ end
43
+
44
+ def remove_solid_queue_from_puma
45
+ gsub_file "config/puma.rb",
46
+ /^(?:#[^\n]*Solid Queue[^\n]*\n)?plugin :solid_queue if ENV\["SOLID_QUEUE_IN_PUMA"\]\n\n?/,
47
+ ""
48
+ end
49
+
50
+ def remove_queue_database
51
+ gsub_file "config/database.yml", /^ queue:\n(?: [^\n]*\n)+/, ""
52
+ end
53
+
54
+ # GoodJob runs in the web container's process, so its threads need connections of their
55
+ # own — the pool `rails new` sizes for the web threads alone would starve them.
56
+ def size_connection_pool
57
+ gsub_file "config/database.yml", POOL_LINE, <<~'YAML'
58
+ \1# Web threads and job threads each check out their own connections, plus one for
59
+ \1# GoodJob's LISTEN/NOTIFY listener. GOOD_JOB_MAX_THREADS falls back to
60
+ \1# RAILS_MAX_THREADS inside GoodJob, so both shares match unless it is set.
61
+ \1\2: <%= web = ENV.fetch("RAILS_MAX_THREADS", 5).to_i; 1 + web + ENV.fetch("GOOD_JOB_MAX_THREADS", web).to_i %>
62
+ YAML
63
+ end
64
+
65
+ # Kamal's file is not what a Trek app deploys with, but leaving Solid Queue's variables
66
+ # in it would describe a backend the app no longer runs.
67
+ def clean_kamal_deploy_file
68
+ return unless File.exist?("config/deploy.yml")
69
+
70
+ gsub_file "config/deploy.yml",
71
+ /^(?:[ \t]*#[^\n]*\n)*([ \t]*)SOLID_QUEUE_IN_PUMA: true\n/,
72
+ <<~'YAML'
73
+ \1# Jobs run inside Puma (config.good_job.execution_mode = :async), so there is no
74
+ \1# separate job process to start. GOOD_JOB_MAX_THREADS sizes its thread pool.
75
+ \1# GOOD_JOB_MAX_THREADS: 5
76
+ YAML
77
+ gsub_file "config/deploy.yml",
78
+ /^[ \t]*# Set number of processes dedicated to Solid Queue[^\n]*\n[ \t]*# JOB_CONCURRENCY:[^\n]*\n\n?/,
79
+ ""
80
+ end
81
+
82
+ def configure_adapter
83
+ return if File.read("config/application.rb").include?("queue_adapter = :good_job")
84
+
85
+ inject_into_file "config/application.rb", adapter_configuration,
86
+ after: /^\s*config\.load_defaults [^\n]*\n/
87
+ end
88
+
89
+ # Active Job's test helper only swaps in its test adapter for a job class with no adapter
90
+ # configured, and config/application.rb now sets GoodJob: give the specs the test adapter
91
+ # back, for have_enqueued_job and the like.
92
+ def configure_test_adapter
93
+ return unless File.exist?("config/environments/test.rb")
94
+ return if File.read("config/environments/test.rb").include?("queue_adapter = :test")
95
+
96
+ inject_into_file "config/environments/test.rb", <<-RUBY, after: /^Rails\.application\.configure do\n/
97
+ # Jobs go to Active Job's test adapter, not GoodJob (config/application.rb), so specs can check
98
+ # what was enqueued.
99
+ config.active_job.queue_adapter = :test
100
+
101
+ RUBY
102
+ end
103
+
104
+ def configure_production
105
+ gsub_file "config/environments/production.rb", /^\s*config\.solid_queue\.connects_to[^\n]*\n/, ""
106
+ gsub_file "config/environments/production.rb",
107
+ /^(\s*)(?:# )?config\.active_job\.queue_adapter\s*=[^\n]*\n/,
108
+ <<~'RUBY'
109
+ \1# Jobs run in the web container's own threads: no worker process to pay for, and a
110
+ \1# deploy that drains them (shutdown_timeout) rather than dropping them.
111
+ \1config.good_job.execution_mode = :async
112
+ \1config.good_job.shutdown_timeout = 25
113
+ RUBY
114
+ end
115
+
116
+ def create_initializer
117
+ template "initializers/good_job.rb.tt", "config/initializers/good_job.rb"
118
+ end
119
+
120
+ def create_mail_delivery_job
121
+ copy_file "jobs/mail_delivery_job.rb", "app/jobs/mail_delivery_job.rb"
122
+ end
123
+
124
+ def mount_dashboard
125
+ route 'mount GoodJob::Engine => "good_job"', namespace: :admin
126
+ end
127
+
128
+ def create_migration
129
+ generate "good_job:install"
130
+ end
131
+
132
+ def create_specs
133
+ return unless File.directory?("spec")
134
+
135
+ copy_file "spec/requests/admin/good_job_spec.rb", "spec/requests/admin/good_job_spec.rb"
136
+ copy_file "spec/jobs/mail_delivery_job_spec.rb", "spec/jobs/mail_delivery_job_spec.rb"
137
+ end
138
+
139
+ private
140
+
141
+ # The model of a signed-in browser, which the dashboard's guard looks the session cookie up
142
+ # in: User::Session, or UserSession in an app generated before Trek nested it.
143
+ def session_model
144
+ File.exist?("app/models/user_session.rb") ? "UserSession" : "User::Session"
145
+ end
146
+
147
+ def adapter_configuration
148
+ <<-RUBY
149
+
150
+ # Use GoodJob as the Active Job backend (see config/initializers/good_job.rb).
151
+ config.active_job.queue_adapter = :good_job
152
+
153
+ # Every `deliver_later` email goes through MailDeliveryJob, which retries a failed
154
+ # delivery — GoodJob discards a failed job (see app/jobs/mail_delivery_job.rb).
155
+ config.action_mailer.delivery_job = "MailDeliveryJob"
156
+ RUBY
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -51,11 +51,11 @@ module Trek
51
51
  directory "db/seeds", "db/seeds"
52
52
  end
53
53
 
54
- # The audit log's specs come with it, from `trek:install:audit_log`, and the tags' (model and policies) from
55
- # `trek:taxonomies`.
54
+ # The audit log's specs come with it, from `trek:audit_log`, the GoodJob dashboard's from
55
+ # `trek:install:good_job`, and the tags' (model and policies) from `trek:taxonomies`.
56
56
  def copy_specs
57
57
  directory "spec/models", "spec/models", exclude_pattern: /audit_log|tag_spec/
58
- directory "spec/requests", "spec/requests", exclude_pattern: /audit_log/
58
+ directory "spec/requests", "spec/requests", exclude_pattern: /audit_log|good_job_spec/
59
59
  directory "spec/mailers", "spec/mailers"
60
60
  directory "spec/fixtures", "spec/fixtures"
61
61
  directory "spec/policies", "spec/policies", exclude_pattern: /tag(_category)?_policy_spec/
@@ -5,7 +5,7 @@ module Trek
5
5
  module Generators
6
6
  module Install
7
7
  class RubyGenerator < Rails::Generators::Base
8
- RUBY_VERSION = "4.0.7"
8
+ RUBY_VERSION = "4.0.9"
9
9
 
10
10
  include Trek::Generators::Helpers
11
11
 
@@ -1,53 +1,15 @@
1
- require "rails/generators/actions"
2
- require "generators/trek/helpers"
1
+ require "generators/trek/scalingo_generator"
3
2
 
4
3
  module Trek
5
4
  module Generators
6
5
  module Install
7
- class ScalingoGenerator < Rails::Generators::Base
8
- BUILDPACK_URL = "https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.0.tar.gz".freeze
6
+ # The Scalingo generator under its former name: `trek:install` doesn't run it, so it's `trek:scalingo` now.
7
+ # Removed in Trek 3.
8
+ class ScalingoGenerator < Trek::Generators::ScalingoGenerator
9
+ hide!
9
10
 
10
- include Trek::Generators::Helpers
11
-
12
- desc "Create Scalingo files."
13
-
14
- # The trek-buildpack replaces the apt + ruby buildpack combination. It
15
- # pins Node from the .node-version that `rails new` already generates.
16
- def remove_buildpacks
17
- remove_file ".buildpacks"
18
- end
19
-
20
- def create_aptfile
21
- create_file "Aptfile" do
22
- <<~TEXT
23
- libjemalloc2
24
- libvips-tools --no-install-recommends
25
- libwebp-dev
26
- TEXT
27
- end
28
- end
29
-
30
- def create_procfile
31
- create_file "Procfile" do
32
- <<~TEXT
33
- web: bundle exec rails server -p $PORT -e $RAILS_ENV -b 0.0.0.0
34
- # worker: bundle exec sidekiq -C config/sidekiq.yml
35
- postdeploy: bundle exec rails db:migrate
36
- TEXT
37
- end
38
- end
39
-
40
- def final_instructions
41
- say ""
42
- say "Configure the Scalingo app environment:"
43
- say ""
44
- say " scalingo --app <app> env-set BUILDPACK_URL=#{BUILDPACK_URL}"
45
- say " scalingo --app <app> env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'"
46
- say ""
47
- say "and make sure config/database.yml reads DATABASE_URL in production."
48
- say ""
49
- say "See https://trek.etamin.studio/deploy for details."
50
- say ""
11
+ def deprecation_warning
12
+ say "`trek:install:scalingo` is deprecated and will be removed in Trek 3: run `trek:scalingo`.", :yellow
51
13
  end
52
14
  end
53
15
  end
@@ -72,6 +72,11 @@ module Trek
72
72
  generate_inline "trek:install:strip_attributes"
73
73
  end
74
74
 
75
+ # Before the emails: the mailer configuration points at MailDeliveryJob.
76
+ def install_jobs
77
+ generate_inline "trek:install:good_job"
78
+ end
79
+
75
80
  def install_emails
76
81
  generate_inline "trek:install:postmark"
77
82
  generate_inline "trek:install:mailer"
@@ -180,6 +185,16 @@ module Trek
180
185
  say ""
181
186
  say " make dev"
182
187
  say ""
188
+ say "More generators, to run when the app needs them:"
189
+ say ""
190
+ say " rails g trek:scaffold Book title intro:text # a model and its admin panel"
191
+ say " rails g trek:taxonomies # tags and their categories"
192
+ say " rails g trek:audit_log # a log of the admin's requests"
193
+ say " rails g trek:public_accounts Customer # accounts for the site's users, apart from the admin's"
194
+ say " rails g trek:scalingo # the files deploying to Scalingo"
195
+ say ""
196
+ say "All of Trek's generators and their options: https://trek.etamin.studio/reference/#generators"
197
+ say ""
183
198
  end
184
199
 
185
200
  private