trek 0.1.24 → 0.1.26
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 +55 -0
- data/CHANGELOG.md +37 -0
- data/CLAUDE.md +1 -0
- data/Gemfile.lock +3 -3
- data/Makefile +1 -0
- data/app/controllers/concerns/trek/filters.rb +52 -0
- data/app/controllers/concerns/trek/localization.rb +13 -1
- data/app/controllers/concerns/trek/scopes.rb +24 -0
- data/app/models/concerns/trek/users/invitable.rb +10 -0
- data/config/i18n-tasks.yml +21 -0
- data/config/locales/trek.en.yml +8 -0
- data/config/locales/trek.fr.yml +18 -3
- data/docs/reference/controllers.md +72 -0
- data/lib/generators/trek/helpers.rb +15 -0
- data/lib/generators/trek/install/agents_generator.rb +26 -0
- data/lib/generators/trek/install/attachments_generator.rb +3 -1
- data/lib/generators/trek/install/authentication_generator.rb +6 -6
- data/lib/generators/trek/install/authorization_generator.rb +4 -2
- data/lib/generators/trek/install/brakeman_generator.rb +4 -2
- data/lib/generators/trek/install/bundler_audit_generator.rb +4 -2
- data/lib/generators/trek/install/config_generator.rb +4 -0
- data/lib/generators/trek/install/healthcheck_generator.rb +4 -2
- data/lib/generators/trek/install/kaminari_generator.rb +4 -2
- data/lib/generators/trek/install/locales_generator.rb +12 -6
- data/lib/generators/trek/install/lookbook_generator.rb +3 -1
- data/lib/generators/trek/install/page_generator.rb +4 -2
- data/lib/generators/trek/install/postmark_generator.rb +5 -3
- data/lib/generators/trek/install/rubocop_generator.rb +4 -2
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +2 -1
- data/lib/generators/trek/install/slim_lint_generator.rb +4 -2
- data/lib/generators/trek/install/specs_generator.rb +3 -2
- data/lib/generators/trek/install/strip_attributes_generator.rb +4 -2
- data/lib/generators/trek/install/translations_generator.rb +3 -1
- data/lib/generators/trek/install/yarn_generator.rb +10 -3
- data/lib/generators/trek/install_generator.rb +7 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +2 -2
- data/lib/generators/trek/templates/AGENTS.md +36 -0
- data/lib/generators/trek/templates/controllers/admin/user_password_resets_controller.rb +4 -1
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +7 -0
- data/lib/generators/trek/templates/mailers/admin/passwords_mailer.rb +1 -1
- data/lib/generators/trek/templates/mailers/admin_mailer.rb +4 -1
- data/lib/generators/trek/templates/views/admin/dashboards/index.html.slim +2 -1
- data/lib/generators/trek/templates/views/admin_mailer/invite.en.html.slim +1 -0
- data/lib/generators/trek/templates/views/admin_mailer/invite.en.text.erb +2 -0
- data/lib/generators/trek/templates/views/admin_mailer/invite.fr.html.slim +1 -0
- data/lib/generators/trek/templates/views/admin_mailer/invite.fr.text.erb +2 -0
- data/lib/trek/version.rb +1 -1
- data/package.json +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a1b779a620ab9d8286b2f42d3164cef475a04d2696e765951e33ef4ac351036
|
|
4
|
+
data.tar.gz: 1485275e1fe7f5bdc5f1d8785de59d8c8e39410bcafe05732e148abf17c4634a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e9492dd289b327e48cd06f9f8d3abd5962c1cf19319bb2fb0bc94bbafc35a1438fef67b80134a56e13400410ffa0504b41518ebe9ad7ca450de4ce2ef35826b
|
|
7
|
+
data.tar.gz: c2b8ecb84e9944bd1477b90b1d98731a8b7403459071de88911b8676eff237c987a04873fb978b486a56e73a676ad35358a4b0dbfa16a8e6ff16d30f6af0d278
|
data/.node-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
24.18.0
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this repository. Keep it accurate — update it when commands or conventions change.
|
|
4
|
+
|
|
5
|
+
## What is Trek
|
|
6
|
+
|
|
7
|
+
Trek is a customizable CMS for Ruby on Rails, shipped as a **Ruby gem** (`trek`) plus a companion **NPM package** (`@etaminstudio/trek`). It's a Rails **engine** (`lib/trek/engine.rb`) that installs a back-office/admin into a host app. Installation and features are driven by Rails generators (`rails g trek:install`, `trek:scaffold`, `trek:admin:user`, …). Authored by Etamin Studio. Private gem hosted on self-hosted GitLab (`git.etaminstud.io`), not public RubyGems.
|
|
8
|
+
|
|
9
|
+
## Tech stack
|
|
10
|
+
|
|
11
|
+
- **Ruby** `>= 3.1` (RuboCop targets 3.4) · **Rails** `>= 6.0, < 8.2` (lockfile 8.1.3; host apps need 7.0+)
|
|
12
|
+
- **Node** 22.21.1 (`.node-version`) · **Yarn** 3.8.0 (berry) · **PostgreSQL** 12+ · **libvips** for images
|
|
13
|
+
- Backend: `action_policy`, `closure_tree`, `acts_as_list`, `mobility` + `i18n-tasks`, `shrine` (+ blurhash/image_processing/aws-sdk-s3), `view_component` + `slim-rails` + `lookbook`, `kaminari`, `deepl-rb`
|
|
14
|
+
- Frontend: **esbuild** (`esbuild.config.js`) + **PostCSS** (`postcss.config.js`); Stimulus/Turbo, TipTap, Uppy, Tom Select, SortableJS
|
|
15
|
+
- Tests: **RSpec** (`rspec-rails`, capybara, simplecov) · Docs: **VitePress** (`docs/`)
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bin/setup # bundle install + yarn install (dev setup)
|
|
21
|
+
bin/console # IRB with trek loaded
|
|
22
|
+
rake # default: spec + rubocop
|
|
23
|
+
rake spec # run tests (or: bundle exec rspec)
|
|
24
|
+
make release # bump version (version.rb + package.json), stamp CHANGELOG, tag, push gem + npm
|
|
25
|
+
yarn docs:dev # VitePress docs locally
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Linters (also run automatically via lefthook on pre-commit):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle exec rubocop
|
|
32
|
+
bundle exec slim-lint "app/**/*.slim"
|
|
33
|
+
yarn eslint "app/**/*.js"
|
|
34
|
+
yarn stylelint "app/**/*.css"
|
|
35
|
+
yarn prettier --check "{app,config,lib,spec}/**/*.{css,js,json,md,yml}"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Security: `bundle exec brakeman` · `bundle exec bundle-audit check --update`
|
|
39
|
+
|
|
40
|
+
## Structure
|
|
41
|
+
|
|
42
|
+
- `app/` — engine code, namespaced under `trek/`: `components/` (ViewComponents), `controllers/`, `forms/`, `models/`, `policies/` (ActionPolicy), `uploaders/` (Shrine), `views/`, `javascript/` (Stimulus)
|
|
43
|
+
- `lib/generators/trek/` — the heart of the gem: `install/` (~30 generators), `scaffold_generator.rb`, `admin/`, `templates/`
|
|
44
|
+
- `lib/trek/` — `engine.rb`, `version.rb`, `i18n_keys_resolver.rb`, `typography_formatter.rb`
|
|
45
|
+
- `config/locales/` — `trek.en.yml`, `trek.fr.yml`
|
|
46
|
+
- `spec/` — RSpec suite · `docs/` — VitePress site
|
|
47
|
+
|
|
48
|
+
## Conventions
|
|
49
|
+
|
|
50
|
+
- **Ruby**: rubocop-rails-omakase + rubocop-rspec (`.rubocop.yml`) — double-quoted strings, line length 120, target 3.4
|
|
51
|
+
- **Slim** linted via slim_lint; **JS/CSS** via ESLint + Stylelint + Prettier 2.8.8
|
|
52
|
+
- **i18n**: keep English + French locales in sync (`config/locales/trek.*.yml`); DeepL auto-translation via `DEEPL_AUTH_KEY`
|
|
53
|
+
- **Versioning**: version lives in both `lib/trek/version.rb` and `package.json` — keep in sync via `make release`
|
|
54
|
+
- **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry.** When the change requires action in the host app on upgrade, add an "Upgrade note" describing what to do. When the change lets host apps remove or simplify existing patches or custom implementations, mention that too (e.g. "you can now drop your custom …").
|
|
55
|
+
- **CI**: GitLab CI (`.gitlab-ci.yml`), stages build → lint → security → test → deploy; docs deploy to Netlify; deps via Renovate
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.26] - 2026-07-02
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `Trek::Localization` now resolves the request locale through an overridable `locale_for_request` method (still `params` → `current_user` → `headers` by default). Apps that need another source can override it and fall back to the default chain with `super` instead of forking the whole concern — e.g. `def locale_for_request = Current.event&.locale.presence || super`. No behavior change for existing consumers.
|
|
8
|
+
- `trek:install:authentication` no longer injects `generates_token_for` declarations into the generated `User`: `:password_reset` (15 min) is provided automatically by Rails' `has_secure_password`, and `:invite` (1 week) now lives in the `Trek::Users::Invitable` concern (which owns the rest of the invite behaviour). **Upgrade note:** existing apps can delete both `generates_token_for` blocks from their `User` — Rails and the concern provide them; customise the reset window via `has_secure_password reset_token: { expires_in: … }` if needed.
|
|
9
|
+
- `trek:install:ruby` now pins **Ruby 4.0.5** (was 4.0.0), and the CI images (repo + generated `.gitlab-ci.yml` template) use the matching `4.0.5` tag. **Upgrade note:** existing apps can set `4.0.5` in `.ruby-version` and bump the `ci-ruby` image tag in `.gitlab-ci.yml`.
|
|
10
|
+
- `trek:install:yarn` now installs **Yarn 4.17.0** (was 3.8.7) and no longer imports the `interactive-tools` plugin, whose `upgrade-interactive` command is built into Yarn 4. Yarn 4 is also what supports the minimal-age-gate exception below. **Upgrade note:** existing apps can upgrade with `yarn set version 4.17.0` followed by `yarn install`, and drop the now-redundant `interactive-tools` plugin.
|
|
11
|
+
- Bumped **Node to 24.18.0** (was 22.21.1) in `.node-version` and the CI images (repo + generated `.gitlab-ci.yml` template, now `ci-ruby/node-24`). **Upgrade note:** existing apps can set `24.18.0` in `.node-version` and bump the `ci-ruby/node-*` image in `.gitlab-ci.yml`.
|
|
12
|
+
- CI now runs `i18n-tasks missing -t diff` (config in `config/i18n-tasks.yml`) so a base-locale key with no translation in another locale fails the build. (Full `health` isn't used: as an engine, Trek's keys are consumed by host apps and many components/templates use relative keys that only resolve there.)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Added the missing French translations that fell back to English: `admin.dialog_component.{title,text}` (the confirmation dialog shown on every destroy action) and `admin.{pages,fragments}.{show.intro,reorder.success,search.alert}`. Also added the English `admin.section`/`admin.sections` flash messages that previously existed only in French. (#194)
|
|
17
|
+
- `trek:install:yarn` now exempts the `@etaminstudio` scope from Yarn's minimal-age gate (`npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in the generated `.yarnrc.yml`), so a freshly published `@etaminstudio/trek` release installs without being quarantined; the gate still applies to public packages. **Upgrade note:** existing apps can add `npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in their `.yarnrc.yml`.
|
|
18
|
+
- `trek:install:locales` no longer generates an invalid `config/initializers/locales.rb` (`I18n.available_locales = [:]` / `I18n.default_locale = :`) when passed no or blank locales. A blank option/env now falls back to the default (`en, fr`), blank entries are dropped, and the list can never end up empty. (#169)
|
|
19
|
+
|
|
20
|
+
## [0.1.25] - 2026-07-01
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Invitation and password-reset emails now tell the recipient when the link expires. Invitations get their own `:invite` token purpose (1 week) — distinct from the 15-minute `:password_reset` token — and the generated `Admin::UserPasswordResetsController` accepts either purpose, so the shared "set your password" form works from both emails. Each mailer reads its window from the model's token definition (`User.token_definitions[:purpose].expires_in`) rather than a hardcoded constant. **Upgrade note:** add `generates_token_for :invite, expires_in: 1.week do password_salt&.last(10) end` to your `User`; have `AdminMailer#invite` issue an `:invite` token (and `include ActionView::Helpers::DateHelper`); and update `Admin::UserPasswordResetsController#set_user_by_token` to try both purposes, e.g. `User.privileged.find_by_token_for(:invite, @token) || User.privileged.find_by_token_for!(:password_reset, @token)`.
|
|
25
|
+
- `trek:install` now generates an `AGENTS.md` (onboarding guidance for AI coding agents: commands, conventions, and a "read the changelog on every upgrade" checklist) plus a `CLAUDE.md` that imports it via `See @AGENTS.md`, so Claude Code and other agents share one source of truth. **Upgrade note:** existing apps can add both files by running `rails g trek:install:agents`.
|
|
26
|
+
- `Trek::Filters` controller concern: filter the index by `params[:filters]` (attribute equality via `where`). Call `apply_filters!` in your `index`.
|
|
27
|
+
- Pedagogical `NotAppliedError` guard on `Trek::Filters` and `Trek::Scopes`: raises (outside production) when the concern is included but `index` never calls `apply_filters!` / `apply_scopes!`.
|
|
28
|
+
- `trek:install:scalingo` now adds `libjemalloc2` to the generated `Aptfile`, enabling the jemalloc allocator to reduce Ruby memory usage on Scalingo.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- The generated dashboard's "search a user" tile now links to the users index (`href: %i[admin users]`) instead of a dead `#`, and gates on `allowed_to: allowed_to?(:index?, User)` so roles that can't reach user management no longer see it (matching the sidebar's Users entry). **Upgrade note:** existing apps can set `href: %i[admin users]` and add `allowed_to: allowed_to?(:index?, User)` on that tile in `app/views/admin/dashboards/index.html.slim`.
|
|
33
|
+
- `trek:install` now points Action Mailer at the same host as the routes' `default_url_options` (driven by `DEFAULT_HOSTNAME`) in the generated `config/initializers/host.rb`, in production only (development and test keep the host from their environment files). Previously the mailer kept Rails' `example.com` default, so invitation and password-reset email links pointed to `https://example.com/...` in production. **Upgrade note:** existing apps can add `ActionMailer::Base.default_url_options = Rails.application.routes.default_url_options if Rails.env.production?` to their `config/initializers/host.rb` (and drop any hardcoded `config.action_mailer.default_url_options` host).
|
|
34
|
+
- The generated `Admin::UsersController` (`trek:install`) seeds a random password in `create_hook`, so users created via the admin form — which never collects one — satisfy `has_secure_password` and the non-null `password_digest` column. Invitees set their real password via the invite email; others via password reset. (Password seeding lives in the controller rather than `Trek::Users::Invitable` so it also covers users created without an invite.) **Upgrade note:** ensure your `Admin::UsersController#create_hook` seeds a password when blank: `@object.password = SecureRandom.alphanumeric(24) if @object.password.blank?`.
|
|
35
|
+
- `trek:install:ci` now sets `POSTGRES_DB` in `.gitlab-ci.yml` from the application name (e.g. `myapp_test`) instead of the hardcoded `project_test`.
|
|
36
|
+
- `trek:install:yarn` now rewrites `bin/setup`'s `yarn install --check-files` to `yarn install`, since `--check-files` is not a valid flag under Yarn berry and was breaking `bin/setup` (and therefore CI).
|
|
37
|
+
- `trek:install` now adds a `!/.env.example` exception to `.gitignore` so the generated `.env.example` template stays tracked (Rails ignores all `/.env*` files by default).
|
|
38
|
+
- `trek:install:postmark` now matches the production.rb SMTP comment with or without the `bin/` prefix, so the Postmark config is injected on Rails 8.0 as well as 8.1 (previously the injection silently did nothing when the anchor did not match).
|
|
39
|
+
|
|
3
40
|
## [0.0.1] - 2023-01-24
|
|
4
41
|
|
|
5
42
|
- Initial release
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
trek (0.1.
|
|
4
|
+
trek (0.1.26)
|
|
5
5
|
action_policy (~> 0.6)
|
|
6
6
|
actioncable
|
|
7
7
|
acts_as_list (~> 1.1)
|
|
@@ -161,7 +161,7 @@ GEM
|
|
|
161
161
|
concurrent-ruby (1.3.7)
|
|
162
162
|
connection_pool (3.0.2)
|
|
163
163
|
content_disposition (1.0.0)
|
|
164
|
-
crass (1.0.
|
|
164
|
+
crass (1.0.7)
|
|
165
165
|
css_parser (3.0.0)
|
|
166
166
|
addressable
|
|
167
167
|
ssrf_filter (~> 1.5)
|
|
@@ -516,7 +516,7 @@ GEM
|
|
|
516
516
|
zeitwerk (>= 2.7)
|
|
517
517
|
xpath (3.2.0)
|
|
518
518
|
nokogiri (~> 1.8)
|
|
519
|
-
yard (0.9.
|
|
519
|
+
yard (0.9.44)
|
|
520
520
|
zeitwerk (2.7.5)
|
|
521
521
|
|
|
522
522
|
PLATFORMS
|
data/Makefile
CHANGED
|
@@ -5,6 +5,7 @@ release:
|
|
|
5
5
|
if [ -z "$$version" ]; then echo "Version cannot be empty"; exit 1; fi; \
|
|
6
6
|
sed -i '' "s/VERSION = \".*\"/VERSION = \"$$version\"/" lib/trek/version.rb; \
|
|
7
7
|
sed -i '' "s/\"version\": \".*\"/\"version\": \"$$version\"/" package.json; \
|
|
8
|
+
awk -v v="$$version" -v d="$$(date +%F)" '!done && /^## \[Unreleased\]/ {print; print ""; print "## [" v "] - " d; done=1; next} {print}' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md; \
|
|
8
9
|
bundle; \
|
|
9
10
|
git add -A; \
|
|
10
11
|
git commit -m "Version $$version"; \
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trek
|
|
4
|
+
module Filters
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# Raised when a controller includes Trek::Filters but its #index never
|
|
8
|
+
# calls apply_filters! — to teach the developer to wire up filtering.
|
|
9
|
+
class NotAppliedError < StandardError
|
|
10
|
+
DEFAULT_MESSAGE = <<~MSG.squish
|
|
11
|
+
Trek::Filters is included but #index never called `apply_filters!`.
|
|
12
|
+
Add `apply_filters!` to your index action (or `@objects = apply_filters(@objects, params[:filters])`).
|
|
13
|
+
See https://trek.etamin.studio/reference/controllers#mixins
|
|
14
|
+
MSG
|
|
15
|
+
|
|
16
|
+
def initialize(message = DEFAULT_MESSAGE) = super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
included do
|
|
20
|
+
# rubocop:disable Rails/LexicallyScopedActionFilter
|
|
21
|
+
after_action :ensure_filters_applied, only: :index
|
|
22
|
+
# rubocop:enable Rails/LexicallyScopedActionFilter
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def apply_filters(scope, filters)
|
|
26
|
+
return scope if filters.blank?
|
|
27
|
+
|
|
28
|
+
filters.each do |filter, value|
|
|
29
|
+
next if value.blank?
|
|
30
|
+
value = nil if value == "nil"
|
|
31
|
+
|
|
32
|
+
scope = scope.where(filter => value)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
scope
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def apply_filters!
|
|
39
|
+
@filters_applied = true
|
|
40
|
+
@objects = apply_filters(@objects, params[:filters])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def ensure_filters_applied
|
|
46
|
+
return if @filters_applied
|
|
47
|
+
return if Rails.env.production?
|
|
48
|
+
|
|
49
|
+
raise NotAppliedError
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -10,13 +10,25 @@ module Trek
|
|
|
10
10
|
|
|
11
11
|
def set_locale
|
|
12
12
|
old_locale = I18n.locale
|
|
13
|
-
I18n.locale =
|
|
13
|
+
I18n.locale = locale_for_request
|
|
14
14
|
|
|
15
15
|
yield
|
|
16
16
|
ensure
|
|
17
17
|
I18n.locale = old_locale
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# The locale a request is answered in. Overridable seam: an app can prepend
|
|
21
|
+
# its own source (e.g. the locale of the event a request belongs to) and
|
|
22
|
+
# fall back to this default chain with `super`, rather than forking the
|
|
23
|
+
# whole concern:
|
|
24
|
+
#
|
|
25
|
+
# def locale_for_request
|
|
26
|
+
# Current.event&.locale.presence || super
|
|
27
|
+
# end
|
|
28
|
+
def locale_for_request
|
|
29
|
+
locale_from_params || locale_from_current_user || locale_from_headers
|
|
30
|
+
end
|
|
31
|
+
|
|
20
32
|
def locale_from_params
|
|
21
33
|
params[:locale]
|
|
22
34
|
end
|
|
@@ -4,6 +4,18 @@ module Trek
|
|
|
4
4
|
module Scopes
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
|
+
# Raised when a controller includes Trek::Scopes but its #index never
|
|
8
|
+
# calls apply_scopes! — to teach the developer to wire up scoping.
|
|
9
|
+
class NotAppliedError < StandardError
|
|
10
|
+
DEFAULT_MESSAGE = <<~MSG.squish
|
|
11
|
+
Trek::Scopes is included but #index never called `apply_scopes!`.
|
|
12
|
+
Add `apply_scopes!` to your index action.
|
|
13
|
+
See https://trek.etamin.studio/reference/controllers#mixins
|
|
14
|
+
MSG
|
|
15
|
+
|
|
16
|
+
def initialize(message = DEFAULT_MESSAGE) = super
|
|
17
|
+
end
|
|
18
|
+
|
|
7
19
|
def apply_scopes(scope, filters)
|
|
8
20
|
return scope if filters.blank?
|
|
9
21
|
|
|
@@ -17,12 +29,17 @@ module Trek
|
|
|
17
29
|
end
|
|
18
30
|
|
|
19
31
|
def apply_scopes!
|
|
32
|
+
@scopes_applied = true
|
|
20
33
|
@unscoped_objects = @objects
|
|
21
34
|
@objects = apply_scopes(@objects, params[:scopes])
|
|
22
35
|
scope_counts
|
|
23
36
|
end
|
|
24
37
|
|
|
25
38
|
included do
|
|
39
|
+
# rubocop:disable Rails/LexicallyScopedActionFilter
|
|
40
|
+
after_action :ensure_scopes_applied, only: :index
|
|
41
|
+
# rubocop:enable Rails/LexicallyScopedActionFilter
|
|
42
|
+
|
|
26
43
|
helper_method :available_scopes
|
|
27
44
|
helper_method :scope_params
|
|
28
45
|
helper_method :unscoped_objects
|
|
@@ -51,5 +68,12 @@ module Trek
|
|
|
51
68
|
def scope_params
|
|
52
69
|
params[:scopes]&.permit(*available_scopes)
|
|
53
70
|
end
|
|
71
|
+
|
|
72
|
+
private def ensure_scopes_applied
|
|
73
|
+
return if @scopes_applied
|
|
74
|
+
return if Rails.env.production?
|
|
75
|
+
|
|
76
|
+
raise NotAppliedError
|
|
77
|
+
end
|
|
54
78
|
end
|
|
55
79
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Trek
|
|
2
4
|
module Users
|
|
3
5
|
module Invitable
|
|
@@ -7,6 +9,14 @@ module Trek
|
|
|
7
9
|
attribute :send_invite, :boolean
|
|
8
10
|
|
|
9
11
|
after_create :send_invite_email, if: :send_invite
|
|
12
|
+
|
|
13
|
+
# Invite links last a week and invalidate once the password is set
|
|
14
|
+
# (password_salt), so a used invite can't be replayed. A distinct,
|
|
15
|
+
# longer-lived purpose than :password_reset. Assumes the including model
|
|
16
|
+
# has has_secure_password.
|
|
17
|
+
generates_token_for :invite, expires_in: 1.week do
|
|
18
|
+
password_salt&.last(10)
|
|
19
|
+
end
|
|
10
20
|
end
|
|
11
21
|
|
|
12
22
|
private
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# i18n-tasks config for the Trek engine.
|
|
2
|
+
#
|
|
3
|
+
# Trek ships translations for host apps, so the usual "unused key" and code
|
|
4
|
+
# scan checks don't apply here (keys are used by generated apps, and many
|
|
5
|
+
# components/templates use relative keys that only resolve in the host). What
|
|
6
|
+
# we do want to guard is locale parity: every key present in the base locale
|
|
7
|
+
# must be translated in the others. `i18n-tasks missing -t diff` enforces that.
|
|
8
|
+
base_locale: en
|
|
9
|
+
locales:
|
|
10
|
+
- en
|
|
11
|
+
- fr
|
|
12
|
+
|
|
13
|
+
data:
|
|
14
|
+
read:
|
|
15
|
+
- config/locales/trek.%{locale}.yml
|
|
16
|
+
|
|
17
|
+
# The gendered resource-label variants (f/m/m_alt) only exist in locales whose
|
|
18
|
+
# models declare a gender; English always resolves to `n` (see i18n_model_gender),
|
|
19
|
+
# so it intentionally omits them.
|
|
20
|
+
ignore_missing:
|
|
21
|
+
- "admin.actions.*_resource.{f,m,m_alt}"
|
data/config/locales/trek.en.yml
CHANGED
|
@@ -155,6 +155,14 @@ en:
|
|
|
155
155
|
search_component:
|
|
156
156
|
search: Search
|
|
157
157
|
search_user: Search for a user with an email
|
|
158
|
+
section:
|
|
159
|
+
destroy:
|
|
160
|
+
confirm: Are you sure you want to delete this section? This operation is irreversible.
|
|
161
|
+
sections:
|
|
162
|
+
create:
|
|
163
|
+
success: Section added
|
|
164
|
+
update:
|
|
165
|
+
success: Section updated
|
|
158
166
|
seo: SEO
|
|
159
167
|
settings: Settings
|
|
160
168
|
user_password_resets:
|
data/config/locales/trek.fr.yml
CHANGED
|
@@ -86,6 +86,9 @@ fr:
|
|
|
86
86
|
index:
|
|
87
87
|
intro: Accès rapide aux fonctions-clés d'administration de votre appli
|
|
88
88
|
title: Raccourcis
|
|
89
|
+
dialog_component:
|
|
90
|
+
text: Cette action est irréversible.
|
|
91
|
+
title: Êtes-vous sûr·e ?
|
|
89
92
|
errors:
|
|
90
93
|
message:
|
|
91
94
|
one: 1 erreur détectée
|
|
@@ -106,6 +109,12 @@ fr:
|
|
|
106
109
|
intro: Gestion des fragments de contenu utilisés sur le site
|
|
107
110
|
new:
|
|
108
111
|
intro: Ajout d'un fragment de contenu au site
|
|
112
|
+
reorder:
|
|
113
|
+
success: Fragments réorganisés
|
|
114
|
+
search:
|
|
115
|
+
alert: Fragment non trouvé avec ce titre
|
|
116
|
+
show:
|
|
117
|
+
intro: Fragment
|
|
109
118
|
update:
|
|
110
119
|
success: Fragment mis à jour
|
|
111
120
|
navigation: Navigation
|
|
@@ -122,6 +131,12 @@ fr:
|
|
|
122
131
|
intro: Gestion des pages du site
|
|
123
132
|
new:
|
|
124
133
|
intro: Ajout d'une page au site
|
|
134
|
+
reorder:
|
|
135
|
+
success: Pages réorganisées
|
|
136
|
+
search:
|
|
137
|
+
alert: Page non trouvée avec ce titre
|
|
138
|
+
show:
|
|
139
|
+
intro: Page
|
|
125
140
|
update:
|
|
126
141
|
success: Page mise à jour
|
|
127
142
|
panels:
|
|
@@ -143,6 +158,9 @@ fr:
|
|
|
143
158
|
success: Action ajoutée
|
|
144
159
|
form:
|
|
145
160
|
title: Action
|
|
161
|
+
passwords_mailer:
|
|
162
|
+
reset:
|
|
163
|
+
subject: Nouveau mot de passe
|
|
146
164
|
search_component:
|
|
147
165
|
search: Rechercher
|
|
148
166
|
search_user: Rechercher un utilisateur par e-mail
|
|
@@ -252,9 +270,6 @@ fr:
|
|
|
252
270
|
admin_mailer:
|
|
253
271
|
invite:
|
|
254
272
|
subject: Votre invitation
|
|
255
|
-
passwords_mailer:
|
|
256
|
-
reset:
|
|
257
|
-
subject: Nouveau mot de passe
|
|
258
273
|
notices:
|
|
259
274
|
authentication_needed: Veuillez vous connecter
|
|
260
275
|
rate_limited: Trop de tentatives, veuillez réessayer plus tard
|
|
@@ -21,3 +21,75 @@ end
|
|
|
21
21
|
|
|
22
22
|
- `include Trek::Pagination` — paginates the index with Kaminari
|
|
23
23
|
- `include Trek::Search` — filters the index by `params[:search]` when the model is [Searchable](/reference/concerns/searchable)
|
|
24
|
+
- `include Trek::Filters` — filters the index by attribute equality (`where(column => value)`) from `params[:filters]`
|
|
25
|
+
- `include Trek::Scopes` — filters the index by **named scopes** from `params[:scopes]`
|
|
26
|
+
|
|
27
|
+
### Filters
|
|
28
|
+
|
|
29
|
+
`Trek::Filters` lets users narrow an index by attribute values. Call `apply_filters!` in your `index` action — it
|
|
30
|
+
rewrites `@objects` from `params[:filters]`, applying a `where(column => value)` per non-blank filter (a value of
|
|
31
|
+
`"nil"` matches `NULL`):
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
module Admin
|
|
35
|
+
class CitiesController < Trek::ResourceController
|
|
36
|
+
include Trek::Filters
|
|
37
|
+
|
|
38
|
+
helper_method :countries
|
|
39
|
+
|
|
40
|
+
def index
|
|
41
|
+
super
|
|
42
|
+
apply_filters!
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def countries
|
|
48
|
+
City.distinct.pluck(:country_code).index_with { |code| I18n.t("countries.#{code}") }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Render the choices with a [`ButtonGroupComponent`](/reference/components), each button linking back to the index
|
|
55
|
+
with the chosen filter:
|
|
56
|
+
|
|
57
|
+
```slim
|
|
58
|
+
= render Trek::ButtonGroupComponent.new do |g|
|
|
59
|
+
- g.with_button text: t("admin.actions.all"), \
|
|
60
|
+
href: [:admin, model], active: params.dig(:filters, :country_code).nil?
|
|
61
|
+
- countries.each do |code, name|
|
|
62
|
+
- g.with_button text: name, \
|
|
63
|
+
href: polymorphic_path([:admin, model], filters: { country_code: code }), \
|
|
64
|
+
active: code == params.dig(:filters, :country_code)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> If a controller includes `Trek::Filters` but its `index` never calls `apply_filters!`, an
|
|
68
|
+
> `after_action` raises `Trek::Filters::NotAppliedError` (outside production) to flag the missing wiring.
|
|
69
|
+
|
|
70
|
+
### Scopes
|
|
71
|
+
|
|
72
|
+
`Trek::Scopes` filters by **named model scopes** rather than raw attributes. Declare the allowed scopes via
|
|
73
|
+
`available_scopes`, then call `apply_scopes!` in your `index`. It also exposes `scope_counts` and
|
|
74
|
+
`unscoped_objects` helpers (the per-scope match counts and the pre-scope collection) for building scope tabs:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
module Admin
|
|
78
|
+
class ArticlesController < Trek::ResourceController
|
|
79
|
+
include Trek::Scopes
|
|
80
|
+
|
|
81
|
+
def index
|
|
82
|
+
super
|
|
83
|
+
apply_scopes!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def available_scopes
|
|
89
|
+
%i[published draft]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Like `Trek::Filters`, forgetting to call `apply_scopes!` raises `Trek::Scopes::NotAppliedError` outside production.
|
|
@@ -63,6 +63,21 @@ module Trek
|
|
|
63
63
|
def install_npm_dependencies(dependencies, dev: false)
|
|
64
64
|
run "yarn add #{dependencies.map { |name, version| "#{name}@#{version}" }.join(" ")} #{dev ? "--dev" : ""}", abort_on_failure: true
|
|
65
65
|
end
|
|
66
|
+
|
|
67
|
+
# Run "bundle install" outside the parent process's bundler environment.
|
|
68
|
+
# The generators run under `bundle exec`, so the child `bundle` process
|
|
69
|
+
# would otherwise auto-load `bundler/setup` against the Gemfile we just
|
|
70
|
+
# modified and raise GemNotFound for the not-yet-installed gem.
|
|
71
|
+
#
|
|
72
|
+
# We use with_original_env (not with_unbundled_env): both restore the
|
|
73
|
+
# pre-`bundle exec` environment, but with_unbundled_env also scrubs every
|
|
74
|
+
# BUNDLE_* var, which drops the credentials (e.g. BUNDLE_GIT__ETAMINSTUD__IO)
|
|
75
|
+
# needed to fetch trek itself from its private git source.
|
|
76
|
+
def bundle_install
|
|
77
|
+
Bundler.with_original_env do
|
|
78
|
+
run "bundle install --quiet", abort_on_failure: true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
66
81
|
end
|
|
67
82
|
end
|
|
68
83
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "rails/generators/actions"
|
|
2
|
+
require "generators/trek/helpers"
|
|
3
|
+
|
|
4
|
+
module Trek
|
|
5
|
+
module Generators
|
|
6
|
+
module Install
|
|
7
|
+
class AgentsGenerator < Rails::Generators::Base
|
|
8
|
+
include Trek::Generators::Helpers
|
|
9
|
+
|
|
10
|
+
desc "Create AGENTS.md and CLAUDE.md guidance files for AI coding agents."
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("../templates", __dir__)
|
|
13
|
+
|
|
14
|
+
def create_agents_md
|
|
15
|
+
copy_file "AGENTS.md", "AGENTS.md"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# CLAUDE.md imports AGENTS.md so Claude Code (which reads CLAUDE.md) and
|
|
19
|
+
# other agents (which read AGENTS.md) share a single source of truth.
|
|
20
|
+
def create_claude_md
|
|
21
|
+
create_file "CLAUDE.md", "See @AGENTS.md\n"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -13,13 +13,15 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../templates", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_gem
|
|
16
|
+
install_needed = %w[aws-sdk-s3 blurhash fastimage image_processing shrine shrine-blurhash].any? { |name| !in_gemfile?(name) }
|
|
17
|
+
|
|
16
18
|
gem "aws-sdk-s3", "~> 1.117" unless in_gemfile?("aws-sdk-s3")
|
|
17
19
|
gem "blurhash", "~> 0.1.7" unless in_gemfile?("blurhash")
|
|
18
20
|
gem "fastimage", "~> 2.2" unless in_gemfile?("fastimage")
|
|
19
21
|
gem "image_processing", "~> 1.8" unless in_gemfile?("image_processing")
|
|
20
22
|
gem "shrine", "~> 3.0" unless in_gemfile?("shrine")
|
|
21
23
|
gem "shrine-blurhash", "~> 0.2.0" unless in_gemfile?("shrine-blurhash")
|
|
22
|
-
|
|
24
|
+
bundle_install if install_needed
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def add_initializer
|
|
@@ -16,8 +16,10 @@ module Trek
|
|
|
16
16
|
source_root File.expand_path("../templates", __dir__)
|
|
17
17
|
|
|
18
18
|
def add_bcrypt_gem
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
return if in_gemfile?("bcrypt")
|
|
20
|
+
|
|
21
|
+
gem "bcrypt", "~> 3.1.7"
|
|
22
|
+
bundle_install
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def copy_admin_authentication_concern
|
|
@@ -64,12 +66,10 @@ module Trek
|
|
|
64
66
|
def user_model_injection
|
|
65
67
|
<<-RUBY
|
|
66
68
|
has_secure_password
|
|
69
|
+
# Token purposes: :password_reset (15 min) comes from has_secure_password;
|
|
70
|
+
# :invite (1 week) comes from Trek::Users::Invitable.
|
|
67
71
|
has_many :user_sessions, dependent: :destroy
|
|
68
72
|
|
|
69
|
-
generates_token_for :password_reset, expires_in: 15.minutes do
|
|
70
|
-
password_salt&.last(10)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
73
|
scope :privileged, -> { where(role: Trek::Users::Roles::PRIVILEGED_ROLES) }
|
|
74
74
|
|
|
75
75
|
RUBY
|
|
@@ -13,8 +13,10 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../templates", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_gem
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
return if in_gemfile?("action_policy")
|
|
17
|
+
|
|
18
|
+
gem "action_policy"
|
|
19
|
+
bundle_install
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def run_install
|
|
@@ -12,8 +12,10 @@ module Trek
|
|
|
12
12
|
source_root File.expand_path("../../../../", __dir__)
|
|
13
13
|
|
|
14
14
|
def add_gem
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
return if in_gemfile?("brakeman")
|
|
16
|
+
|
|
17
|
+
gem "brakeman", group: [:development, :test]
|
|
18
|
+
bundle_install
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -12,8 +12,10 @@ module Trek
|
|
|
12
12
|
source_root File.expand_path("../../../../", __dir__)
|
|
13
13
|
|
|
14
14
|
def add_gem
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
return if in_gemfile?("bundler-audit")
|
|
16
|
+
|
|
17
|
+
gem "bundler-audit", group: [:development, :test]
|
|
18
|
+
bundle_install
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -22,6 +22,10 @@ module Trek
|
|
|
22
22
|
Rails.application.routes.default_url_options[:protocol] = Rails.application.config.force_ssl ? "https" : "http"
|
|
23
23
|
Rails.application.routes.default_url_options[:host] = ENV.fetch("DEFAULT_HOSTNAME", "localhost:3000")
|
|
24
24
|
Rails.application.config.canonical_url = "\#{Rails.application.routes.default_url_options[:protocol]}://\#{Rails.application.routes.default_url_options[:host]}"
|
|
25
|
+
|
|
26
|
+
# Mailers run outside a request, so in production they need the same host for
|
|
27
|
+
# link helpers. Development and test keep the host from their environment files.
|
|
28
|
+
ActionMailer::Base.default_url_options = Rails.application.routes.default_url_options if Rails.env.production?
|
|
25
29
|
RUBY
|
|
26
30
|
end
|
|
27
31
|
end
|
|
@@ -12,8 +12,10 @@ module Trek
|
|
|
12
12
|
source_root File.expand_path("../templates", __dir__)
|
|
13
13
|
|
|
14
14
|
def add_gem
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
return if in_gemfile?("rails-healthcheck")
|
|
16
|
+
|
|
17
|
+
gem "rails-healthcheck"
|
|
18
|
+
bundle_install
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def add_initializer
|
|
@@ -10,8 +10,10 @@ module Trek
|
|
|
10
10
|
desc "Install Kaminari."
|
|
11
11
|
|
|
12
12
|
def add_gem
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
return if in_gemfile?("kaminari")
|
|
14
|
+
|
|
15
|
+
gem "kaminari"
|
|
16
|
+
bundle_install
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
end
|
|
@@ -12,18 +12,24 @@ module Trek
|
|
|
12
12
|
|
|
13
13
|
source_root File.expand_path("../templates", __dir__)
|
|
14
14
|
|
|
15
|
+
DEFAULT_LOCALES = "en, fr".freeze
|
|
16
|
+
|
|
15
17
|
class_option :locales, type: :string,
|
|
16
|
-
desc: "Comma-separated locales to enable, e.g. en,fr (default: en)"
|
|
18
|
+
desc: "Comma-separated locales to enable, e.g. en,fr (default: en, fr)"
|
|
17
19
|
|
|
18
20
|
def set_locales
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
# `.presence` so a blank option/env (e.g. an empty answer) falls back
|
|
22
|
+
# to the default instead of producing an empty, invalid initializer.
|
|
23
|
+
raw = options[:locales].presence || ENV["TREK_LOCALES"].presence || DEFAULT_LOCALES
|
|
24
|
+
@locales = raw.split(",").map(&:strip).reject(&:blank?)
|
|
21
25
|
|
|
22
26
|
invalid = @locales.reject { |locale| valid_locales?(locale) }
|
|
23
|
-
|
|
27
|
+
if invalid.any?
|
|
28
|
+
say "Ignoring unavailable locales: #{invalid.join(", ")}", :yellow
|
|
29
|
+
@locales -= invalid
|
|
30
|
+
end
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
@locales -= invalid
|
|
32
|
+
# Never leave the list empty — that would generate `[:]` / `:`.
|
|
27
33
|
@locales = ["en"] if @locales.empty?
|
|
28
34
|
end
|
|
29
35
|
|
|
@@ -13,11 +13,13 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../../../../", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_gem
|
|
16
|
+
install_needed = %w[lookbook listen actioncable].any? { |name| !in_gemfile?(name) }
|
|
17
|
+
|
|
16
18
|
gem "lookbook", ">= 2.3.13", group: :development unless in_gemfile?("lookbook")
|
|
17
19
|
gem "listen", group: :development unless in_gemfile?("listen")
|
|
18
20
|
gem "actioncable", group: :development unless in_gemfile?("actioncable")
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
bundle_install if install_needed
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
end
|
|
@@ -55,8 +55,10 @@ module Trek
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def install_closure_tree
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
unless in_gemfile?("closure_tree")
|
|
59
|
+
gem "closure_tree", "~> 9.0"
|
|
60
|
+
bundle_install
|
|
61
|
+
end
|
|
60
62
|
rails_command "db:migrate" # `pages` table must exist
|
|
61
63
|
generate "closure_tree:migration Page"
|
|
62
64
|
end
|
|
@@ -13,8 +13,10 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../templates", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_gem
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
return if in_gemfile?("postmark-rails")
|
|
17
|
+
|
|
18
|
+
gem "postmark-rails"
|
|
19
|
+
bundle_install
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def inject_postmark_to_application_mailer
|
|
@@ -28,7 +30,7 @@ module Trek
|
|
|
28
30
|
def inject_postmark_to_config
|
|
29
31
|
inject_into_file "config/environments/production.rb",
|
|
30
32
|
config_injection,
|
|
31
|
-
after:
|
|
33
|
+
after: %r{# Specify outgoing SMTP server\. Remember to add smtp/\* credentials via (?:bin/)?rails credentials:edit\.\n}
|
|
32
34
|
run "rubocop --autocorrect config/environments/production.rb", abort_on_failure: true
|
|
33
35
|
end
|
|
34
36
|
|
|
@@ -12,8 +12,10 @@ module Trek
|
|
|
12
12
|
source_root File.expand_path("../../../../", __dir__)
|
|
13
13
|
|
|
14
14
|
def add_gem
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
return if in_gemfile?("rubocop-rspec")
|
|
16
|
+
|
|
17
|
+
gem "rubocop-rspec", group: [:development, :test], require: false
|
|
18
|
+
bundle_install
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def copy_rubocop_config
|
|
@@ -12,8 +12,10 @@ module Trek
|
|
|
12
12
|
source_root File.expand_path("../../../../", __dir__)
|
|
13
13
|
|
|
14
14
|
def add_gem
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
return if in_gemfile?("slim_lint")
|
|
16
|
+
|
|
17
|
+
gem "slim_lint", group: [:development, :test]
|
|
18
|
+
bundle_install
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def copy_slim_lint_config
|
|
@@ -13,13 +13,14 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../templates", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_gems
|
|
16
|
+
install_needed = %w[rspec-rails capybara rspec_junit_formatter rspec-html-matchers simplecov].any? { |name| !in_gemfile?(name) }
|
|
17
|
+
|
|
16
18
|
gem "rspec-rails", "~> 8.0", group: [:development, :test] unless in_gemfile?("rspec-rails")
|
|
17
19
|
gem "capybara", "~> 3.38", group: [:test] unless in_gemfile?("capybara")
|
|
18
20
|
gem "rspec_junit_formatter", "~> 0.6.0", group: [:test] unless in_gemfile?("rspec_junit_formatter")
|
|
19
21
|
gem "rspec-html-matchers", "~> 0.10.0", group: [:test] unless in_gemfile?("rspec-html-matchers")
|
|
20
22
|
gem "simplecov", "~> 0.22.0", group: [:test] unless in_gemfile?("simplecov")
|
|
21
|
-
|
|
22
|
-
run "bundle install --quiet", abort_on_failure: true
|
|
23
|
+
bundle_install if install_needed
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def run_install
|
|
@@ -12,8 +12,10 @@ module Trek
|
|
|
12
12
|
source_root File.expand_path("../templates", __dir__)
|
|
13
13
|
|
|
14
14
|
def add_gem
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
return if in_gemfile?("strip_attributes")
|
|
16
|
+
|
|
17
|
+
gem "strip_attributes"
|
|
18
|
+
bundle_install
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -13,11 +13,13 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../templates", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_gem
|
|
16
|
+
install_needed = %w[rails-i18n i18n-tasks deepl-rb mobility].any? { |name| !in_gemfile?(name) }
|
|
17
|
+
|
|
16
18
|
gem "rails-i18n", ">= 6.0.0" unless in_gemfile?("rails-i18n")
|
|
17
19
|
gem "i18n-tasks", "~> 1.0" unless in_gemfile?("i18n-tasks")
|
|
18
20
|
gem "deepl-rb", "~> 3.0" unless in_gemfile?("deepl-rb")
|
|
19
21
|
gem "mobility", "~> 1.3.2" unless in_gemfile?("mobility")
|
|
20
|
-
|
|
22
|
+
bundle_install if install_needed
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def run_install
|
|
@@ -5,7 +5,7 @@ module Trek
|
|
|
5
5
|
module Generators
|
|
6
6
|
module Install
|
|
7
7
|
class YarnGenerator < Rails::Generators::Base
|
|
8
|
-
YARN_VERSION = "
|
|
8
|
+
YARN_VERSION = "4.17.0"
|
|
9
9
|
|
|
10
10
|
include Trek::Generators::Helpers
|
|
11
11
|
|
|
@@ -41,8 +41,12 @@ module Trek
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
# The default bin/setup (generated by `rails new -j esbuild`) runs
|
|
45
|
+
# `yarn install --check-files`, but `--check-files` is not a valid flag
|
|
46
|
+
# under Yarn berry (the version we pin above), so it breaks `bin/setup`
|
|
47
|
+
# in CI. Drop the flag.
|
|
48
|
+
def fix_bin_setup
|
|
49
|
+
gsub_file "bin/setup", "yarn install --check-files", "yarn install"
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
private
|
|
@@ -59,6 +63,9 @@ module Trek
|
|
|
59
63
|
npmScopes:
|
|
60
64
|
etaminstudio:
|
|
61
65
|
npmRegistryServer: "https://git.etaminstud.io/api/v4/packages/npm"
|
|
66
|
+
# Our own registry — skip Yarn's minimal-age gate so a freshly
|
|
67
|
+
# published Trek release installs without the quarantine delay.
|
|
68
|
+
npmMinimalAgeGate: 0
|
|
62
69
|
YAML
|
|
63
70
|
end
|
|
64
71
|
|
|
@@ -52,6 +52,9 @@ module Trek
|
|
|
52
52
|
<<~GITIGNORE
|
|
53
53
|
|
|
54
54
|
.DS_Store
|
|
55
|
+
|
|
56
|
+
# Keep the .env template tracked (Rails ignores all /.env* files).
|
|
57
|
+
!/.env.example
|
|
55
58
|
GITIGNORE
|
|
56
59
|
end
|
|
57
60
|
|
|
@@ -121,6 +124,10 @@ module Trek
|
|
|
121
124
|
generate "trek:install:readme"
|
|
122
125
|
end
|
|
123
126
|
|
|
127
|
+
def install_agents
|
|
128
|
+
generate "trek:install:agents"
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
def install_admin
|
|
125
132
|
generate "trek:install:admin"
|
|
126
133
|
generate "trek:install:admin:routes"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# Variables
|
|
5
5
|
variables:
|
|
6
|
-
POSTGRES_DB: "
|
|
6
|
+
POSTGRES_DB: "<%= application_name %>_test"
|
|
7
7
|
POSTGRES_HOST_AUTH_METHOD: "trust"
|
|
8
8
|
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
|
9
9
|
RAILS_ENV: "test"
|
|
@@ -20,7 +20,7 @@ stages:
|
|
|
20
20
|
|
|
21
21
|
# Defaults
|
|
22
22
|
default:
|
|
23
|
-
image: registry.git.etaminstud.io/devops/ci-ruby/node-
|
|
23
|
+
image: registry.git.etaminstud.io/devops/ci-ruby/node-24:4.0.5
|
|
24
24
|
|
|
25
25
|
services:
|
|
26
26
|
- postgres:16
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this repository.
|
|
4
|
+
|
|
5
|
+
## About this project
|
|
6
|
+
|
|
7
|
+
This is a Ruby on Rails application whose back-office/admin is built with **Trek**, a Rails CMS engine. Trek was installed via `rails g trek:install` and provides the admin UI, authentication, authorization, attachments, and base content models. Project-specific work lives in `app/` — admin controllers under `app/controllers/admin/`, plus your own models, policies, and components. Prefer Trek's generators (`rails g trek:scaffold`, `rails g trek:admin:user`, …) over hand-writing admin plumbing.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
make setup # install dependencies (yarn + bin/setup)
|
|
12
|
+
make dev # start the server (overmind)
|
|
13
|
+
make admin # create an admin user
|
|
14
|
+
make seed # seed the database
|
|
15
|
+
make test # run the RSpec suite (alias: make spec)
|
|
16
|
+
make lint # rubocop + slim-lint + eslint + stylelint + prettier
|
|
17
|
+
make fix # auto-correct lint/format issues
|
|
18
|
+
make scan # brakeman + bundle-audit
|
|
19
|
+
|
|
20
|
+
## Conventions
|
|
21
|
+
|
|
22
|
+
- **Ruby**: RuboCop with rubocop-rails-omakase — double-quoted strings, 120-col lines.
|
|
23
|
+
- **Views**: Slim templates + ViewComponents (linted with slim-lint).
|
|
24
|
+
- **Assets**: esbuild + PostCSS; Stimulus/Turbo (Hotwired).
|
|
25
|
+
- **i18n**: keep every locale file in `config/locales/` in sync across languages.
|
|
26
|
+
- **Git hooks**: lefthook auto-formats staged files on pre-commit — let it run.
|
|
27
|
+
|
|
28
|
+
## Upgrading Trek
|
|
29
|
+
|
|
30
|
+
**Whenever you bump the `trek` gem — and its companion `@etaminstudio/trek` npm package — always:**
|
|
31
|
+
|
|
32
|
+
1. **Read Trek's `CHANGELOG.md`** for every version between the installed and target version, not just the latest.
|
|
33
|
+
2. **Apply every "Upgrade note".** These are the manual steps host apps must perform (config tweaks, new initializers, regenerated files, data migrations). Do not skip them — the upgrade is not complete until each is applied.
|
|
34
|
+
3. **Remove now-redundant local code.** Where the changelog says a Trek change lets host apps drop or simplify a local patch, monkey-patch, or custom implementation, delete that code as part of the upgrade.
|
|
35
|
+
4. **Keep versions aligned**: the `trek` gem version and the `@etaminstudio/trek` npm version must match.
|
|
36
|
+
5. **Verify**: run `make lint` and `make test` before committing the upgrade.
|
|
@@ -42,8 +42,11 @@ module Admin
|
|
|
42
42
|
@token = params[:token]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# The same edit/update flow is reached from a password-reset email and from
|
|
46
|
+
# an invitation email, which carry different token purposes (and expiries).
|
|
45
47
|
def set_user_by_token
|
|
46
|
-
@user = User.privileged.find_by_token_for
|
|
48
|
+
@user = User.privileged.find_by_token_for(:invite, @token) ||
|
|
49
|
+
User.privileged.find_by_token_for!(:password_reset, @token)
|
|
47
50
|
rescue ActiveRecord::RecordNotFound
|
|
48
51
|
redirect_to new_admin_user_session_path, alert: t("admin.user_password_resets.not_found")
|
|
49
52
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
@@ -5,6 +5,13 @@ module Admin
|
|
|
5
5
|
@object.role = "editor"
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
# The form never collects a password — created users set their own through
|
|
9
|
+
# the invite/reset email — so seed a random one to satisfy
|
|
10
|
+
# has_secure_password and the non-null password_digest column.
|
|
11
|
+
def create_hook
|
|
12
|
+
@object.password = SecureRandom.alphanumeric(24) if @object.password.blank?
|
|
13
|
+
end
|
|
14
|
+
|
|
8
15
|
def theme
|
|
9
16
|
value = params[:theme].to_s
|
|
10
17
|
Current.user.update!(theme: value) if User.themes.key?(value)
|
|
@@ -4,7 +4,7 @@ class Admin::PasswordsMailer < ApplicationMailer
|
|
|
4
4
|
def reset(user)
|
|
5
5
|
@user = user
|
|
6
6
|
@url = edit_admin_user_password_reset_url(@user.generate_token_for(:password_reset))
|
|
7
|
-
@expiration_delay = distance_of_time_in_words(0,
|
|
7
|
+
@expiration_delay = distance_of_time_in_words(0, User.token_definitions[:password_reset].expires_in)
|
|
8
8
|
|
|
9
9
|
I18n.with_locale(@user.locale || I18n.default_locale) do
|
|
10
10
|
mail to: @user.email
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
class AdminMailer < ApplicationMailer
|
|
2
|
+
include ActionView::Helpers::DateHelper
|
|
3
|
+
|
|
2
4
|
def invite(user)
|
|
3
5
|
@user = user
|
|
4
|
-
@url = edit_admin_user_password_reset_url(@user.generate_token_for(:
|
|
6
|
+
@url = edit_admin_user_password_reset_url(@user.generate_token_for(:invite))
|
|
7
|
+
@expiration_delay = distance_of_time_in_words(0, User.token_definitions[:invite].expires_in)
|
|
5
8
|
|
|
6
9
|
I18n.with_locale(@user.locale || I18n.default_locale) do
|
|
7
10
|
mail to: @user.email
|
data/lib/trek/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trek
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.26
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mohamed Bengrich
|
|
@@ -441,7 +441,9 @@ files:
|
|
|
441
441
|
- ".yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs"
|
|
442
442
|
- ".yarn/releases/yarn-3.8.0.cjs"
|
|
443
443
|
- ".yarnrc.yml"
|
|
444
|
+
- AGENTS.md
|
|
444
445
|
- CHANGELOG.md
|
|
446
|
+
- CLAUDE.md
|
|
445
447
|
- Gemfile
|
|
446
448
|
- Gemfile.lock
|
|
447
449
|
- LICENSE
|
|
@@ -635,6 +637,7 @@ files:
|
|
|
635
637
|
- app/components/trek/toaster_component/toaster_component.js
|
|
636
638
|
- app/controllers/concerns/trek/authentication.rb
|
|
637
639
|
- app/controllers/concerns/trek/authorization.rb
|
|
640
|
+
- app/controllers/concerns/trek/filters.rb
|
|
638
641
|
- app/controllers/concerns/trek/hooks.rb
|
|
639
642
|
- app/controllers/concerns/trek/localization.rb
|
|
640
643
|
- app/controllers/concerns/trek/model.rb
|
|
@@ -727,6 +730,7 @@ files:
|
|
|
727
730
|
- app/views/trek/shared/_resource_heading.html.slim
|
|
728
731
|
- app/views/trek/shared/_resource_index_heading.html.slim
|
|
729
732
|
- config/brakeman.ignore
|
|
733
|
+
- config/i18n-tasks.yml
|
|
730
734
|
- config/locales/trek.en.yml
|
|
731
735
|
- config/locales/trek.fr.yml
|
|
732
736
|
- docs/.vitepress/config.mjs
|
|
@@ -786,6 +790,7 @@ files:
|
|
|
786
790
|
- lib/generators/trek/install/admin/user_sessions_generator.rb
|
|
787
791
|
- lib/generators/trek/install/admin/users_generator.rb
|
|
788
792
|
- lib/generators/trek/install/admin_generator.rb
|
|
793
|
+
- lib/generators/trek/install/agents_generator.rb
|
|
789
794
|
- lib/generators/trek/install/application_generator.rb
|
|
790
795
|
- lib/generators/trek/install/assets_generator.rb
|
|
791
796
|
- lib/generators/trek/install/attachments_generator.rb
|
|
@@ -832,6 +837,7 @@ files:
|
|
|
832
837
|
- lib/generators/trek/scaffold_generator.rb
|
|
833
838
|
- lib/generators/trek/taxonomies_generator.rb
|
|
834
839
|
- lib/generators/trek/templates/.gitlab-ci.yml
|
|
840
|
+
- lib/generators/trek/templates/AGENTS.md
|
|
835
841
|
- lib/generators/trek/templates/Makefile
|
|
836
842
|
- lib/generators/trek/templates/README.md
|
|
837
843
|
- lib/generators/trek/templates/components/index.js
|