plutonium 0.65.0 → 0.66.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/.claude/skills/plutonium/SKILL.md +43 -43
- data/.claude/skills/plutonium-app/SKILL.md +101 -59
- data/.claude/skills/plutonium-async-interactions/SKILL.md +19 -19
- data/.claude/skills/plutonium-auth/SKILL.md +119 -53
- data/.claude/skills/plutonium-behavior/SKILL.md +110 -78
- data/.claude/skills/plutonium-dashboard/SKILL.md +11 -4
- data/.claude/skills/plutonium-kanban/SKILL.md +81 -49
- data/.claude/skills/plutonium-resource/SKILL.md +144 -133
- data/.claude/skills/plutonium-tenancy/SKILL.md +104 -55
- data/.claude/skills/plutonium-testing/SKILL.md +130 -33
- data/.claude/skills/plutonium-ui/SKILL.md +151 -96
- data/.claude/skills/plutonium-wizard/SKILL.md +93 -82
- data/CHANGELOG.md +21 -0
- data/README.md +9 -9
- data/SECURITY.md +1 -1
- data/app/assets/plutonium.css +1 -1
- data/docs/.vitepress/sync-skills.mjs +6 -3
- data/docs/blog/introducing-plutonium-dashboards.md +4 -5
- data/docs/blog/introducing-plutonium-i18n.md +4 -5
- data/docs/getting-started/installation.md +5 -5
- data/docs/getting-started/tutorial/02-first-resource.md +3 -3
- data/docs/getting-started/tutorial/03-authentication.md +7 -7
- data/docs/getting-started/tutorial/04-authorization.md +21 -4
- data/docs/getting-started/tutorial/05-custom-actions.md +2 -2
- data/docs/getting-started/tutorial/06-nested-resources.md +5 -2
- data/docs/getting-started/tutorial/07-author-portal.md +2 -2
- data/docs/getting-started/tutorial/08-customizing-ui.md +45 -30
- data/docs/getting-started/tutorial/index.md +1 -1
- data/docs/guides/adding-resources.md +10 -7
- data/docs/guides/authentication.md +25 -25
- data/docs/guides/authorization.md +24 -24
- data/docs/guides/creating-packages.md +17 -17
- data/docs/guides/custom-actions.md +32 -32
- data/docs/guides/customizing-ui.md +29 -26
- data/docs/guides/dashboards.md +1 -1
- data/docs/guides/index.md +3 -3
- data/docs/guides/kanban.md +55 -55
- data/docs/guides/multi-tenancy.md +35 -22
- data/docs/guides/nested-resources.md +21 -21
- data/docs/guides/performance.md +3 -3
- data/docs/guides/search-filtering.md +13 -13
- data/docs/guides/testing.md +16 -12
- data/docs/guides/theming.md +32 -17
- data/docs/guides/troubleshooting.md +2 -2
- data/docs/guides/user-invites.md +17 -17
- data/docs/guides/user-profile.md +51 -24
- data/docs/guides/wizards.md +55 -55
- data/docs/reference/app/generators.md +22 -22
- data/docs/reference/app/index.md +15 -18
- data/docs/reference/app/packages.md +8 -8
- data/docs/reference/app/portals.md +75 -29
- data/docs/reference/auth/accounts.md +15 -15
- data/docs/reference/auth/index.md +12 -12
- data/docs/reference/auth/profile.md +67 -29
- data/docs/reference/behavior/async-interactions.md +24 -24
- data/docs/reference/behavior/controllers.md +28 -28
- data/docs/reference/behavior/index.md +5 -5
- data/docs/reference/behavior/interactions.md +44 -44
- data/docs/reference/behavior/policies.md +48 -28
- data/docs/reference/configuration.md +6 -6
- data/docs/reference/dashboard/dsl.md +2 -2
- data/docs/reference/dashboard/index.md +1 -1
- data/docs/reference/generators/lite.md +7 -7
- data/docs/reference/i18n.md +23 -0
- data/docs/reference/index.md +1 -1
- data/docs/reference/kanban/authorization.md +9 -9
- data/docs/reference/kanban/dsl.md +32 -32
- data/docs/reference/kanban/index.md +1 -1
- data/docs/reference/kanban/positioning.md +17 -15
- data/docs/reference/resource/actions.md +51 -51
- data/docs/reference/resource/definition.md +73 -73
- data/docs/reference/resource/export.md +6 -6
- data/docs/reference/resource/index.md +16 -16
- data/docs/reference/resource/model.md +24 -24
- data/docs/reference/resource/positioning.md +78 -76
- data/docs/reference/resource/query.md +13 -13
- data/docs/reference/tenancy/entity-scoping.md +65 -35
- data/docs/reference/tenancy/index.md +11 -11
- data/docs/reference/tenancy/invites.md +20 -20
- data/docs/reference/tenancy/nested-resources.md +13 -13
- data/docs/reference/testing/index.md +116 -22
- data/docs/reference/ui/assets.md +57 -25
- data/docs/reference/ui/components.md +20 -20
- data/docs/reference/ui/displays.md +14 -14
- data/docs/reference/ui/forms.md +35 -35
- data/docs/reference/ui/index.md +17 -15
- data/docs/reference/ui/layouts.md +21 -21
- data/docs/reference/ui/pages.md +22 -22
- data/docs/reference/ui/tables.md +7 -7
- data/docs/reference/wizard/anchoring-resume.md +33 -32
- data/docs/reference/wizard/dsl.md +44 -44
- data/docs/reference/wizard/index.md +6 -6
- data/docs/reference/wizard/one-time.md +18 -18
- data/docs/reference/wizard/registration-launch.md +32 -32
- data/docs/reference/wizard/storage-config.md +23 -23
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/profile/conn_generator.rb +6 -0
- data/lib/plutonium/resource/record/associated_with.rb +23 -2
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +7 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/src/css/components.css +10 -10
- metadata +2 -2
data/docs/reference/app/index.md
CHANGED
|
@@ -4,9 +4,9 @@ How a Plutonium app is assembled: installation, the package system (feature vs p
|
|
|
4
4
|
|
|
5
5
|
## Sub-pages
|
|
6
6
|
|
|
7
|
-
- [Packages](./packages)
|
|
8
|
-
- [Portals](./portals)
|
|
9
|
-
- [Generators](./generators)
|
|
7
|
+
- [Packages](./packages): feature vs portal packages, structure, namespacing, package loading
|
|
8
|
+
- [Portals](./portals): portal engines, mounting, controller concerns, `register_resource` (including singular and custom routes), connecting resources via `pu:res:conn`
|
|
9
|
+
- [Generators](./generators): full `pu:*` generator catalog
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -30,7 +30,7 @@ The `plutonium.rb` template re-runs the full app bootstrap (dotenv, annotate, so
|
|
|
30
30
|
bin/rails app:template \
|
|
31
31
|
LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
|
|
32
32
|
|
|
33
|
-
# Or manual
|
|
33
|
+
# Or manual: add `gem "plutonium"` to Gemfile, then:
|
|
34
34
|
bundle install
|
|
35
35
|
rails generate pu:core:install
|
|
36
36
|
```
|
|
@@ -38,7 +38,7 @@ rails generate pu:core:install
|
|
|
38
38
|
## Full setup workflow
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
# 1. Core install
|
|
41
|
+
# 1. Core install: base controllers, policies, definitions, layouts
|
|
42
42
|
rails generate pu:core:install
|
|
43
43
|
|
|
44
44
|
# 2. Auth (if needed)
|
|
@@ -55,10 +55,7 @@ rails db:prepare
|
|
|
55
55
|
# 5. Connect resource to portal
|
|
56
56
|
rails generate pu:res:conn Post --dest=admin_portal
|
|
57
57
|
|
|
58
|
-
# 6.
|
|
59
|
-
# mount AdminPortal::Engine, at: "/admin"
|
|
60
|
-
|
|
61
|
-
# 7. Start
|
|
58
|
+
# 6. Start (step 3 already mounted the portal at /admin)
|
|
62
59
|
bin/dev # uses Procfile to run Rails + CSS watcher
|
|
63
60
|
```
|
|
64
61
|
|
|
@@ -70,10 +67,10 @@ Visit `http://localhost:3000/admin`.
|
|
|
70
67
|
app/
|
|
71
68
|
├── controllers/
|
|
72
69
|
│ ├── plutonium_controller.rb # non-resource base
|
|
73
|
-
│ └── resource_controller.rb # CRUD base
|
|
70
|
+
│ └── resource_controller.rb # CRUD base; see Behavior › Controllers
|
|
74
71
|
├── definitions/resource_definition.rb
|
|
75
72
|
├── interactions/resource_interaction.rb
|
|
76
|
-
├── models/resource_record.rb # abstract model
|
|
73
|
+
├── models/resource_record.rb # abstract model: includes Plutonium::Resource::Record
|
|
77
74
|
├── policies/resource_policy.rb
|
|
78
75
|
└── views/layouts/resource.html.erb
|
|
79
76
|
|
|
@@ -103,7 +100,7 @@ Plutonium.configure do |config|
|
|
|
103
100
|
# :classic preserves the legacy header + sidebar (only when upgrading).
|
|
104
101
|
# config.shell = :classic
|
|
105
102
|
|
|
106
|
-
# Custom assets
|
|
103
|
+
# Custom assets; see UI › Assets
|
|
107
104
|
# config.assets.stylesheet = "custom_stylesheet"
|
|
108
105
|
# config.assets.script = "custom_script"
|
|
109
106
|
# config.assets.logo = "custom_logo.png"
|
|
@@ -150,9 +147,9 @@ Meta-generators (`pu:saas:setup`) propagate these flags to the generators they c
|
|
|
150
147
|
|
|
151
148
|
## Related
|
|
152
149
|
|
|
153
|
-
- [Packages](./packages)
|
|
154
|
-
- [Portals](./portals)
|
|
155
|
-
- [Generators](./generators)
|
|
156
|
-
- [Auth](/reference/auth/)
|
|
157
|
-
- [UI › Assets](/reference/ui/assets)
|
|
158
|
-
- [Tutorial](/getting-started/tutorial/)
|
|
150
|
+
- [Packages](./packages): feature vs portal package structure
|
|
151
|
+
- [Portals](./portals): portal engines, routing, resource connection
|
|
152
|
+
- [Generators](./generators): full generator reference
|
|
153
|
+
- [Auth](/reference/auth/): Rodauth setup and account types
|
|
154
|
+
- [UI › Assets](/reference/ui/assets): Tailwind, Stimulus, design tokens
|
|
155
|
+
- [Tutorial](/getting-started/tutorial/): step-by-step walkthrough
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Packages
|
|
2
2
|
|
|
3
|
-
Plutonium apps are organized into **packages
|
|
3
|
+
Plutonium apps are organized into **packages**: Rails engines with stricter conventions. Two flavors, hard split:
|
|
4
4
|
|
|
5
5
|
| Type | Purpose | Generator | Examples |
|
|
6
6
|
|---|---|---|---|
|
|
@@ -60,7 +60,7 @@ Each feature package gets its own base classes:
|
|
|
60
60
|
- `Blogging::ResourceDefinition`
|
|
61
61
|
- `Blogging::ResourceInteraction`
|
|
62
62
|
|
|
63
|
-
These inherit from the main app's base classes
|
|
63
|
+
These inherit from the main app's base classes; extend them for package-wide defaults.
|
|
64
64
|
|
|
65
65
|
### Creating resources inside a feature package
|
|
66
66
|
|
|
@@ -111,13 +111,13 @@ This is loaded from `config/application.rb`. Migrations from all packages are pi
|
|
|
111
111
|
|
|
112
112
|
## When to use which
|
|
113
113
|
|
|
114
|
-
**Feature packages
|
|
114
|
+
**Feature packages**: domain logic that:
|
|
115
115
|
|
|
116
116
|
- Could be reused across multiple portals (admin and customer both edit `Blogging::Post`).
|
|
117
117
|
- Has no inherent UI / auth (it's just behavior).
|
|
118
118
|
- You want isolated from other domains (`billing` should not depend on `blogging`).
|
|
119
119
|
|
|
120
|
-
**Portal packages
|
|
120
|
+
**Portal packages**: user-facing surfaces that:
|
|
121
121
|
|
|
122
122
|
- Have a specific auth flow (admin vs customer vs public).
|
|
123
123
|
- Render different views of the same underlying resources.
|
|
@@ -137,10 +137,10 @@ packages/
|
|
|
137
137
|
└── controllers, views, routes
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
The portals expose the features. A single feature can be exposed by multiple portals
|
|
140
|
+
The portals expose the features. A single feature can be exposed by multiple portals, usually with different policies and definitions per portal.
|
|
141
141
|
|
|
142
142
|
## Related
|
|
143
143
|
|
|
144
|
-
- [Portals](./portals)
|
|
145
|
-
- [Generators](./generators)
|
|
146
|
-
- [Guide: Creating Packages](/guides/creating-packages)
|
|
144
|
+
- [Portals](./portals): portal-specific configuration (mounting, auth, route registration)
|
|
145
|
+
- [Generators](./generators): `pu:pkg:package` and `pu:pkg:portal` flags
|
|
146
|
+
- [Guide: Creating Packages](/guides/creating-packages): task-oriented walkthrough
|
|
@@ -5,7 +5,7 @@ A portal is a Rails engine mixing in `Plutonium::Portal::Engine`. It defines its
|
|
|
5
5
|
## 🚨 Critical
|
|
6
6
|
|
|
7
7
|
- **Use `pu:pkg:portal` for everything.** Never hand-write the engine file, controller concern, or layout.
|
|
8
|
-
- **Pass `--auth=<name>`, `--public`, or `--byo`** for unattended runs
|
|
8
|
+
- **Pass `--auth=<name>`, `--public`, or `--byo`** for unattended runs; without one of these flags, the generator prompts.
|
|
9
9
|
- **Always connect resources with `pu:res:conn`.** Until connected, a resource has no portal routes and is invisible.
|
|
10
10
|
- **For custom routes on a registered resource, pass `as:`.** Without it, `resource_url_for` can't build URLs.
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ rails g pu:pkg:portal <name>
|
|
|
20
20
|
| Option | Description |
|
|
21
21
|
|---|---|
|
|
22
22
|
| `--auth=NAME` | Rodauth account to authenticate with (e.g. `--auth=user`) |
|
|
23
|
-
| `--public` | Public access
|
|
23
|
+
| `--public` | Public access, no authentication |
|
|
24
24
|
| `--byo` | Bring your own authentication |
|
|
25
25
|
| `--scope=CLASS` | Entity class for multi-tenancy (e.g. `--scope=Organization`) |
|
|
26
26
|
|
|
@@ -51,7 +51,9 @@ end
|
|
|
51
51
|
|
|
52
52
|
## Controller concern (auth)
|
|
53
53
|
|
|
54
|
-
Every portal has a `Concerns::Controller
|
|
54
|
+
Every portal has a `Concerns::Controller`, included by both its `ResourceController` (resource pages) and its `PlutoniumController` (dashboard and other non-resource pages). The generator wires this up; you customize for auth flow and shared before_action hooks.
|
|
55
|
+
|
|
56
|
+
Portal-wide helpers the layout calls belong here, declared with `helper_method`, so they work on the dashboard as well as resource pages. The common case is `profile_url`: `Plutonium::Auth::Rodauth` defines it as `nil`, and the avatar menu only shows a Profile link when it returns a URL. `pu:profile:conn --dest=<portal>` writes the override into this concern (see [Auth › Profile](/reference/auth/profile)).
|
|
55
57
|
|
|
56
58
|
### Rodauth
|
|
57
59
|
|
|
@@ -89,19 +91,63 @@ end
|
|
|
89
91
|
|
|
90
92
|
## Mounting
|
|
91
93
|
|
|
94
|
+
`pu:pkg:portal` writes the mount at the bottom of the portal's own `packages/<name>_portal/config/routes.rb`, at `/<name>`, wrapped in an auth constraint when `--auth` is given:
|
|
95
|
+
|
|
92
96
|
```ruby
|
|
93
|
-
# config/routes.rb
|
|
97
|
+
# packages/admin_portal/config/routes.rb (after the engine's routes.draw block)
|
|
94
98
|
Rails.application.routes.draw do
|
|
95
|
-
# Authenticated mount
|
|
96
99
|
constraints Rodauth::Rails.authenticate(:user) do
|
|
97
100
|
mount AdminPortal::Engine, at: "/admin"
|
|
98
101
|
end
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
With `--public` or `--byo` the mount is unconstrained and the portal handles its own auth.
|
|
106
|
+
|
|
107
|
+
To change the path, edit `at:` in place. Don't mount the engine again in `config/routes.rb`: the second `mount` reuses the route name (`admin_portal`) and Rails raises `ArgumentError: Invalid route name, already in use`.
|
|
108
|
+
|
|
109
|
+
Route order matters: the app's `config/routes.rb` is drawn first, then each package's routes file, then gem engines (Active Storage, Turbo).
|
|
110
|
+
|
|
111
|
+
### Mounting a portal at `/`
|
|
112
|
+
|
|
113
|
+
Two things change when a portal is mounted at `"/"`.
|
|
114
|
+
|
|
115
|
+
**Drop the `Rodauth::Rails.authenticate` constraint and authenticate in the controller concern instead.** The constraint does not fail the match for an anonymous visitor; it calls `rodauth.require_account`, which redirects to login. A constrained mount at `/` matches every path the app's own routes did not claim, so it redirects anonymous requests meant for routes drawn after it (other portals, Active Storage) and turns unknown URLs into login redirects.
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
# packages/desk_portal/config/routes.rb
|
|
119
|
+
Rails.application.routes.draw do
|
|
120
|
+
mount DeskPortal::Engine, at: "/"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# packages/desk_portal/app/controllers/desk_portal/concerns/controller.rb
|
|
124
|
+
module DeskPortal
|
|
125
|
+
module Concerns
|
|
126
|
+
module Controller
|
|
127
|
+
extend ActiveSupport::Concern
|
|
128
|
+
include Plutonium::Portal::Controller
|
|
129
|
+
include Plutonium::Auth::Rodauth(:user)
|
|
130
|
+
|
|
131
|
+
included do
|
|
132
|
+
before_action { rodauth.require_account }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Move the engine's root off `/` but keep the name.** The app's `root` is drawn first, so the generated `root to: "dashboard#index"` is unreachable and the portal's `root_path` points at the app's home page. Plutonium's header, icon rail, breadcrumbs and wizard exits all link to `root_path`, so the portal still needs a route named `root`:
|
|
99
140
|
|
|
100
|
-
|
|
101
|
-
|
|
141
|
+
```ruby
|
|
142
|
+
DeskPortal::Engine.routes.draw do
|
|
143
|
+
get "dashboard", to: "dashboard#index", as: :root
|
|
144
|
+
register_resource ::Comment
|
|
145
|
+
# register resources above.
|
|
102
146
|
end
|
|
103
147
|
```
|
|
104
148
|
|
|
149
|
+
The same applies to `register_dashboard ..., at: "/"`. Confirm with `Rails.application.routes.recognize_path("/")` (still the app's home) and `recognize_path("/dashboard")`.
|
|
150
|
+
|
|
105
151
|
## Routes & `register_resource`
|
|
106
152
|
|
|
107
153
|
Portal routes live in `packages/<name>_portal/config/routes.rb`:
|
|
@@ -126,7 +172,7 @@ For each call, Plutonium auto-generates:
|
|
|
126
172
|
- Nested routes for every registered `has_many` / `has_one` parent (prefixed `nested_`)
|
|
127
173
|
- Route names that `resource_url_for` can resolve
|
|
128
174
|
|
|
129
|
-
You list every resource the portal exposes. If a resource isn't registered, it has no URLs in that portal
|
|
175
|
+
You list every resource the portal exposes. If a resource isn't registered, it has no URLs in that portal, so `resource_url_for` will fail.
|
|
130
176
|
|
|
131
177
|
### Choosing nested associations
|
|
132
178
|
|
|
@@ -136,11 +182,11 @@ Name the associations that get nested routes, and the rest are not drawn:
|
|
|
136
182
|
register_resource ::Post, associations: %i[comments post_detail]
|
|
137
183
|
```
|
|
138
184
|
|
|
139
|
-
`associations: []` draws none, and a name that is not a routable association fails the boot. Set `config.nested_association_routes = :declared` to make naming them the rule
|
|
185
|
+
`associations: []` draws none, and a name that is not a routable association fails the boot. Set `config.nested_association_routes = :declared` to make naming them the rule; see [Tenancy › Nested resources](../tenancy/nested-resources#declaring-which-associations-get-routes).
|
|
140
186
|
|
|
141
187
|
### Singular (singleton) resources
|
|
142
188
|
|
|
143
|
-
For resources with no collection
|
|
189
|
+
For resources with no collection: a single per-user `Profile`, app-wide `Settings`, etc.:
|
|
144
190
|
|
|
145
191
|
```ruby
|
|
146
192
|
register_resource ::Profile, singular: true
|
|
@@ -178,12 +224,12 @@ end
|
|
|
178
224
|
```
|
|
179
225
|
|
|
180
226
|
::: warning Always pass `as:`
|
|
181
|
-
Without `as:`, `resource_url_for(@post, action: :preview)` fails because there's no named route
|
|
227
|
+
Without `as:`, `resource_url_for(@post, action: :preview)` fails because there's no named route, which is especially critical for nested resources.
|
|
182
228
|
:::
|
|
183
229
|
|
|
184
|
-
For most operations with business logic, prefer **interactive actions** (definition + interaction
|
|
230
|
+
For most operations with business logic, prefer **interactive actions** (definition + interaction; see [Resource › Actions](/reference/resource/actions)) over custom controller routes. Action routes wire automatically with no `register_resource` block needed.
|
|
185
231
|
|
|
186
|
-
## Connecting resources
|
|
232
|
+
## Connecting resources: `pu:res:conn`
|
|
187
233
|
|
|
188
234
|
A resource is invisible until connected to at least one portal. The generator wires up the portal-specific controller, policy, definition, and route registration.
|
|
189
235
|
|
|
@@ -191,7 +237,7 @@ A resource is invisible until connected to at least one portal. The generator wi
|
|
|
191
237
|
rails g pu:res:conn RESOURCE [RESOURCE...] --dest=PORTAL_NAME [--singular]
|
|
192
238
|
```
|
|
193
239
|
|
|
194
|
-
Pass resources directly
|
|
240
|
+
Pass resources directly to avoid interactive prompts. No `--src` needed.
|
|
195
241
|
|
|
196
242
|
```bash
|
|
197
243
|
# Main app resources
|
|
@@ -286,13 +332,13 @@ end
|
|
|
286
332
|
## Per-portal overrides
|
|
287
333
|
|
|
288
334
|
```ruby
|
|
289
|
-
# Definition
|
|
335
|
+
# Definition: how fields render per portal
|
|
290
336
|
class AdminPortal::PostDefinition < ::PostDefinition
|
|
291
337
|
scope :pending_review
|
|
292
338
|
input :internal_notes, hint: "Not shown to the author"
|
|
293
339
|
end
|
|
294
340
|
|
|
295
|
-
# Policy
|
|
341
|
+
# Policy: which fields exist, and who may act
|
|
296
342
|
# `internal_notes` appears for admins because THIS permits it,
|
|
297
343
|
# not because the definition above mentions it.
|
|
298
344
|
class AdminPortal::PostPolicy < ::PostPolicy
|
|
@@ -302,7 +348,7 @@ class AdminPortal::PostPolicy < ::PostPolicy
|
|
|
302
348
|
def permitted_attributes_for_create = %i[title content featured internal_notes]
|
|
303
349
|
end
|
|
304
350
|
|
|
305
|
-
# Controller
|
|
351
|
+
# Controller: different redirect after submit
|
|
306
352
|
module AdminPortal
|
|
307
353
|
class PostsController < ResourceController
|
|
308
354
|
private
|
|
@@ -321,7 +367,7 @@ config.after_initialize do
|
|
|
321
367
|
end
|
|
322
368
|
```
|
|
323
369
|
|
|
324
|
-
Strategies: `:path` (entity id in URL
|
|
370
|
+
Strategies: `:path` (entity id in URL, the default) or a custom method name on the portal controller concern.
|
|
325
371
|
|
|
326
372
|
For the full multi-tenancy story, see [Tenancy › Entity scoping](/reference/tenancy/entity-scoping).
|
|
327
373
|
|
|
@@ -330,13 +376,13 @@ For the full multi-tenancy story, see [Tenancy › Entity scoping](/reference/te
|
|
|
330
376
|
The generated `dashboard#index` is a plain page listing the registered resources. To replace it with a [dashboard](/guides/dashboards) of metric and chart cards, run `rails g pu:dashboard Home --dest=<portal> --at=/`, which swaps the `root to:` line for a `register_dashboard ... at: "/"` registration.
|
|
331
377
|
|
|
332
378
|
```ruby
|
|
333
|
-
# config/routes.rb
|
|
379
|
+
# packages/admin_portal/config/routes.rb
|
|
334
380
|
AdminPortal::Engine.routes.draw do
|
|
335
381
|
root to: "dashboard#index"
|
|
336
382
|
get "settings", to: "settings#index"
|
|
337
383
|
end
|
|
338
384
|
|
|
339
|
-
# Controller
|
|
385
|
+
# Controller: inherit from PlutoniumController, NOT ResourceController
|
|
340
386
|
module AdminPortal
|
|
341
387
|
class DashboardController < PlutoniumController
|
|
342
388
|
def index
|
|
@@ -351,7 +397,7 @@ See [UI › Pages](/reference/ui/pages) for custom Phlex page classes.
|
|
|
351
397
|
## Multiple portals
|
|
352
398
|
|
|
353
399
|
```ruby
|
|
354
|
-
# Admin
|
|
400
|
+
# Admin: full access, entity-scoped
|
|
355
401
|
module AdminPortal
|
|
356
402
|
class Engine < Rails::Engine
|
|
357
403
|
include Plutonium::Portal::Engine
|
|
@@ -362,7 +408,7 @@ module AdminPortal
|
|
|
362
408
|
end
|
|
363
409
|
end
|
|
364
410
|
|
|
365
|
-
# Customer dashboard
|
|
411
|
+
# Customer dashboard: entity-scoped to the customer's organization
|
|
366
412
|
module DashboardPortal
|
|
367
413
|
class Engine < Rails::Engine
|
|
368
414
|
include Plutonium::Portal::Engine
|
|
@@ -373,7 +419,7 @@ module DashboardPortal
|
|
|
373
419
|
end
|
|
374
420
|
end
|
|
375
421
|
|
|
376
|
-
# Public
|
|
422
|
+
# Public: no auth, no entity scoping
|
|
377
423
|
module PublicPortal
|
|
378
424
|
class Engine < Rails::Engine
|
|
379
425
|
include Plutonium::Portal::Engine
|
|
@@ -383,9 +429,9 @@ end
|
|
|
383
429
|
|
|
384
430
|
## Related
|
|
385
431
|
|
|
386
|
-
- [Packages](./packages)
|
|
387
|
-
- [Generators](./generators)
|
|
388
|
-
- [Behavior › Controllers](/reference/behavior/controllers)
|
|
389
|
-
- [Tenancy › Entity scoping](/reference/tenancy/entity-scoping)
|
|
390
|
-
- [Auth](/reference/auth/)
|
|
391
|
-
- [UI › Layouts](/reference/ui/layouts)
|
|
432
|
+
- [Packages](./packages): feature vs portal split, structure, namespacing
|
|
433
|
+
- [Generators](./generators): full `pu:pkg:portal` / `pu:res:conn` option reference
|
|
434
|
+
- [Behavior › Controllers](/reference/behavior/controllers): controller key methods, hooks, customizations
|
|
435
|
+
- [Tenancy › Entity scoping](/reference/tenancy/entity-scoping): multi-tenancy mechanics
|
|
436
|
+
- [Auth](/reference/auth/): Rodauth account types referenced by `--auth=`
|
|
437
|
+
- [UI › Layouts](/reference/ui/layouts): customizing portal chrome
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Accounts
|
|
2
2
|
|
|
3
|
-
Rodauth account types. Pick one (or several
|
|
3
|
+
Rodauth account types. Pick one (or several; apps can have multiple side-by-side).
|
|
4
4
|
|
|
5
|
-
## Basic account
|
|
5
|
+
## Basic account: `pu:rodauth:account`
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
rails generate pu:rodauth:account user [options]
|
|
@@ -59,7 +59,7 @@ rails g pu:rodauth:account api_user --api_only --jwt --jwt_refresh
|
|
|
59
59
|
rails g pu:rodauth:account user --kitchen_sink
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
## Admin account
|
|
62
|
+
## Admin account: `pu:rodauth:admin`
|
|
63
63
|
|
|
64
64
|
Pre-configured secure admin with multi-phase login, **required** TOTP, recovery codes, lockout, active session tracking, audit logging, role-based access, invite interaction, and **no public signup**.
|
|
65
65
|
|
|
@@ -74,7 +74,7 @@ rails g pu:rodauth:admin admin --extra-attributes=name:string,department:string
|
|
|
74
74
|
| `--roles` | `super_admin,admin` | Comma-separated roles (positional enum) |
|
|
75
75
|
| `--extra_attributes` | | Additional model attributes (e.g. `name:string`) |
|
|
76
76
|
|
|
77
|
-
**Role-ordering convention:** index 0 is the most privileged. Generated invite interaction defaults new invitees to `roles[1]
|
|
77
|
+
**Role-ordering convention:** index 0 is the most privileged. Generated invite interaction defaults new invitees to `roles[1]`, so the order in `--roles=` matters.
|
|
78
78
|
|
|
79
79
|
```ruby
|
|
80
80
|
enum :role, super_admin: 0, admin: 1
|
|
@@ -82,12 +82,12 @@ enum :role, super_admin: 0, admin: 1
|
|
|
82
82
|
|
|
83
83
|
**Invite + resend.** The admin resource gets two actions:
|
|
84
84
|
|
|
85
|
-
- **Invite
|
|
86
|
-
- **Resend invitation
|
|
85
|
+
- **Invite**: invite a new admin by email; Rodauth sends a verification link and the invitee sets their own password through the verify flow.
|
|
86
|
+
- **Resend invitation**: re-send the verification email. Only shown for admins who haven't verified yet.
|
|
87
87
|
|
|
88
88
|
This uses Rodauth account verification, separate from the [Tenancy › Invites](/reference/tenancy/invites) system.
|
|
89
89
|
|
|
90
|
-
Rake task for direct admin creation (generated alongside the account
|
|
90
|
+
Rake task for direct admin creation (generated alongside the account; namespace is `rodauth`, task name is the account name):
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
93
|
EMAIL=admin@example.com rails rodauth:admin
|
|
@@ -96,7 +96,7 @@ EMAIL=admin@example.com rails rodauth:admin
|
|
|
96
96
|
|
|
97
97
|
The task creates the account and triggers a verification email; the admin sets their own password via that flow. No password is passed on the command line.
|
|
98
98
|
|
|
99
|
-
## SaaS setup
|
|
99
|
+
## SaaS setup: `pu:saas:setup` (meta-generator) {#saas-setup}
|
|
100
100
|
|
|
101
101
|
Creates the User + Entity + Membership trio AND runs:
|
|
102
102
|
|
|
@@ -152,7 +152,7 @@ class OrganizationCustomer < ApplicationRecord
|
|
|
152
152
|
end
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
## API client
|
|
155
|
+
## API client: `pu:saas:api_client`
|
|
156
156
|
|
|
157
157
|
For machine-to-machine authentication. HTTP Basic Auth with auto-generated password.
|
|
158
158
|
|
|
@@ -233,12 +233,12 @@ end
|
|
|
233
233
|
Emitted by the generators; both parts are required for concurrent portal logins.
|
|
234
234
|
|
|
235
235
|
```ruby
|
|
236
|
-
# app/rodauth/rodauth_plugin.rb
|
|
236
|
+
# app/rodauth/rodauth_plugin.rb: the shared base, once
|
|
237
237
|
enable :session_isolation
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
```ruby
|
|
241
|
-
# app/rodauth/<name>_rodauth_plugin.rb
|
|
241
|
+
# app/rodauth/<name>_rodauth_plugin.rb: once per account type
|
|
242
242
|
session_key_prefix "admin_" # namespaces EVERY key, account id included
|
|
243
243
|
remember_cookie_key "_admin_remember"
|
|
244
244
|
```
|
|
@@ -249,7 +249,7 @@ Do **not** also set `session_key`: explicit values bypass `convert_session_key`
|
|
|
249
249
|
|
|
250
250
|
## Related
|
|
251
251
|
|
|
252
|
-
- [Profile](./profile)
|
|
253
|
-
- [App › Portals › Controller concern (auth)](/reference/app/portals#controller-concern-auth)
|
|
254
|
-
- [Tenancy › Invites](/reference/tenancy/invites)
|
|
255
|
-
- [App › Generators › Authentication generators](/reference/app/generators#authentication-generators)
|
|
252
|
+
- [Profile](./profile): profile resource + SecuritySection component
|
|
253
|
+
- [App › Portals › Controller concern (auth)](/reference/app/portals#controller-concern-auth): wiring accounts into portal controllers
|
|
254
|
+
- [Tenancy › Invites](/reference/tenancy/invites): invitation system on top of Rodauth signup
|
|
255
|
+
- [App › Generators › Authentication generators](/reference/app/generators#authentication-generators): full generator catalog
|
|
@@ -4,18 +4,18 @@ Plutonium uses [Rodauth](http://rodauth.jeremyevans.net/) via [rodauth-rails](ht
|
|
|
4
4
|
|
|
5
5
|
## Sub-pages
|
|
6
6
|
|
|
7
|
-
- [Accounts](./accounts)
|
|
8
|
-
- [Profile](./profile)
|
|
7
|
+
- [Accounts](./accounts): Rodauth install, basic accounts, admin accounts, SaaS setup, account customization
|
|
8
|
+
- [Profile](./profile): profile resource generator, the SecuritySection component
|
|
9
9
|
|
|
10
10
|
## 🚨 Critical
|
|
11
11
|
|
|
12
12
|
- **Use the generators.** `pu:rodauth:install`, `pu:rodauth:account`, `pu:rodauth:admin`, `pu:saas:setup`, `pu:profile:install`, `pu:profile:conn`. Never hand-write Rodauth plugin files, account models, or profile resources.
|
|
13
|
-
- **Role index 0 is the most privileged** (`owner`, `super_admin`). Invite interactions default new invitees to **index 1
|
|
13
|
+
- **Role index 0 is the most privileged** (`owner`, `super_admin`). Invite interactions default new invitees to **index 1**, so the order in `--roles=` matters.
|
|
14
14
|
- **`pu:saas:setup --roles=...` always prepends `owner` as index 0.** Don't include `owner` in the option.
|
|
15
15
|
- **`pu:saas:setup` is a meta-generator.** It also runs `pu:saas:portal`, `pu:profile:setup`, `pu:saas:welcome`, and `pu:invites:install`. Don't re-run those manually.
|
|
16
|
-
- **Profile association is always `:profile`** regardless of the model class
|
|
17
|
-
- **Profile needs `pu:profile:conn` to be visible
|
|
18
|
-
- **
|
|
16
|
+
- **Profile association is always `:profile`** regardless of the model class: `current_user.profile`, `build_profile`, `params.require(:profile)`. `pu:profile:conn` hardcodes `current_user.profile`.
|
|
17
|
+
- **Profile needs `pu:profile:conn` to be visible.** Without it, there is no singular `/profile` route and `profile_url` stays `nil` (no menu link).
|
|
18
|
+
- **Check an existing profile policy for `update?`.** Older `pu:profile:conn` output lacks `def update? = true`, so the profile can't be edited once it exists. See [Profile](./profile#what-pu-profile-conn-generates).
|
|
19
19
|
|
|
20
20
|
## Install Rodauth
|
|
21
21
|
|
|
@@ -34,7 +34,7 @@ class ResourceController < PlutoniumController
|
|
|
34
34
|
end
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Multiple account types
|
|
37
|
+
Multiple account types: include the matching `:name`:
|
|
38
38
|
|
|
39
39
|
```ruby
|
|
40
40
|
class AdminController < PlutoniumController
|
|
@@ -80,9 +80,9 @@ Authorization: Bearer <access_token>
|
|
|
80
80
|
|
|
81
81
|
## Related
|
|
82
82
|
|
|
83
|
-
- [Accounts](./accounts)
|
|
84
|
-
- [Profile](./profile)
|
|
85
|
-
- [Tenancy › Invites](/reference/tenancy/invites)
|
|
86
|
-
- [App › Portals › Controller concern (auth)](/reference/app/portals#controller-concern-auth)
|
|
87
|
-
- [Guides › Authentication](/guides/authentication)
|
|
83
|
+
- [Accounts](./accounts): account types and feature flags
|
|
84
|
+
- [Profile](./profile): profile resource + SecuritySection
|
|
85
|
+
- [Tenancy › Invites](/reference/tenancy/invites): invitation system on top of Rodauth signup
|
|
86
|
+
- [App › Portals › Controller concern (auth)](/reference/app/portals#controller-concern-auth): portal-side wiring
|
|
87
|
+
- [Guides › Authentication](/guides/authentication): task-oriented walkthrough
|
|
88
88
|
- [Guides › User profile](/guides/user-profile)
|