trek 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +55 -0
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +1 -0
- data/Gemfile.lock +6 -6
- data/Makefile +1 -0
- data/app/components/trek/form/actions_component/actions_component.css +2 -2
- data/app/controllers/concerns/trek/authentication.rb +1 -1
- data/app/controllers/concerns/trek/filters.rb +52 -0
- data/app/controllers/concerns/trek/localization.rb +1 -1
- data/app/controllers/concerns/trek/scopes.rb +24 -0
- data/app/helpers/trek/theme_helper.rb +2 -2
- data/app/models/concerns/trek/users/invitable.rb +4 -3
- data/config/locales/trek.en.yml +9 -6
- data/config/locales/trek.fr.yml +9 -6
- data/docs/.vitepress/config.mjs +5 -0
- data/docs/reference/controllers.md +72 -0
- data/lib/generators/trek/helpers.rb +15 -0
- data/lib/generators/trek/install/admin/user_password_resets_generator.rb +0 -4
- data/lib/generators/trek/install/admin/user_sessions_generator.rb +0 -7
- data/lib/generators/trek/install/admin/users_generator.rb +5 -1
- 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 +70 -1
- 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 +10 -7
- data/lib/generators/trek/install/lookbook_generator.rb +3 -1
- data/lib/generators/trek/install/mailer_generator.rb +14 -9
- 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/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/user_generator.rb +27 -1
- data/lib/generators/trek/install/yarn_generator.rb +8 -0
- data/lib/generators/trek/install_generator.rb +11 -4
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/AGENTS.md +36 -0
- data/lib/generators/trek/templates/controllers/admin/base_controller.rb +1 -0
- data/lib/generators/trek/templates/controllers/admin/user_password_resets_controller.rb +26 -27
- data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +13 -20
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +13 -0
- data/lib/generators/trek/templates/controllers/concerns/trek/authentication.rb +62 -0
- data/lib/generators/trek/templates/layouts/admin.html.slim +1 -1
- data/lib/generators/trek/templates/mailers/admin/passwords_mailer.rb +13 -0
- data/lib/generators/trek/templates/mailers/admin_mailer.rb +13 -0
- data/lib/generators/trek/templates/migrations/create_users.rb.erb +11 -0
- data/lib/generators/trek/templates/migrations/user_sessions.rb.erb +11 -0
- data/lib/generators/trek/templates/migrations/users.rb.erb +2 -0
- data/lib/generators/trek/templates/models/current.rb +3 -0
- data/lib/generators/trek/templates/models/user.rb +2 -0
- data/lib/generators/trek/templates/models/user_session.rb +3 -0
- data/lib/generators/trek/templates/views/admin/dashboards/index.html.slim +2 -1
- data/lib/generators/trek/templates/views/admin/pages/_form.html.slim +0 -14
- data/lib/generators/trek/templates/views/admin/passwords_mailer/reset.en.html.slim +10 -0
- data/lib/generators/trek/templates/views/{user_mailer/reset_password_email.en.text.erb → admin/passwords_mailer/reset.en.text.erb} +3 -1
- data/lib/generators/trek/templates/views/admin/passwords_mailer/reset.fr.html.slim +10 -0
- data/lib/generators/trek/templates/views/admin/passwords_mailer/reset.fr.text.erb +11 -0
- data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +7 -7
- data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +5 -2
- data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +12 -3
- data/lib/generators/trek/templates/views/admin/users/show.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/invite.en.html.slim +9 -0
- data/lib/generators/trek/templates/views/{user_mailer/invite_email.en.text.erb → admin_mailer/invite.en.text.erb} +3 -1
- data/lib/generators/trek/templates/views/admin_mailer/invite.fr.html.slim +9 -0
- data/lib/generators/trek/templates/views/{user_mailer/reset_password_email.fr.text.erb → admin_mailer/invite.fr.text.erb} +3 -1
- data/lib/trek/version.rb +1 -1
- data/package.json +3 -2
- data/yarn.lock +1807 -2486
- metadata +22 -12
- data/app/models/trek/user_password_reset.rb +0 -12
- data/lib/generators/trek/templates/mailers/user_mailer.rb +0 -21
- data/lib/generators/trek/templates/views/user_mailer/invite_email.en.html.slim +0 -29
- data/lib/generators/trek/templates/views/user_mailer/invite_email.fr.html.slim +0 -29
- data/lib/generators/trek/templates/views/user_mailer/invite_email.fr.text.erb +0 -9
- data/lib/generators/trek/templates/views/user_mailer/reset_password_email.en.html.slim +0 -7
- data/lib/generators/trek/templates/views/user_mailer/reset_password_email.fr.html.slim +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71867750da0cef4ae1928a4245e69742d7d32c6614c54da4d107f992da5e0e23
|
|
4
|
+
data.tar.gz: 71cb0d0b2ce4098030705245aaf92b50eefec11b4491bc0180e145eeb2f6a8e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e3a672cb3ebf8abee38d436832d92d3cc5c8bd5502f37344bda47275d4bd090a7326920cd823ed84e30990cd9feb453dddc2e62bfc1d43967de230498fd8287
|
|
7
|
+
data.tar.gz: aa4c5e9ee652804fc0361b8ac9e459863f67ff42441484e26fd8387312d8ba70517809b04d81fb68d9b9cab0ea6e6c7a27b943486c80c94b1544678841806fab
|
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,25 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.25] - 2026-07-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- 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)`.
|
|
8
|
+
- `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`.
|
|
9
|
+
- `Trek::Filters` controller concern: filter the index by `params[:filters]` (attribute equality via `where`). Call `apply_filters!` in your `index`.
|
|
10
|
+
- 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!`.
|
|
11
|
+
- `trek:install:scalingo` now adds `libjemalloc2` to the generated `Aptfile`, enabling the jemalloc allocator to reduce Ruby memory usage on Scalingo.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- 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`.
|
|
16
|
+
- `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).
|
|
17
|
+
- 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?`.
|
|
18
|
+
- `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`.
|
|
19
|
+
- `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).
|
|
20
|
+
- `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).
|
|
21
|
+
- `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).
|
|
22
|
+
|
|
3
23
|
## [0.0.1] - 2023-01-24
|
|
4
24
|
|
|
5
25
|
- 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.25)
|
|
5
5
|
action_policy (~> 0.6)
|
|
6
6
|
actioncable
|
|
7
7
|
acts_as_list (~> 1.1)
|
|
@@ -158,10 +158,10 @@ GEM
|
|
|
158
158
|
activerecord (>= 7.2.0)
|
|
159
159
|
with_advisory_lock (>= 7.0.0)
|
|
160
160
|
zeitwerk (~> 2.7)
|
|
161
|
-
concurrent-ruby (1.3.
|
|
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)
|
|
@@ -298,9 +298,9 @@ GEM
|
|
|
298
298
|
net-smtp (0.5.1)
|
|
299
299
|
net-protocol
|
|
300
300
|
nio4r (2.7.5)
|
|
301
|
-
nokogiri (1.19.
|
|
301
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
302
302
|
racc (~> 1.4)
|
|
303
|
-
nokogiri (1.19.
|
|
303
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
304
304
|
racc (~> 1.4)
|
|
305
305
|
parallel (1.27.0)
|
|
306
306
|
parser (3.3.10.0)
|
|
@@ -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
|
|
@@ -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,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Trek
|
|
2
4
|
module Users
|
|
3
5
|
module Invitable
|
|
4
6
|
extend ActiveSupport::Concern
|
|
5
7
|
|
|
6
8
|
included do
|
|
7
|
-
|
|
9
|
+
attribute :send_invite, :boolean
|
|
8
10
|
|
|
9
11
|
after_create :send_invite_email, if: :send_invite
|
|
10
12
|
end
|
|
@@ -12,8 +14,7 @@ module Trek
|
|
|
12
14
|
private
|
|
13
15
|
|
|
14
16
|
def send_invite_email
|
|
15
|
-
|
|
16
|
-
UserMailer.invite_email(self).deliver_later
|
|
17
|
+
AdminMailer.invite(self).deliver_later
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
end
|
data/config/locales/trek.en.yml
CHANGED
|
@@ -149,17 +149,20 @@ en:
|
|
|
149
149
|
success: Action added
|
|
150
150
|
form:
|
|
151
151
|
title: Action
|
|
152
|
+
passwords_mailer:
|
|
153
|
+
reset:
|
|
154
|
+
subject: Reset your password
|
|
152
155
|
search_component:
|
|
153
156
|
search: Search
|
|
154
157
|
search_user: Search for a user with an email
|
|
155
158
|
seo: SEO
|
|
156
159
|
settings: Settings
|
|
157
160
|
user_password_resets:
|
|
161
|
+
not_found: Invalid or expired password reset link.
|
|
158
162
|
create:
|
|
159
163
|
success: A password recovery email has been sent to you
|
|
160
164
|
new:
|
|
161
165
|
remembered_password: I found my password!
|
|
162
|
-
title: Reset password
|
|
163
166
|
update:
|
|
164
167
|
success: Your password has been changed
|
|
165
168
|
user_sessions:
|
|
@@ -170,7 +173,6 @@ en:
|
|
|
170
173
|
success: You are now logged out
|
|
171
174
|
new:
|
|
172
175
|
forgot_password: I forgot my password
|
|
173
|
-
title: Log in
|
|
174
176
|
users:
|
|
175
177
|
create:
|
|
176
178
|
success: User added
|
|
@@ -248,8 +250,9 @@ en:
|
|
|
248
250
|
auto: auto
|
|
249
251
|
dark: dark
|
|
250
252
|
light: light
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
admin_mailer:
|
|
254
|
+
invite:
|
|
253
255
|
subject: Your invitation
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
notices:
|
|
257
|
+
authentication_needed: Please log in
|
|
258
|
+
rate_limited: Too many attempts, please try again later
|
data/config/locales/trek.fr.yml
CHANGED
|
@@ -143,6 +143,9 @@ fr:
|
|
|
143
143
|
success: Action ajoutée
|
|
144
144
|
form:
|
|
145
145
|
title: Action
|
|
146
|
+
passwords_mailer:
|
|
147
|
+
reset:
|
|
148
|
+
subject: Nouveau mot de passe
|
|
146
149
|
search_component:
|
|
147
150
|
search: Rechercher
|
|
148
151
|
search_user: Rechercher un utilisateur par e-mail
|
|
@@ -157,11 +160,11 @@ fr:
|
|
|
157
160
|
seo: SEO
|
|
158
161
|
settings: Paramètres
|
|
159
162
|
user_password_resets:
|
|
163
|
+
not_found: Lien de réinitialisation de mot de passe invalide ou expiré.
|
|
160
164
|
create:
|
|
161
165
|
success: Un e-mail de récupération de mot de passe vous a été envoyé
|
|
162
166
|
new:
|
|
163
167
|
remembered_password: J'ai retrouvé mon mot de passe !
|
|
164
|
-
title: Mot de passe oublié
|
|
165
168
|
update:
|
|
166
169
|
success: Votre mot de passe a été modifié
|
|
167
170
|
user_sessions:
|
|
@@ -172,7 +175,6 @@ fr:
|
|
|
172
175
|
success: Vous êtes maintenant déconnecté·e
|
|
173
176
|
new:
|
|
174
177
|
forgot_password: J'ai oublié mon mot de passe
|
|
175
|
-
title: Connexion
|
|
176
178
|
users:
|
|
177
179
|
create:
|
|
178
180
|
success: Utilisateur ajouté
|
|
@@ -250,8 +252,9 @@ fr:
|
|
|
250
252
|
auto: auto
|
|
251
253
|
dark: sombre
|
|
252
254
|
light: clair
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
admin_mailer:
|
|
256
|
+
invite:
|
|
255
257
|
subject: Votre invitation
|
|
256
|
-
|
|
257
|
-
|
|
258
|
+
notices:
|
|
259
|
+
authentication_needed: Veuillez vous connecter
|
|
260
|
+
rate_limited: Trop de tentatives, veuillez réessayer plus tard
|
data/docs/.vitepress/config.mjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { defineConfig } from 'vitepress'
|
|
2
|
+
import llmstxt from 'vitepress-plugin-llms'
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
// https://vitepress.dev/reference/site-config
|
|
4
6
|
export default defineConfig({
|
|
7
|
+
vite: {
|
|
8
|
+
plugins: [llmstxt()],
|
|
9
|
+
},
|
|
5
10
|
title: "Trek",
|
|
6
11
|
description: "A modern content management & back-office system for Ruby on Rails",
|
|
7
12
|
lang: 'en-US',
|
|
@@ -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
|
|
@@ -12,10 +12,6 @@ module Trek
|
|
|
12
12
|
|
|
13
13
|
source_root File.expand_path("../../templates", __dir__)
|
|
14
14
|
|
|
15
|
-
def add_routes
|
|
16
|
-
route "resources :user_password_resets, only: %i[new create edit update]", namespace: :admin
|
|
17
|
-
end
|
|
18
|
-
|
|
19
15
|
def copy_controller
|
|
20
16
|
template "controllers/admin/user_password_resets_controller.rb",
|
|
21
17
|
"app/controllers/admin/user_password_resets_controller.rb"
|
|
@@ -12,13 +12,6 @@ module Trek
|
|
|
12
12
|
|
|
13
13
|
source_root File.expand_path("../../templates", __dir__)
|
|
14
14
|
|
|
15
|
-
def add_routes
|
|
16
|
-
route "resources :user_sessions, only: %i[new create destroy]", namespace: :admin
|
|
17
|
-
|
|
18
|
-
route 'get :login, to: "user_sessions#new", as: :login', namespace: :admin
|
|
19
|
-
route 'delete :logout, to: "user_sessions#destroy", as: :logout', namespace: :admin
|
|
20
|
-
end
|
|
21
|
-
|
|
22
15
|
def copy_controller
|
|
23
16
|
template "controllers/admin/user_sessions_controller.rb",
|
|
24
17
|
"app/controllers/admin/user_sessions_controller.rb"
|
|
@@ -13,7 +13,11 @@ module Trek
|
|
|
13
13
|
source_root File.expand_path("../../templates", __dir__)
|
|
14
14
|
|
|
15
15
|
def add_routes
|
|
16
|
-
route
|
|
16
|
+
route <<~RUBY, namespace: :admin
|
|
17
|
+
resources :users do
|
|
18
|
+
patch :theme, on: :collection
|
|
19
|
+
end
|
|
20
|
+
RUBY
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def copy_controller
|
|
@@ -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
|