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.
- checksums.yaml +4 -4
- data/.node-version +1 -1
- data/AGENTS.md +7 -4
- data/CHANGELOG.md +37 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +16 -3
- data/Makefile +2 -0
- data/app/components/trek/dashboard/tile_component/tile_component.css +9 -0
- data/app/components/trek/dashboard/tile_component/tile_component.html.slim +4 -2
- data/app/components/trek/dashboard/tile_component.rb +12 -2
- data/app/components/trek/form/content_editor_component/content_editor_component.css +5 -0
- data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +19 -93
- data/app/components/trek/form/content_editor_component/content_editor_component.js +94 -251
- data/app/components/trek/form/content_editor_component/extensions/block_view.js +40 -0
- data/app/components/trek/form/content_editor_component/extensions/image_block.js +28 -26
- data/app/components/trek/form/content_editor_component/extensions/prompts_block.js +15 -26
- data/app/components/trek/form/content_editor_component/extensions/table_of_contents.js +1 -3
- data/app/components/trek/form/content_editor_component.rb +15 -0
- data/app/components/trek/form/image_field_component/image_field_component.css +40 -1
- data/app/components/trek/form/image_field_component/image_field_component.html.slim +0 -9
- data/app/components/trek/form/image_field_component.rb +36 -9
- data/app/content/trek/content/cleaner.rb +53 -0
- data/app/content/trek/content/document.rb +2 -2
- data/app/content/trek/content/prompts_block.rb +1 -2
- data/app/content/trek/content.rb +6 -0
- data/app/controllers/concerns/trek/reorderable.rb +24 -6
- data/app/controllers/trek/csp_reports_controller.rb +14 -1
- data/app/controllers/trek/panels/prompts_controller.rb +1 -1
- data/app/controllers/trek/resource_controller.rb +5 -5
- data/app/jobs/trek/purge_expired_sessions_job.rb +4 -3
- data/app/models/concerns/trek/contentable.rb +15 -1
- data/app/models/concerns/trek/users/sign_in_code.rb +70 -0
- data/app/models/concerns/trek/users/sign_in_lockout.rb +4 -2
- data/app/views/trek/panels/links/_form.html.slim +2 -4
- data/app/views/trek/panels/prompts/create.turbo_stream.slim +0 -1
- data/config/locales/trek.en.yml +26 -11
- data/config/locales/trek.fr.yml +26 -11
- data/docs/.vitepress/config.mjs +4 -1
- data/docs/background-jobs.md +69 -0
- data/docs/content-editor.md +219 -0
- data/docs/customize.md +2 -0
- data/docs/deploy.md +2 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +1 -1
- data/docs/reference/components.md +2 -2
- data/docs/reference/concerns/contentable.md +10 -4
- data/docs/reference/concerns/formattable.md +1 -1
- data/docs/reference/environment-variables.md +1 -0
- data/docs/reference/formatters.md +2 -30
- data/docs/reference/forms.md +1 -1
- data/docs/reference/generators/audit-log.md +2 -2
- data/docs/reference/generators/install.md +3 -2
- data/docs/reference/generators/public-accounts.md +141 -0
- data/docs/reference/generators/security-headers.md +1 -1
- data/docs/reference/index.md +2 -1
- data/docs/reference/models/user.md +3 -1
- data/docs/what-is-trek.md +1 -1
- data/lib/generators/trek/audit_log_generator.rb +54 -0
- data/lib/generators/trek/install/admin/users_generator.rb +6 -1
- data/lib/generators/trek/install/audit_log_generator.rb +9 -45
- data/lib/generators/trek/install/good_job_generator.rb +161 -0
- data/lib/generators/trek/install/model_specs_generator.rb +3 -3
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +7 -45
- data/lib/generators/trek/install_generator.rb +15 -0
- data/lib/generators/trek/public_accounts_generator.rb +267 -0
- data/lib/generators/trek/scalingo_generator.rb +55 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/controllers/admin/password_renewals_controller.rb +11 -0
- data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +1 -1
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +0 -8
- data/lib/generators/trek/templates/initializers/content_security_policy.rb +3 -2
- data/lib/generators/trek/templates/initializers/good_job.rb.tt +38 -0
- data/lib/generators/trek/templates/jobs/mail_delivery_job.rb +6 -0
- data/lib/generators/trek/templates/layouts/admin.html.slim +5 -0
- data/lib/generators/trek/templates/mailers/admin_mailer.rb +4 -2
- data/lib/generators/trek/templates/package.json.tt +1 -1
- data/lib/generators/trek/templates/public_accounts/api/authentication.rb.tt +76 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/accounts_controller.rb.tt +22 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/active_sessions_controller.rb.tt +21 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/base_controller.rb.tt +71 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/email_changes_controller.rb.tt +55 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/email_confirmations_controller.rb.tt +19 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/password_resets_controller.rb.tt +28 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/passwords_controller.rb.tt +17 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/registrations_controller.rb.tt +17 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/sessions_controller.rb.tt +45 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/sign_in_codes_controller.rb.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/jobs/sign_in_code_job.rb.tt +13 -0
- data/lib/generators/trek/templates/public_accounts/locales/accounts.en.yml.tt +205 -0
- data/lib/generators/trek/templates/public_accounts/locales/accounts.fr.yml.tt +205 -0
- data/lib/generators/trek/templates/public_accounts/mailers/account_mailer.rb.tt +85 -0
- data/lib/generators/trek/templates/public_accounts/migrations/create_accounts.rb.tt +46 -0
- data/lib/generators/trek/templates/public_accounts/models/account/session.rb.tt +47 -0
- data/lib/generators/trek/templates/public_accounts/models/account/sign_in_code.rb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/models/account.rb.tt +73 -0
- data/lib/generators/trek/templates/public_accounts/spec/api/accounts_spec.rb.tt +90 -0
- data/lib/generators/trek/templates/public_accounts/spec/api/sign_ins_spec.rb.tt +158 -0
- data/lib/generators/trek/templates/public_accounts/spec/models/account/sign_in_code_spec.rb.tt +56 -0
- data/lib/generators/trek/templates/public_accounts/spec/models/account_spec.rb.tt +109 -0
- data/lib/generators/trek/templates/public_accounts/spec/web/accounts_spec.rb.tt +126 -0
- data/lib/generators/trek/templates/public_accounts/spec/web/authentication_spec.rb.tt +43 -0
- data/lib/generators/trek/templates/public_accounts/spec/web/sign_ins_spec.rb.tt +215 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.html.slim.tt +13 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.html.slim.tt +9 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.html.slim.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.html.slim.tt +13 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.html.slim.tt +9 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.html.slim.tt +9 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.html.slim.tt +23 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.text.erb.tt +14 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.html.slim.tt +24 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.text.erb.tt +14 -0
- data/lib/generators/trek/templates/public_accounts/web/authentication.rb.tt +97 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/accounts_controller.rb.tt +33 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/active_sessions_controller.rb.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/base_controller.rb.tt +52 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/email_changes_controller.rb.tt +85 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/email_confirmations_controller.rb.tt +19 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/password_resets_controller.rb.tt +41 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/passwords_controller.rb.tt +23 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/registrations_controller.rb.tt +24 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/sessions_controller.rb.tt +78 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/sign_in_codes_controller.rb.tt +27 -0
- data/lib/generators/trek/templates/public_accounts/web/views/_flash.html.slim.tt +5 -0
- data/lib/generators/trek/templates/public_accounts/web/views/accounts/show.html.slim.tt +52 -0
- data/lib/generators/trek/templates/public_accounts/web/views/email_changes/edit.html.slim.tt +17 -0
- data/lib/generators/trek/templates/public_accounts/web/views/email_changes/new.html.slim.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/web/views/password_resets/edit.html.slim.tt +26 -0
- data/lib/generators/trek/templates/public_accounts/web/views/password_resets/new.html.slim.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/web/views/passwords/edit.html.slim.tt +25 -0
- data/lib/generators/trek/templates/public_accounts/web/views/registrations/new.html.slim.tt +28 -0
- data/lib/generators/trek/templates/public_accounts/web/views/sessions/new.html.slim.tt +38 -0
- data/lib/generators/trek/templates/public_accounts/web/views/sign_in_codes/new.html.slim.tt +17 -0
- data/lib/generators/trek/templates/spec/jobs/mail_delivery_job_spec.rb +11 -0
- data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +10 -0
- data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +1 -1
- data/lib/generators/trek/templates/spec/requests/admin/good_job_spec.rb +34 -0
- data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +1 -1
- data/lib/generators/trek/templates/spec/requests/admin/panels_spec.rb +16 -0
- data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +2 -2
- data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +1 -1
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +2 -2
- data/lib/generators/trek/templates/views/admin/users/index.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +1 -1
- data/lib/trek/csp_reports.rb +22 -4
- data/lib/trek/typography_formatter.rb +35 -4
- data/lib/trek/version.rb +1 -1
- data/package.json +12 -8
- data/trek.gemspec +1 -1
- data/vitest.config.mjs +24 -0
- data/yarn.lock +1004 -288
- metadata +90 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Background jobs
|
|
2
|
+
|
|
3
|
+
Trek apps run Active Job on [GoodJob](https://github.com/bensheldon/good_job), installed by `rails g trek:install` (or on its own with `rails g trek:install:good_job`). GoodJob stores its queue in the application's own PostgreSQL database, so there is no second database to provision — which is why the installer removes the Solid Queue setup `rails new` generates.
|
|
4
|
+
|
|
5
|
+
## What the installer sets up
|
|
6
|
+
|
|
7
|
+
| Where | What |
|
|
8
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `Gemfile` | `good_job` replaces `solid_queue` (Solid Cache stays: its table lives in the primary database) |
|
|
10
|
+
| `config/application.rb` | `config.active_job.queue_adapter = :good_job` and `config.action_mailer.delivery_job = "MailDeliveryJob"` |
|
|
11
|
+
| `config/environments/production.rb` | `config.good_job.execution_mode = :async` and `config.good_job.shutdown_timeout = 25` |
|
|
12
|
+
| `config/environments/test.rb` | `config.active_job.queue_adapter = :test`, so specs can check what is enqueued (`have_enqueued_job`) |
|
|
13
|
+
| `config/initializers/good_job.rb` | queues, cron, error reporting, and the dashboard guard |
|
|
14
|
+
| `config/database.yml` | a connection pool sized for the web threads **and** the job threads |
|
|
15
|
+
| `app/jobs/mail_delivery_job.rb` | the delivery job every `deliver_later` email goes through |
|
|
16
|
+
| `config/routes.rb` | `mount GoodJob::Engine => "good_job"` inside `namespace :admin` |
|
|
17
|
+
|
|
18
|
+
## Async in Puma, or a dedicated worker
|
|
19
|
+
|
|
20
|
+
In production the jobs run in the web container's own threads (`execution_mode = :async`): nothing else to deploy, nothing else to pay for. `shutdown_timeout = 25` lets a restart drain the jobs in flight instead of dropping them.
|
|
21
|
+
|
|
22
|
+
`GOOD_JOB_MAX_THREADS` sizes the job pool. It also feeds the database pool, since `config/database.yml` reserves connections for the web threads and the job threads separately:
|
|
23
|
+
|
|
24
|
+
```yml
|
|
25
|
+
max_connections: <%= web = ENV.fetch("RAILS_MAX_THREADS", 5).to_i; 1 + web + ENV.fetch("GOOD_JOB_MAX_THREADS", web).to_i %>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
When the jobs outgrow the web container — long-running work, or a queue that must not compete with requests — move them to their own process: set `config.good_job.execution_mode = :external` and uncomment the worker line in the `Procfile` (`worker: bundle exec good_job start`). Note that in `:external` mode only the worker runs a scheduler, so cron then runs there and nowhere else.
|
|
29
|
+
|
|
30
|
+
## Queues
|
|
31
|
+
|
|
32
|
+
One scheduler serves every queue by default (`config.good_job.queues = "*"`). Give a queue its own lane when it must not wait behind the others:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
config.good_job.queues = "mailers:2;*:3"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The thread counts have to stay within `GOOD_JOB_MAX_THREADS` — they are database connections too.
|
|
39
|
+
|
|
40
|
+
## Scheduled jobs (cron)
|
|
41
|
+
|
|
42
|
+
Recurring jobs are declared in the initializer, and only run in production (a development machine should not fire them):
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
config.good_job.enable_cron = Rails.env.production?
|
|
46
|
+
config.good_job.cron = {
|
|
47
|
+
purge_old_sessions: {
|
|
48
|
+
cron: "0 3 * * *", # every day at 3am
|
|
49
|
+
class: "PurgeOldSessionsJob",
|
|
50
|
+
description: "Delete the user sessions that expired more than a month ago"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## The dashboard
|
|
56
|
+
|
|
57
|
+
GoodJob's dashboard is mounted at `/admin/good_job`. It sits outside `Admin::BaseController`, so it is guarded in the initializer by the same signed session cookie the admin uses: anyone who is not an administrator gets a 404 rather than a redirect, which keeps the route invisible. The installed request spec covers the three cases (administrator, privileged non-administrator, visitor).
|
|
58
|
+
|
|
59
|
+
## Mail deliveries are retried
|
|
60
|
+
|
|
61
|
+
GoodJob discards a failed job rather than retrying it, so a transient error from the mail service would lose the email. Every `deliver_later` goes through `MailDeliveryJob` instead, which retries five times with a growing delay:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class MailDeliveryJob < ActionMailer::MailDeliveryJob
|
|
65
|
+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
An error raised in a scheduler thread, outside a job's own execution, has no retry handler to catch it; `config.good_job.on_thread_error` reports it through `Rails.error` like the rest of the app.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Content editor
|
|
2
|
+
|
|
3
|
+
Trek's content editor is where the admin writes the content of pages, page versions and fragments: headings, lists, quotes, links, images and calls to action, laid out as they will read.
|
|
4
|
+
|
|
5
|
+
It's built on [Tiptap](https://tiptap.dev), an editor framework on top of [ProseMirror](https://prosemirror.net). That's why the editor doesn't save HTML. It saves a structured document: a tree of typed nodes (paragraphs, headings, images…) and marks (bold, links…), each with typed attributes. The site turns that document into HTML when it shows it, with the [`prosemirror`](https://github.com/etaminstudio/prosemirror) gem.
|
|
6
|
+
|
|
7
|
+
Keeping structure rather than markup lets Trek:
|
|
8
|
+
|
|
9
|
+
- **render the content safely:** the HTML is built from known types, so content can't inject markup;
|
|
10
|
+
- **change how a block looks** on the site without touching stored content, for example images rendered by the app's own component;
|
|
11
|
+
- **keep links to pages working** when they move: a link stores the page, not its URL;
|
|
12
|
+
- **read the content as data:** extract its text, search it, check it.
|
|
13
|
+
|
|
14
|
+
This guide follows content from end to end:
|
|
15
|
+
|
|
16
|
+
1. **Editing:** [using the editor](#using-the-editor) in admin forms, and its panels.
|
|
17
|
+
2. **Storage:** [how content is stored](#how-content-is-stored), as JSON in a `jsonb` column.
|
|
18
|
+
3. **Rendering:** [rendering on the site](#rendering-on-the-site), through `Trek::Content.schema`, and [customizing it](#customizing-the-rendering).
|
|
19
|
+
|
|
20
|
+
## Using the editor
|
|
21
|
+
|
|
22
|
+
Add it to an admin form with `f.content_editor`:
|
|
23
|
+
|
|
24
|
+
```slim
|
|
25
|
+
= form_with model: [:admin, @book] do |f|
|
|
26
|
+
= f.group :content
|
|
27
|
+
= f.content_editor :content
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Option | Default | Effect |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `nodes` | `true` | the toolbar's text buttons: bold, italic, link, clear formatting, line break |
|
|
33
|
+
| `blocks` | `true` | the toolbar's block buttons: paragraph, headings 2 to 4, bullet list, quote, horizontal rule |
|
|
34
|
+
| `floating` | `true` | the menu on an empty line, inserting an image or a prompt |
|
|
35
|
+
| `autofocus` | `false` | puts the cursor at the end of the content on load |
|
|
36
|
+
|
|
37
|
+
Undo and redo are always there. For a short field, such as an introduction, turn the rest off:
|
|
38
|
+
|
|
39
|
+
```slim
|
|
40
|
+
= f.content_editor :intro, blocks: false, floating: false
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Tiptap's Markdown shortcuts work too: typing `1. ` starts an ordered list, and `~~text~~` strikes text through, even though the toolbar has no button for them. The site renders them like the rest.
|
|
44
|
+
|
|
45
|
+
### Links
|
|
46
|
+
|
|
47
|
+
The link button opens a panel to pick a page of the site, or type any URL. A link to a page is stored as its GlobalID (`gid://app/Page/12`) rather than its URL. It's turned into a URL when the content is rendered, so the link survives the page moving or changing its slug (see [Links to records with GlobalID](https://github.com/etaminstudio/prosemirror/blob/main/COOKBOOK.md#links-to-records-with-globalid) in the `prosemirror` gem's cookbook).
|
|
48
|
+
|
|
49
|
+
### Images
|
|
50
|
+
|
|
51
|
+
The image entry of the floating menu opens a panel to upload an image (with Uppy, stored by Shrine's `ModelImageUploader`). The panel also sets its alternative text, caption, credit and size (`default` or `larger`). The node keeps the uploaded file's data, so the image belongs to the content, not to a record.
|
|
52
|
+
|
|
53
|
+
### Prompts
|
|
54
|
+
|
|
55
|
+
The prompt entry inserts a call to action: a text, a link (a page or a URL, as for links), where it opens, and a title.
|
|
56
|
+
|
|
57
|
+
### Panels
|
|
58
|
+
|
|
59
|
+
The link, image and prompt panels are the `Admin::Panels::*` controllers that the installer generates (see [Controllers](/reference/controllers)).
|
|
60
|
+
|
|
61
|
+
## How content is stored
|
|
62
|
+
|
|
63
|
+
The editor saves a ProseMirror document in the form's field, as JSON, with Tiptap's camelCase type names:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"type": "doc",
|
|
68
|
+
"content": [
|
|
69
|
+
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Opening hours" }] },
|
|
70
|
+
{
|
|
71
|
+
"type": "paragraph",
|
|
72
|
+
"content": [
|
|
73
|
+
{ "type": "text", "text": "See the " },
|
|
74
|
+
{ "type": "text", "text": "map", "marks": [{ "type": "link", "attrs": { "href": "gid://app/Page/12" } }] }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{ "type": "imageBlock", "attrs": { "file_data": "{\"id\":\"…\",\"storage\":\"store\"}", "alt": "The entrance", "text": "Main entrance", "credit": "Photo: A. Smith", "size": "larger" } }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- **Column:** Trek's models keep it in a `jsonb` `content` column. [Translatable](/reference/concerns/translatable) models hold one document per locale.
|
|
83
|
+
- **Cleaning:** before saving, [Contentable](/reference/concerns/contentable) removes what rendering skips: empty paragraphs, empty list items, and lists left empty. An empty document keeps one paragraph, and whatever the schema doesn't know is kept. The editor submits the content as a JSON string, and it's stored as a document. Empty paragraphs used for spacing are removed for good.
|
|
84
|
+
- **Typography:** [Formattable](/reference/concerns/formattable) works on documents, changing only their text nodes. Add `:content` to the generated `format_attributes Trek::TypographyFormatter, :title, :description` to apply French typography to it.
|
|
85
|
+
- **Search:** to find content by its words, search the text nodes rather than the JSON, or every document matches `paragraph`. `content_text` gives the plain text, and `Fragment.search` shows a query in SQL (see [Fragment](/reference/models/fragment)).
|
|
86
|
+
|
|
87
|
+
## Rendering on the site
|
|
88
|
+
|
|
89
|
+
A model with a `content` column includes [Contentable](/reference/concerns/contentable):
|
|
90
|
+
|
|
91
|
+
```slim
|
|
92
|
+
/ ERB or Slim
|
|
93
|
+
= @page.formatted_content
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
# Phlex
|
|
98
|
+
render @page.content_document.to_phlex
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For a [versioned](/reference/concerns/versionable) page, render its live version's content, `current_content`, which comes from a `PageVersion`: `Trek::Content::Document.parse(@page.current_version || @page, @page.current_content).to_html`.
|
|
102
|
+
|
|
103
|
+
`formatted_content` only reads the `content` column. To render another column, such as a book's `intro`, parse it with `Trek::Content::Document`. Its first argument is the record holding the content: when that content is invalid, the error reports name the record (`trek_content: "Book#3"`, see [Invalid content](#invalid-content)), so you know which one to fix.
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
Trek::Content::Document.parse(book, book.intro).to_html
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### What the HTML contains
|
|
110
|
+
|
|
111
|
+
- **Escaping:** HTML is escaped as it is built. Nothing a user types can open a tag.
|
|
112
|
+
- **Attributes** are typed: a value that doesn't fit, such as a heading level of 9, is replaced by its default, or removed.
|
|
113
|
+
- **URLs:** links and images only allow `http`, `https`, `mailto`, `tel` and relative URLs.
|
|
114
|
+
- **Links to pages** become the page's URL, through `url_for`. The records a document links to load together, with one query per model. A link to a deleted record is removed, and its text kept.
|
|
115
|
+
- **Empty nodes:** empty paragraphs, empty list items, and lists with only empty items render nothing ([Skip empty paragraphs](https://github.com/etaminstudio/prosemirror/blob/main/COOKBOOK.md#skip-empty-paragraphs)). Saving removes them from the stored content too ([cleaning](#how-content-is-stored)), but content saved before Trek did is only cleaned when it's saved again.
|
|
116
|
+
- **Images** render as a `<figure>` with a `data-size` attribute, with the caption and the credit in its `<figcaption>`.
|
|
117
|
+
- **Prompts** render as a link.
|
|
118
|
+
- **Unknown attributes,** such as heading ids added by the editor, are kept in the JSON but never rendered. To give headings anchors, see [Heading anchors](https://github.com/etaminstudio/prosemirror/blob/main/COOKBOOK.md#heading-anchors) in the `prosemirror` gem's cookbook.
|
|
119
|
+
|
|
120
|
+
### Invalid content
|
|
121
|
+
|
|
122
|
+
A document that doesn't fit the schema is fixed as it's read, and each problem is reported to `Rails.error` (`handled: true`):
|
|
123
|
+
|
|
124
|
+
- an attribute of the wrong type is converted when the meaning is clear, or replaced by its default, or its node or mark is removed (a link keeps its text);
|
|
125
|
+
- a node of an unknown type renders only its content;
|
|
126
|
+
- links to deleted records, reported as `Prosemirror::UnresolvedURL`.
|
|
127
|
+
|
|
128
|
+
The report's context names the record holding the content (`trek_content: "Page#12"`), which AppSignal shows as a tag, so a problem leads back to the page to fix.
|
|
129
|
+
|
|
130
|
+
## Customizing the rendering
|
|
131
|
+
|
|
132
|
+
`Trek::Content.schema` is Tiptap's schema (`Prosemirror::Schema.tiptap(case: :camel)`), with Trek's own node types:
|
|
133
|
+
|
|
134
|
+
| Node type | Role |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `Trek::Content::Paragraph` | skips empty paragraphs |
|
|
137
|
+
| `Trek::Content::ListItem` | skips empty list items, with no content or only empty paragraphs |
|
|
138
|
+
| `Trek::Content::BulletList`, `OrderedList` | skip lists with only empty items |
|
|
139
|
+
| `Trek::Content::ImageBlock` | renders image blocks as a `<figure>`: the image, its caption and credit |
|
|
140
|
+
| `Trek::Content::PromptsBlock` | renders prompt blocks as a link |
|
|
141
|
+
|
|
142
|
+
Node and mark types are [Phlex](https://www.phlex.fun) components with [Literal](https://literal.fun) properties (see the [`prosemirror` README](https://github.com/etaminstudio/prosemirror#custom-nodes-and-marks)). To change how one renders, subclass it, and put it in the schema from an initializer. The cookbook's [Render your own components](https://github.com/etaminstudio/prosemirror/blob/main/COOKBOOK.md#render-your-own-components) shows the pattern.
|
|
143
|
+
|
|
144
|
+
### Images with the app's component
|
|
145
|
+
|
|
146
|
+
For example, images at the app's own sizes:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
# app/content/image_block.rb
|
|
150
|
+
class ImageBlock < Trek::Content::ImageBlock
|
|
151
|
+
def view_template
|
|
152
|
+
return unless image
|
|
153
|
+
|
|
154
|
+
render Components::Figure.new(
|
|
155
|
+
src: image.derivation_url(:thumbnail, 930, 1200),
|
|
156
|
+
alt: @alt,
|
|
157
|
+
caption: @text,
|
|
158
|
+
credit: @credit
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# config/initializers/trek.rb
|
|
166
|
+
Rails.application.config.to_prepare do
|
|
167
|
+
Trek::Content.schema = Trek::Content.default_schema.with(ImageBlock)
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- **Registering:** `with` replaces the type of the same name, and keeps the others.
|
|
172
|
+
- **Reloading:** the default schema is rebuilt after code reloading, so `to_prepare` sees the reloaded classes in development.
|
|
173
|
+
- **Inside a node type:**
|
|
174
|
+
- the node's attributes are instance variables (`@alt`, `@text`);
|
|
175
|
+
- `image` is the uploaded file, or `nil` when the data is missing or invalid;
|
|
176
|
+
- `safe_url(url)` checks a URL against the allowed schemes.
|
|
177
|
+
|
|
178
|
+
### Prompts as the app's buttons
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
class PromptsBlock < Trek::Content::PromptsBlock
|
|
182
|
+
def view_template
|
|
183
|
+
href = @href.present? && safe_url(@href)
|
|
184
|
+
return if !href || @text.blank?
|
|
185
|
+
|
|
186
|
+
render Components::Button.new(href:, target: @target, title: @title) { @text }
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Register it the same way: `Trek::Content.default_schema.with(ImageBlock, PromptsBlock)`.
|
|
192
|
+
|
|
193
|
+
### Node types of the app's own
|
|
194
|
+
|
|
195
|
+
A node of a type the schema doesn't know renders only its content, and is reported. To render it, add a class of that name to the schema (`Callout` for `callout`):
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
class Callout < Prosemirror::NodeType
|
|
199
|
+
group :block
|
|
200
|
+
content "block+"
|
|
201
|
+
|
|
202
|
+
prop :tone, _Union?("info", "warning")
|
|
203
|
+
|
|
204
|
+
def view_template(&) = aside(data_tone: @tone, &)
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Trek's editor has a fixed set of Tiptap extensions, so it can't insert such a node yet. It renders nodes that come from elsewhere: imported content, or JSON written by a script.
|
|
209
|
+
|
|
210
|
+
## Importing content
|
|
211
|
+
|
|
212
|
+
The `prosemirror` gem builds documents from HTML (with the `herb` gem), Markdown (with `commonmarker`) or plain text, using the schema's parse rules. To fill pages from an older site:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
document = Prosemirror.from_html(old_html, schema: Trek::Content.schema)
|
|
216
|
+
page.update!(content: document.to_h)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`to_h` gives the JSON the editor reads, with invalid parts fixed. See the [`prosemirror` README](https://github.com/etaminstudio/prosemirror#html-import) for what each import keeps.
|
data/docs/customize.md
CHANGED
|
@@ -48,6 +48,8 @@ Trek's UI components accept options where it matters:
|
|
|
48
48
|
f.content_editor :intro, nodes: false, floating: false
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
To render content blocks with your own components, see [Customizing the rendering](/content-editor#customizing-the-rendering).
|
|
52
|
+
|
|
51
53
|
- **`Trek::LayoutComponent`** — use the `panel` slot to add your own side panel to admin screens.
|
|
52
54
|
|
|
53
55
|
## Models
|
data/docs/deploy.md
CHANGED
|
@@ -17,7 +17,7 @@ Compared with the stock buildpacks, deploys typically go from around 3 minutes t
|
|
|
17
17
|
Generate the Scalingo files (an `Aptfile` and a `Procfile` with the web process and post-deploy migrations):
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
rails g trek:
|
|
20
|
+
rails g trek:scalingo
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Then configure the Scalingo app's environment, pinning the buildpack to a release through its archive:
|
|
@@ -57,7 +57,7 @@ Before going live, check what Trek expects from the hosting: backups, a shared c
|
|
|
57
57
|
|
|
58
58
|
Apps deployed with the stock `.buildpacks` (apt + ruby) migrate in three steps:
|
|
59
59
|
|
|
60
|
-
1. delete `.buildpacks` (re-running `rails g trek:
|
|
60
|
+
1. delete `.buildpacks` (re-running `rails g trek:scalingo` does it) and check that `.node-version` exists — apps generated with `rails new -j esbuild` have it
|
|
61
61
|
2. set `BUILDPACK_URL` as above, and map `DATABASE_URL` if the app doesn't read it yet
|
|
62
62
|
3. deploy — the first build fills the caches, the next ones benefit from them
|
|
63
63
|
|
data/docs/getting-started.md
CHANGED
|
@@ -132,4 +132,4 @@ Next steps:
|
|
|
132
132
|
|
|
133
133
|
- [Customize Trek](/customize) — branding, favicon, components
|
|
134
134
|
- [Reference manual](/reference/) — generators, models, components and more
|
|
135
|
-
- [Deploying to Scalingo](/deploy) — `trek:
|
|
135
|
+
- [Deploying to Scalingo](/deploy) — `trek:scalingo`, a few environment variables, and the dedicated trek-buildpack handles the rest
|
data/docs/index.md
CHANGED
|
@@ -23,7 +23,7 @@ features:
|
|
|
23
23
|
details: One installer sets up authentication, authorization, file uploads, emails, i18n and a polished admin UI — with sensible conventions you can override later.
|
|
24
24
|
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M6 6H30V30A12 12 0 0 1 18 42H6ZM12 14H24V18H12ZM12 22H20V26H12Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M34 6H42V32A4 4 0 0 1 34 32Z"/></svg>'
|
|
25
25
|
title: Modern content editing
|
|
26
|
-
details: A Tiptap-based
|
|
26
|
+
details: A Tiptap-based content editor, hierarchical pages with versioning and SEO-friendly paths, plus reusable content fragments.
|
|
27
27
|
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M6 18H30V30H42V42H6Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M6 18V6A12 12 0 0 1 18 18Z"/></svg>'
|
|
28
28
|
title: Scaffold-driven development
|
|
29
29
|
details: One command generates the model, admin panel, views, policy, routes and locales for any resource — just like Rails scaffolding, but for your back-office.
|
|
@@ -47,10 +47,10 @@ Rendered by the [form builder](/reference/forms) helpers, under `Trek::Form::`:
|
|
|
47
47
|
|
|
48
48
|
## Content editor
|
|
49
49
|
|
|
50
|
-
`Trek::Form::ContentEditorComponent` wraps the [Tiptap](https://tiptap.dev) editor and accepts
|
|
50
|
+
`Trek::Form::ContentEditorComponent` wraps the [Tiptap](https://tiptap.dev) editor and accepts `nodes`, `blocks` and `floating` (all enabled by default) to toggle the text buttons, the block buttons and the floating menu, and `autofocus`:
|
|
51
51
|
|
|
52
52
|
```ruby
|
|
53
53
|
f.content_editor :intro, nodes: false, floating: false
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Content is stored as ProseMirror JSON and rendered with [`formatted_content`](/reference/concerns/contentable). Image uploads (Uppy), link insertion and prompts are served by the [panel controllers](/reference/controllers).
|
|
56
|
+
Content is stored as ProseMirror JSON and rendered with [`formatted_content`](/reference/concerns/contentable). Image uploads (Uppy), link insertion and prompts are served by the [panel controllers](/reference/controllers). See the [Content editor](/content-editor) guide for its toolbar, how content is stored and rendered, and how to customize it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Trek::Contentable
|
|
2
2
|
|
|
3
|
-
Utilities for
|
|
3
|
+
Utilities for content stored as ProseMirror JSON in a `content` column — the format produced by Trek's [content editor](/content-editor).
|
|
4
4
|
|
|
5
5
|
## Methods
|
|
6
6
|
|
|
@@ -8,8 +8,8 @@ Utilities for rich text stored as ProseMirror JSON in a `content` column — the
|
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| `parsed_content` | the content as a hash (parsing JSON strings if needed) |
|
|
10
10
|
| `content_document` | the content as a `Prosemirror::Document`, to query or render it |
|
|
11
|
-
| `formatted_content` | safe HTML, rendered as described in [
|
|
12
|
-
| `content_text` | plain text
|
|
11
|
+
| `formatted_content` | safe HTML, rendered as described in [Rendering on the site](/content-editor#rendering-on-the-site) |
|
|
12
|
+
| `content_text` | the plain text of the content: blocks separated by a blank line, hard breaks by a newline |
|
|
13
13
|
| `prosemirror_content?` | whether `content` is a valid ProseMirror document |
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
@@ -23,7 +23,13 @@ page.formatted_content # => "<p>Hello <strong>world</strong></p>"
|
|
|
23
23
|
page.content_text # => "Hello world"
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Content in another column renders through the same schema: `Trek::Content::Document.parse(record, record.intro).to_html`. See the [Content editor](/content-editor) guide for the whole path, from the editor to the HTML.
|
|
27
|
+
|
|
28
|
+
## Cleaning before saving
|
|
29
|
+
|
|
30
|
+
Before validation, empty paragraphs, empty list items and lists left empty are removed from the content, as rendering skips them (`Trek::Content::Cleaner`). The document stays one the editor opens: an empty document keeps one paragraph, a list item keeps the first paragraph it needs, and node types or attributes the schema doesn't know are kept. Content submitted as a JSON string, as the content editor sends it, is stored as a document. Translated content is cleaned in the current locale.
|
|
31
|
+
|
|
26
32
|
## Requirements
|
|
27
33
|
|
|
28
34
|
- A `content` jsonb column
|
|
29
|
-
- Used by [Page](/reference/models/page)
|
|
35
|
+
- Used by [Page](/reference/models/page) and [Fragment](/reference/models/fragment)
|
|
@@ -21,4 +21,4 @@ page.valid?
|
|
|
21
21
|
page.title # => "Un titre : voici…"
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Works on plain strings **and** on ProseMirror documents (only `text` nodes are touched; the structure is preserved).
|
|
24
|
+
Works on plain strings **and** on ProseMirror documents (only `text` nodes are touched; the structure is preserved), including a document submitted by the content editor as a JSON string, which comes back as a Hash.
|
|
@@ -9,5 +9,6 @@
|
|
|
9
9
|
| `APPSIGNAL_APP_ENV` | environment name reported to AppSignal (optional, defaults to `RAILS_ENV`) |
|
|
10
10
|
| `DEEPL_AUTH_KEY` | automatic locale translation in the [scaffold generator](/reference/generators/scaffold) |
|
|
11
11
|
| `FEATURE_AUDIT_LOG` | `false` stops the [audit log](/reference/generators/audit-log) recording requests (on by default) |
|
|
12
|
+
| `GOOD_JOB_MAX_THREADS` | number of job threads GoodJob runs, and the share of the database pool reserved for them (optional, defaults to `RAILS_MAX_THREADS`) — see [Background jobs](/background-jobs) |
|
|
12
13
|
| `FEATURE_STORAGE` | `local` (development) or `s3` (production) |
|
|
13
14
|
| `S3_BUCKET`, `S3_REGION`, `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_BASE_URL` | S3 storage configuration for [uploads](/reference/uploaders) |
|
|
@@ -18,36 +18,8 @@ format_attributes Trek::TypographyFormatter, :title
|
|
|
18
18
|
|
|
19
19
|
## Rich text rendering
|
|
20
20
|
|
|
21
|
-
`formatted_content` (from [Contentable](/reference/concerns/contentable)) renders the content editor's ProseMirror JSON to HTML with the [`prosemirror`](https://github.com/etaminstudio/prosemirror) gem. HTML is escaped as it is built, attributes are typed,
|
|
22
|
-
|
|
23
|
-
`Trek::Content.schema` is Tiptap's schema, with:
|
|
24
|
-
|
|
25
|
-
| Node type | Role |
|
|
26
|
-
| --- | --- |
|
|
27
|
-
| `Trek::Content::Paragraph` | skips empty paragraphs |
|
|
28
|
-
| `Trek::Content::ListItem` | skips empty list items, with no content or only empty paragraphs |
|
|
29
|
-
| `Trek::Content::BulletList`, `OrderedList` | skip lists with only empty items |
|
|
30
|
-
| `Trek::Content::ImageBlock` | renders image blocks as a `<figure>`: the image, its caption and credit |
|
|
31
|
-
| `Trek::Content::PromptsBlock` | renders prompt blocks as a link |
|
|
32
|
-
|
|
33
|
-
Links to records are stored as GlobalIDs (`gid://app/Page/1`). `Trek::Content::LinkResolver` turns them into the record's URL with `url_for`, loading the records of a document's links at once, with one query per model; a link to a deleted record is removed and its text kept, and reported to `Rails.error` as a `Prosemirror::UnresolvedURL`. Problems in content are reported with the record holding it in their context (`trek_content: "Page#12"`), which AppSignal shows as a tag.
|
|
21
|
+
`formatted_content` (from [Contentable](/reference/concerns/contentable)) renders the content editor's ProseMirror JSON to HTML with the [`prosemirror`](https://github.com/etaminstudio/prosemirror) gem, through `Trek::Content.schema`: HTML is escaped as it is built, attributes are typed, links and images only allow safe URLs, and links to pages are resolved. See [Rendering on the site](/content-editor#rendering-on-the-site) for what the HTML contains, and how invalid content is reported.
|
|
34
22
|
|
|
35
23
|
### Custom blocks
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```ruby
|
|
40
|
-
# app/content/image_block.rb
|
|
41
|
-
class ImageBlock < Trek::Content::ImageBlock
|
|
42
|
-
def view_template
|
|
43
|
-
render Components::Figure.new(src: image&.url, alt: @alt, caption: @text, credit: @credit)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# config/initializers/trek.rb
|
|
48
|
-
Rails.application.config.to_prepare do
|
|
49
|
-
Trek::Content.schema = Trek::Content.default_schema.with(ImageBlock)
|
|
50
|
-
end
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
The same works for a node type of your own, added to the editor with a Tiptap extension.
|
|
25
|
+
Trek's node types (`Trek::Content::ImageBlock`, `PromptsBlock`…) can be subclassed to render the app's own components, and registered with `Trek::Content.schema = Trek::Content.default_schema.with(…)`: see [Customizing the rendering](/content-editor#customizing-the-rendering).
|
data/docs/reference/forms.md
CHANGED
|
@@ -5,7 +5,7 @@ Trek ships `Trek::FormBuilder`, an extension of [`ViewComponent::Form`](https://
|
|
|
5
5
|
| Helper | Renders |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| `f.group` | a label + input wrapper with errors |
|
|
8
|
-
| `f.content_editor` | the
|
|
8
|
+
| `f.content_editor` | the [content editor](/content-editor), based on Tiptap |
|
|
9
9
|
| `f.image_field` | an image uploader with thumbnail preview (Uppy + Shrine) |
|
|
10
10
|
| `f.sound_field` | an audio file uploader |
|
|
11
11
|
| `f.switch_box` | a toggle switch |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Audit log
|
|
2
2
|
|
|
3
3
|
```sh
|
|
4
|
-
rails g trek:
|
|
4
|
+
rails g trek:audit_log
|
|
5
5
|
bin/rails db:migrate
|
|
6
6
|
```
|
|
7
7
|
|
|
@@ -108,6 +108,6 @@ The rows hold personal data: the author, the IP address, the user agent, and wha
|
|
|
108
108
|
|
|
109
109
|
The `audit_logs` table uses the column names of the audit logs some apps wrote themselves (`actor`, `auditable`, `action`, `http_method`, `path`, `http_status`, `params`, `ip_address`, `user_agent`, `request_id`, `error_class`, `created_at`), so existing rows carry over. In such an app, skip the migration and replace the app's own files:
|
|
110
110
|
|
|
111
|
-
1. Run `rails g trek:
|
|
111
|
+
1. Run `rails g trek:audit_log`, delete the generated `db/migrate/*_trek_audit_logs.rb`, and let the generator overwrite the model, controller, views and policy.
|
|
112
112
|
2. Delete the app's `Auditable` concern, its initializer and its purge job, and update the job's cron entry to `Trek::AuditLog::PurgeJob`.
|
|
113
113
|
3. Move app-specific columns (e.g. a request duration, or a denormalized foreign key) to `audit_attributes`, and their scopes and associations to the `AuditLog` model.
|
|
@@ -15,6 +15,8 @@ Trek provides an install generator, that will:
|
|
|
15
15
|
- create the relevant policies
|
|
16
16
|
- generate and run the relevant migrations
|
|
17
17
|
|
|
18
|
+
It ends with what to do next: create an admin user, start the server, and the generators it leaves for later, to run as the app needs them: [`trek:scaffold`](/reference/generators/scaffold), [`trek:taxonomies`](/reference/generators/taxonomies), [`trek:audit_log`](/reference/generators/audit-log), [`trek:public_accounts`](/reference/generators/public-accounts) and [`trek:scalingo`](/deploy).
|
|
19
|
+
|
|
18
20
|
## Sub-generators
|
|
19
21
|
|
|
20
22
|
The installer orchestrates dedicated sub-generators, all under the `trek:install:*` namespace. Each one can also be run individually, e.g. `rails g trek:install:healthcheck`.
|
|
@@ -32,12 +34,11 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
32
34
|
| Tooling | esbuild, PostCSS, EditorConfig, Procfiles, Makefile, health check endpoint |
|
|
33
35
|
| Linting | Standard, ESLint, Stylelint, Prettier, Slim Lint, Lefthook git hooks |
|
|
34
36
|
| Security | Brakeman, bundler-audit, ruby_audit; HTTPS only (`force_ssl`, with HSTS), a Content Security Policy and the Permissions-Policy header (`trek:install:security_headers`) — see [Security headers](/reference/generators/security-headers) |
|
|
37
|
+
| Jobs | GoodJob as the Active Job backend, replacing Solid Queue — dashboard at `/admin/good_job`, cron in `config/initializers/good_job.rb`, pool sized by `GOOD_JOB_MAX_THREADS` — see [Background jobs](/background-jobs) |
|
|
35
38
|
| Monitoring | AppSignal error tracking and performance monitoring — `config/appsignal.rb`, the push API key in the credentials, and the `AppsignalContext` concern tagging transactions per area and user |
|
|
36
39
|
| Specs | RSpec, and specs for the models Trek installs (`trek:install:model_specs`): `spec/models/page_spec.rb` and `spec/models/user_spec.rb`, with their test data as [Oaken](https://github.com/kaspth/oaken) seed scripts — defaults in `db/seeds/setup/`, scenarios in `db/seeds/test/cases/` loaded with `seed "cases/…"`. They check Trek's behaviour in your app — slugs and paths, user validations and roles — so a customization that breaks it fails your suite; edit or remove the ones you deliberately change. Trek's own suite runs the same files |
|
|
37
40
|
| N+1 detection | Prosopite (development/test), scanning every request — see [Controllers › N+1 detection](/reference/controllers#n-1-detection) |
|
|
38
41
|
| CI | GitLab CI configuration: specs, linters, Brakeman, bundler-audit, and, commented out, a [Software Bill of Materials](#software-bill-of-materials) |
|
|
39
|
-
| Deployment | Scalingo files (`trek:install:scalingo`, run on demand) — see [Deploying to Scalingo](/deploy) |
|
|
40
|
-
| Audit log | The `AuditLog` model recording the admin's requests, and its admin list (`trek:install:audit_log`, run on demand) — see [Audit log](/reference/generators/audit-log) |
|
|
41
42
|
|
|
42
43
|
## ENV variables read by the installer
|
|
43
44
|
|