plutonium 0.33.1 → 0.34.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/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Creating Packages
|
|
2
|
+
|
|
3
|
+
This guide covers creating and organizing Feature Packages and Portal Packages.
|
|
4
|
+
|
|
5
|
+
## Package Types
|
|
6
|
+
|
|
7
|
+
| Type | Purpose | Generator |
|
|
8
|
+
|------|---------|-----------|
|
|
9
|
+
| **Feature Package** | Business logic (models, definitions, policies) | `rails g pu:pkg:package NAME` |
|
|
10
|
+
| **Portal Package** | Web interface (routes, auth, UI) | `rails g pu:pkg:portal NAME` |
|
|
11
|
+
|
|
12
|
+
## Creating a Feature Package
|
|
13
|
+
|
|
14
|
+
### Using the Generator
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
rails g pu:pkg:package blogging
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Generated Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
packages/blogging/
|
|
24
|
+
├── app/
|
|
25
|
+
│ ├── controllers/blogging/
|
|
26
|
+
│ │ └── resource_controller.rb
|
|
27
|
+
│ ├── definitions/blogging/
|
|
28
|
+
│ │ └── resource_definition.rb
|
|
29
|
+
│ ├── interactions/blogging/
|
|
30
|
+
│ │ └── resource_interaction.rb
|
|
31
|
+
│ ├── models/blogging/
|
|
32
|
+
│ │ └── resource_record.rb
|
|
33
|
+
│ ├── policies/blogging/
|
|
34
|
+
│ │ └── resource_policy.rb
|
|
35
|
+
│ └── views/blogging/
|
|
36
|
+
└── lib/
|
|
37
|
+
└── engine.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Engine Configuration
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# packages/blogging/lib/engine.rb
|
|
44
|
+
module Blogging
|
|
45
|
+
class Engine < Rails::Engine
|
|
46
|
+
include Plutonium::Package::Engine
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Namespacing
|
|
52
|
+
|
|
53
|
+
All classes are auto-namespaced:
|
|
54
|
+
- `app/models/blogging/post.rb` → `Blogging::Post`
|
|
55
|
+
- `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
|
|
56
|
+
|
|
57
|
+
## Creating a Portal Package
|
|
58
|
+
|
|
59
|
+
### Using the Generator
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
rails g pu:pkg:portal admin
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Generator Options
|
|
66
|
+
|
|
67
|
+
| Option | Description |
|
|
68
|
+
|--------|-------------|
|
|
69
|
+
| `--auth=NAME` | Rodauth account to authenticate with |
|
|
70
|
+
| `--public` | Grant public access (no authentication) |
|
|
71
|
+
| `--byo` | Bring your own authentication |
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Non-interactive examples
|
|
75
|
+
rails g pu:pkg:portal admin --auth=admin
|
|
76
|
+
rails g pu:pkg:portal api --public
|
|
77
|
+
rails g pu:pkg:portal custom --byo
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Without flags, the generator prompts interactively.
|
|
81
|
+
|
|
82
|
+
### Generated Structure
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
packages/admin_portal/
|
|
86
|
+
├── app/
|
|
87
|
+
│ ├── controllers/admin_portal/
|
|
88
|
+
│ │ ├── concerns/controller.rb
|
|
89
|
+
│ │ ├── dashboard_controller.rb
|
|
90
|
+
│ │ ├── plutonium_controller.rb
|
|
91
|
+
│ │ └── resource_controller.rb
|
|
92
|
+
│ ├── definitions/admin_portal/
|
|
93
|
+
│ │ └── resource_definition.rb
|
|
94
|
+
│ ├── policies/admin_portal/
|
|
95
|
+
│ │ └── resource_policy.rb
|
|
96
|
+
│ └── views/admin_portal/
|
|
97
|
+
│ └── dashboard/index.html.erb
|
|
98
|
+
├── config/
|
|
99
|
+
│ └── routes.rb
|
|
100
|
+
└── lib/
|
|
101
|
+
└── engine.rb
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Portal Engine
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# packages/admin_portal/lib/engine.rb
|
|
108
|
+
module AdminPortal
|
|
109
|
+
class Engine < Rails::Engine
|
|
110
|
+
include Plutonium::Portal::Engine
|
|
111
|
+
|
|
112
|
+
config.after_initialize do
|
|
113
|
+
# Multi-tenancy (optional)
|
|
114
|
+
scope_to_entity Organization, strategy: :path
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Portal Authentication
|
|
121
|
+
|
|
122
|
+
Authentication is configured in the controller concern based on generator options:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
|
|
126
|
+
module AdminPortal
|
|
127
|
+
module Concerns
|
|
128
|
+
module Controller
|
|
129
|
+
extend ActiveSupport::Concern
|
|
130
|
+
include Plutonium::Portal::Controller
|
|
131
|
+
include Plutonium::Auth::Rodauth(:admin)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For public access:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
include Plutonium::Auth::Public
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For custom authentication:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
included do
|
|
147
|
+
helper_method :current_user
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def current_user
|
|
151
|
+
# Your authentication logic
|
|
152
|
+
@current_user ||= User.find_by(api_key: request.headers["X-API-Key"])
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Portal Routes
|
|
157
|
+
|
|
158
|
+
The portal generator creates routes and auto-mounts to the main app:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# packages/admin_portal/config/routes.rb
|
|
162
|
+
AdminPortal::Engine.routes.draw do
|
|
163
|
+
root to: "dashboard#index"
|
|
164
|
+
|
|
165
|
+
# Register resources here
|
|
166
|
+
register_resource ::Post
|
|
167
|
+
register_resource Blogging::Comment
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Also adds to main app routes:
|
|
171
|
+
# config/routes.rb (auto-generated)
|
|
172
|
+
Rails.application.routes.draw do
|
|
173
|
+
constraints Rodauth::Rails.authenticate(:admin) do
|
|
174
|
+
mount AdminPortal::Engine, at: "/admin"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Custom Routes on Resources
|
|
180
|
+
|
|
181
|
+
Add member or collection routes with a block:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
register_resource ::Post do
|
|
185
|
+
member do
|
|
186
|
+
get :preview
|
|
187
|
+
post :publish
|
|
188
|
+
end
|
|
189
|
+
collection do
|
|
190
|
+
get :archived
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Package Loading
|
|
196
|
+
|
|
197
|
+
Packages are loaded via `config/packages.rb` (generated during install):
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# config/packages.rb
|
|
201
|
+
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
|
|
202
|
+
load package
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This is automatically required in `config/application.rb`.
|
|
207
|
+
|
|
208
|
+
## Adding Resources to Packages
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Add to main app
|
|
212
|
+
rails g pu:res:scaffold Post title:string --dest=main_app
|
|
213
|
+
|
|
214
|
+
# Add to a feature package
|
|
215
|
+
rails g pu:res:scaffold Post title:string --dest=blogging
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Resources are namespaced:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
# packages/blogging/app/models/blogging/post.rb
|
|
222
|
+
module Blogging
|
|
223
|
+
class Post < Blogging::ResourceRecord
|
|
224
|
+
# Model code
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Connecting Resources to Portals
|
|
230
|
+
|
|
231
|
+
Resources must be connected to portals to be accessible:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Connect main app resource
|
|
235
|
+
rails g pu:res:conn Post --dest=admin_portal
|
|
236
|
+
|
|
237
|
+
# Connect namespaced resource
|
|
238
|
+
rails g pu:res:conn Blogging::Post --dest=admin_portal
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Entity Scoping (Multi-tenancy)
|
|
242
|
+
|
|
243
|
+
Automatically scope all data to a parent entity:
|
|
244
|
+
|
|
245
|
+
### Path Strategy
|
|
246
|
+
|
|
247
|
+
Entity ID in URL path:
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
# packages/admin_portal/lib/engine.rb
|
|
251
|
+
config.after_initialize do
|
|
252
|
+
scope_to_entity Organization, strategy: :path
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Routes become: `/organizations/:organization_id/posts`
|
|
257
|
+
|
|
258
|
+
### Custom Strategy
|
|
259
|
+
|
|
260
|
+
Implement your own lookup method:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
config.after_initialize do
|
|
264
|
+
scope_to_entity Organization, strategy: :current_organization
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# In controller concern
|
|
268
|
+
def current_organization
|
|
269
|
+
@current_organization ||= Organization.find_by!(subdomain: request.subdomain)
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Package Best Practices
|
|
274
|
+
|
|
275
|
+
### 1. Single Responsibility
|
|
276
|
+
Each feature package should handle one domain:
|
|
277
|
+
- `blogging` - Posts, comments, categories
|
|
278
|
+
- `inventory` - Products, stock, warehouses
|
|
279
|
+
- `billing` - Invoices, payments, subscriptions
|
|
280
|
+
|
|
281
|
+
### 2. Clear Naming
|
|
282
|
+
- Feature packages: domain nouns (`blogging`, `billing`)
|
|
283
|
+
- Portal packages: role + portal (`admin_portal`, `api_portal`)
|
|
284
|
+
|
|
285
|
+
### 3. Minimal Cross-Dependencies
|
|
286
|
+
Limit dependencies between feature packages. If two packages are tightly coupled, consider merging them.
|
|
287
|
+
|
|
288
|
+
### 4. Portal Customization
|
|
289
|
+
Put UI customizations in portal packages, not feature packages:
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
# Good: Portal-specific definition
|
|
293
|
+
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
|
|
294
|
+
|
|
295
|
+
# Bad: Feature package with portal-specific code
|
|
296
|
+
# packages/blogging/app/definitions/blogging/admin_post_definition.rb
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Multiple Portals Pattern
|
|
300
|
+
|
|
301
|
+
Common pattern for different user types:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
packages/
|
|
305
|
+
├── blogging/ # Feature: blog functionality
|
|
306
|
+
├── billing/ # Feature: payment/invoicing
|
|
307
|
+
├── admin_portal/ # Portal: admin interface
|
|
308
|
+
├── dashboard_portal/ # Portal: user dashboard
|
|
309
|
+
└── public_portal/ # Portal: public read-only
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Each portal can:
|
|
313
|
+
- Have different authentication
|
|
314
|
+
- Show different fields
|
|
315
|
+
- Allow different actions
|
|
316
|
+
- Use different layouts
|
|
317
|
+
|
|
318
|
+
## Portal-Specific Overrides
|
|
319
|
+
|
|
320
|
+
### Override Definition
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
|
|
324
|
+
class AdminPortal::PostDefinition < ::PostDefinition
|
|
325
|
+
# Add portal-specific scopes
|
|
326
|
+
scope :my_posts, -> { where(user: current_user) }
|
|
327
|
+
end
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Override Policy
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
# packages/admin_portal/app/policies/admin_portal/post_policy.rb
|
|
334
|
+
class AdminPortal::PostPolicy < ::PostPolicy
|
|
335
|
+
include AdminPortal::ResourcePolicy
|
|
336
|
+
|
|
337
|
+
def destroy?
|
|
338
|
+
true # Admins can delete
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def permitted_attributes_for_create
|
|
342
|
+
%i[title content featured internal_notes] # More fields
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Override Controller
|
|
348
|
+
|
|
349
|
+
```ruby
|
|
350
|
+
# packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
|
|
351
|
+
module AdminPortal
|
|
352
|
+
class PostsController < ResourceController
|
|
353
|
+
private
|
|
354
|
+
|
|
355
|
+
def preferred_action_after_submit
|
|
356
|
+
"index"
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Controller Hierarchy
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
::PlutoniumController (app-wide base)
|
|
366
|
+
↓
|
|
367
|
+
::ResourceController (resource handling)
|
|
368
|
+
↓
|
|
369
|
+
AdminPortal::ResourceController (portal base)
|
|
370
|
+
↓
|
|
371
|
+
AdminPortal::PostsController (resource-specific)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Controllers are auto-created if not defined.
|
|
375
|
+
|
|
376
|
+
## Related
|
|
377
|
+
|
|
378
|
+
- [Adding Resources](./adding-resources)
|
|
379
|
+
- [Authentication](./authentication)
|
|
380
|
+
- [Multi-tenancy](./multi-tenancy)
|