plutonium 0.23.4 → 0.23.5

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/sqlite_json_alias.rb +1 -1
  4. data/docs/.vitepress/config.ts +60 -19
  5. data/docs/guide/cursor-rules.md +75 -0
  6. data/docs/guide/deep-dive/authorization.md +189 -0
  7. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  8. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  9. data/docs/guide/index.md +28 -0
  10. data/docs/guide/introduction/02-core-concepts.md +440 -0
  11. data/docs/guide/tutorial/01-project-setup.md +75 -0
  12. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  13. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  14. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  15. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  16. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  17. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  18. data/docs/index.md +24 -31
  19. data/docs/modules/action.md +190 -0
  20. data/docs/modules/authentication.md +236 -0
  21. data/docs/modules/configuration.md +599 -0
  22. data/docs/modules/controller.md +398 -0
  23. data/docs/modules/core.md +316 -0
  24. data/docs/modules/definition.md +876 -0
  25. data/docs/modules/display.md +759 -0
  26. data/docs/modules/form.md +605 -0
  27. data/docs/modules/generator.md +288 -0
  28. data/docs/modules/index.md +167 -0
  29. data/docs/modules/interaction.md +470 -0
  30. data/docs/modules/package.md +151 -0
  31. data/docs/modules/policy.md +176 -0
  32. data/docs/modules/portal.md +710 -0
  33. data/docs/modules/query.md +287 -0
  34. data/docs/modules/resource_record.md +618 -0
  35. data/docs/modules/routing.md +641 -0
  36. data/docs/modules/table.md +293 -0
  37. data/docs/modules/ui.md +631 -0
  38. data/docs/public/plutonium.mdc +667 -0
  39. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  40. data/lib/plutonium/ui/display/resource.rb +7 -2
  41. data/lib/plutonium/ui/table/resource.rb +8 -3
  42. data/lib/plutonium/version.rb +1 -1
  43. metadata +36 -9
  44. data/docs/guide/getting-started/authorization.md +0 -296
  45. data/docs/guide/getting-started/core-concepts.md +0 -432
  46. data/docs/guide/getting-started/index.md +0 -21
  47. data/docs/guide/tutorial.md +0 -401
  48. /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,710 @@
1
+ ---
2
+ title: Portal Module
3
+ ---
4
+
5
+ # Portal Module
6
+
7
+ The Portal module is your key to building sophisticated, multi-tenant applications with distinct user experiences. Think of portals as separate "faces" of your application—each designed for different types of users, with their own authentication, styling, and access controls, while sharing the same underlying business logic.
8
+
9
+ ::: tip
10
+ The Portal module is located in `lib/plutonium/portal/`. Portals are typically generated as packages in the `packages/` directory.
11
+ :::
12
+
13
+ ## What Portals Solve
14
+
15
+ Modern applications often need to serve different types of users with completely different interfaces:
16
+
17
+ - **Admin Portal**: Full system access for administrators and staff
18
+ - **Customer Portal**: Self-service interface for customers and clients
19
+ - **Partner Portal**: Specialized access for business partners
20
+ - **Public Portal**: Public-facing content and marketing pages
21
+
22
+ Each portal can have its own authentication system, visual design, feature set, and data access patterns, while sharing the same core business logic and data models.
23
+
24
+ ## Core Portal Capabilities
25
+
26
+ - **Application Segmentation**: Create completely isolated user experiences
27
+ - **Multi-Tenant Architecture**: Automatically scope data to organizations, accounts, or other entities
28
+ - **Independent Routing**: Each portal has its own URL structure and route namespace
29
+ - **Portal-Specific Authentication**: Different login systems and security requirements per portal
30
+ - **Flexible Access Control**: Fine-grained permissions tailored to each user type
31
+
32
+ ## Creating a Portal
33
+
34
+ Portals are Rails Engines enhanced with Plutonium's portal functionality. The easiest way to create one is with the generator:
35
+
36
+ ::: code-group
37
+ ```bash [Generate a Portal]
38
+ rails generate pu:pkg:portal admin
39
+ ```
40
+
41
+ ```ruby [packages/admin_portal/lib/engine.rb]
42
+ module AdminPortal
43
+ class Engine < ::Rails::Engine
44
+ # This inclusion provides all portal functionality
45
+ include Plutonium::Portal::Engine
46
+
47
+ # Optional: Configure multi-tenancy
48
+ scope_to_entity Organization, strategy: :path
49
+ end
50
+ end
51
+ ```
52
+
53
+ ```ruby [packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb]
54
+ # A base concern is created for portal-wide logic
55
+ module AdminPortal
56
+ module Concerns
57
+ module Controller
58
+ extend ActiveSupport::Concern
59
+ include Plutonium::Portal::Controller
60
+
61
+ # Include authentication specific to this portal
62
+ include Plutonium::Auth::Rodauth(:admin)
63
+
64
+ included do
65
+ # Add portal-specific logic
66
+ before_action :ensure_admin_access
67
+ layout "admin_portal"
68
+
69
+ # Portal-wide error handling
70
+ rescue_from AdminPortal::AccessDenied, with: :handle_access_denied
71
+ end
72
+
73
+ private
74
+
75
+ def ensure_admin_access
76
+ redirect_to root_path, error: "Admin access required" unless current_user&.admin?
77
+ end
78
+
79
+ def handle_access_denied(exception)
80
+ redirect_to admin_root_path, error: "Access denied: #{exception.message}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ ```
86
+ :::
87
+
88
+ ## Multi-Tenancy with Entity Scoping
89
+
90
+ One of the most powerful features of portals is automatic multi-tenancy through entity scoping. When you scope a portal to an entity (like Organization or Account), all data access is automatically filtered and secured.
91
+
92
+ ### Path-Based Scoping
93
+
94
+ The most straightforward approach uses URL parameters to identify the tenant:
95
+
96
+ ::: code-group
97
+ ```ruby [Engine Configuration]
98
+ # packages/admin_portal/lib/engine.rb
99
+ scope_to_entity Organization, strategy: :path
100
+ ```
101
+
102
+ ```ruby [Route Configuration]
103
+ # packages/admin_portal/config/routes.rb
104
+ AdminPortal::Engine.routes.draw do
105
+ # These routes are automatically nested under /organizations/:organization_id
106
+ register_resource Blog::Post
107
+ register_resource Blog::Comment
108
+ end
109
+
110
+ # Generated routes:
111
+ # GET /organizations/:organization_id/posts
112
+ # GET /organizations/:organization_id/posts/:id
113
+ # POST /organizations/:organization_id/posts
114
+ ```
115
+
116
+ ```ruby [Automatic Data Scoping]
117
+ # In any controller within the Admin Portal
118
+ class AdminPortal::PostsController < AdminPortal::ResourceController
119
+ def index
120
+ # current_scoped_entity returns the Organization from the URL
121
+ # All queries are automatically scoped to this organization
122
+ # @posts = current_scoped_entity.posts.authorized_scope(...)
123
+ end
124
+ end
125
+ ```
126
+ :::
127
+
128
+ ### Custom Scoping Strategies
129
+
130
+ For more sophisticated multi-tenancy, implement custom scoping strategies:
131
+
132
+ ::: code-group
133
+ ```ruby [Engine Configuration]
134
+ # Use subdomain-based tenancy
135
+ scope_to_entity Account, strategy: :current_account
136
+ ```
137
+
138
+ ```ruby [Controller Implementation]
139
+ module CustomerPortal::Concerns::Controller
140
+ private
141
+
142
+ # Method name must match the strategy name exactly
143
+ def current_account
144
+ @current_account ||= Account.find_by!(subdomain: request.subdomain)
145
+ rescue ActiveRecord::RecordNotFound
146
+ redirect_to root_path, error: "Invalid account subdomain"
147
+ end
148
+ end
149
+ ```
150
+ :::
151
+
152
+ ### Database Association Requirements
153
+
154
+ For automatic scoping to work, Plutonium needs to find a path from your resources to the scoping entity:
155
+
156
+ ::: code-group
157
+ ```ruby [Direct Association (Preferred)]
158
+ # Plutonium automatically finds this relationship
159
+ class Post < ApplicationRecord
160
+ belongs_to :organization
161
+ end
162
+ ```
163
+
164
+ ```ruby [Indirect Association]
165
+ # Plutonium can traverse one level of has_one or belongs_to
166
+ class Post < ApplicationRecord
167
+ belongs_to :author, class_name: 'User'
168
+ has_one :organization, through: :author
169
+ end
170
+ ```
171
+
172
+ ```ruby [Manual Scope (For Complex Cases)]
173
+ # Define a scope for complex relationships
174
+ class Comment < ApplicationRecord
175
+ belongs_to :post
176
+
177
+ scope :associated_with_organization, ->(organization) do
178
+ joins(post: :author).where(users: { organization_id: organization.id })
179
+ end
180
+ end
181
+ ```
182
+ :::
183
+
184
+ ## Portal Examples and Use Cases
185
+
186
+ ### Admin Portal: Internal Management
187
+
188
+ Perfect for system administrators and internal staff who need full access:
189
+
190
+ ::: code-group
191
+ ```ruby [Configuration]
192
+ # packages/admin_portal/lib/engine.rb
193
+ scope_to_entity Organization, strategy: :path
194
+
195
+ # packages/admin_portal/config/routes.rb
196
+ register_resource User
197
+ register_resource Organization
198
+ register_resource Blog::Post
199
+ register_resource Analytics::Report
200
+ ```
201
+
202
+ ```ruby [Authentication & Authorization]
203
+ # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
204
+ include Plutonium::Auth::Rodauth(:admin)
205
+
206
+ included do
207
+ before_action :require_admin_role
208
+ before_action :set_admin_context
209
+ end
210
+
211
+ private
212
+
213
+ def require_admin_role
214
+ redirect_to root_path unless current_user&.admin?
215
+ end
216
+ ```
217
+ :::
218
+
219
+ ### Customer Portal: Self-Service Interface
220
+
221
+ Designed for customers to manage their own accounts and data:
222
+
223
+ ::: code-group
224
+ ```ruby [Configuration]
225
+ # packages/customer_portal/lib/engine.rb
226
+ scope_to_entity Organization, strategy: :current_organization
227
+
228
+ # packages/customer_portal/config/routes.rb
229
+ register_resource Project
230
+ register_resource Invoice
231
+ register_resource SupportTicket
232
+ ```
233
+
234
+ ```ruby [Custom Scoping]
235
+ # packages/customer_portal/app/controllers/customer_portal/concerns/controller.rb
236
+ include Plutonium::Auth::Rodauth(:customer)
237
+
238
+ private
239
+
240
+ def current_organization
241
+ @current_organization ||= current_user&.organization
242
+ end
243
+ ```
244
+ :::
245
+
246
+ ### Public Portal: No Authentication Required
247
+
248
+ For marketing sites, blogs, and public content:
249
+
250
+ ::: code-group
251
+ ```ruby [Configuration]
252
+ # packages/public_portal/lib/engine.rb
253
+ # No scope_to_entity - public data
254
+
255
+ # packages/public_portal/config/routes.rb
256
+ register_resource Blog::Post
257
+ register_resource Page
258
+ register_resource ContactForm
259
+ ```
260
+
261
+ ```ruby [Public Access]
262
+ # packages/public_portal/app/controllers/public_portal/concerns/controller.rb
263
+ # No authentication required
264
+ include Plutonium::Portal::Controller
265
+
266
+ # Custom public-specific logic
267
+ before_action :track_visitor_analytics
268
+ ```
269
+ :::
270
+
271
+ ## Authentication Integration
272
+
273
+ ### Rodauth Multi-Account Setup
274
+
275
+ Portals integrate seamlessly with Rodauth for sophisticated authentication:
276
+
277
+ ```ruby
278
+ # config/rodauth.rb
279
+ class RodauthApp < Roda
280
+ plugin :rodauth, json: :only do
281
+ # Admin authentication
282
+ rodauth :admin do
283
+ enable :login, :logout, :create_account, :verify_account,
284
+ :reset_password, :change_password, :otp, :recovery_codes
285
+
286
+ rails_account_model { Admin }
287
+ rails_controller { Rodauth::AdminController }
288
+ prefix "/admin/auth"
289
+
290
+ # Require MFA for admin accounts
291
+ two_factor_auth_required? true
292
+ end
293
+
294
+ # Customer authentication with user-friendly features
295
+ rodauth :customer do
296
+ enable :login, :logout, :create_account, :verify_account,
297
+ :reset_password, :change_password, :remember
298
+
299
+ rails_account_model { Customer }
300
+ rails_controller { Rodauth::CustomerController }
301
+ prefix "/auth"
302
+
303
+ # Remember me functionality
304
+ remember_deadline 30.days
305
+ end
306
+ end
307
+ end
308
+ ```
309
+
310
+ ### Portal-Specific Authentication
311
+
312
+ Each portal includes its appropriate authentication:
313
+
314
+ ```ruby
315
+ # Admin Portal - High security
316
+ module AdminPortal
317
+ module Concerns
318
+ module Controller
319
+ include Plutonium::Auth::Rodauth(:admin)
320
+ end
321
+ end
322
+ end
323
+
324
+ # Customer Portal - User-friendly
325
+ module CustomerPortal
326
+ module Concerns
327
+ module Controller
328
+ include Plutonium::Auth::Rodauth(:customer)
329
+ end
330
+ end
331
+ end
332
+ ```
333
+
334
+ ### Route-Level Authentication
335
+
336
+ Enforce authentication at the routing level:
337
+
338
+ ```ruby
339
+ # config/routes.rb
340
+ Rails.application.routes.draw do
341
+ # Admin portal requires admin authentication
342
+ constraints Rodauth::Rails.authenticate(:admin) do
343
+ mount AdminPortal::Engine, at: "/admin"
344
+ end
345
+
346
+ # Customer portal requires customer authentication
347
+ constraints Rodauth::Rails.authenticate(:customer) do
348
+ mount CustomerPortal::Engine, at: "/app"
349
+ end
350
+
351
+ # Public portal has no authentication constraint
352
+ mount PublicPortal::Engine, at: "/"
353
+ end
354
+ ```
355
+
356
+ ## Resource Management and Access Control
357
+
358
+ ### Resource Registration
359
+
360
+ Resources must be explicitly registered with each portal:
361
+
362
+ ```ruby
363
+ # Admin portal - comprehensive access
364
+ AdminPortal::Engine.routes.draw do
365
+ register_resource User
366
+ register_resource Organization
367
+ register_resource Blog::Post
368
+ register_resource Blog::Comment
369
+ register_resource Analytics::Report
370
+ register_resource Billing::Invoice
371
+ end
372
+
373
+ # Customer portal - limited, relevant resources
374
+ CustomerPortal::Engine.routes.draw do
375
+ register_resource Project
376
+ register_resource Billing::Invoice # Access controlled via policy
377
+ register_resource SupportTicket
378
+ end
379
+
380
+ # Public portal - read-only, published content
381
+ PublicPortal::Engine.routes.draw do
382
+ register_resource Blog::Post # Only published posts via policy
383
+ register_resource Page # Only public pages via policy
384
+ end
385
+ ```
386
+
387
+ ### Conditional Resource Registration
388
+
389
+ Dynamically register resources based on configuration or environment:
390
+
391
+ ```ruby
392
+ AdminPortal::Engine.routes.draw do
393
+ register_resource User
394
+ register_resource Organization
395
+
396
+ # Feature flags
397
+ register_resource Blog::Post if Rails.application.config.enable_blog
398
+ register_resource Analytics::Report if Rails.application.config.enable_analytics
399
+
400
+ # Environment-specific resources
401
+ register_resource SystemLog if Rails.env.development?
402
+ register_resource PerformanceMetric if Rails.env.production?
403
+ end
404
+ ```
405
+
406
+ ### Portal-Specific Access Control
407
+
408
+ Since `register_resource` doesn't support Rails' `only:` and `except:` options, access control is handled through portal-specific policies:
409
+
410
+ ```ruby
411
+ # Customer portal - read-only invoice access
412
+ class CustomerPortal::Billing::InvoicePolicy < Plutonium::Resource::Policy
413
+ def create?
414
+ false # Customers can't create invoices
415
+ end
416
+
417
+ def update?
418
+ false # Customers can't modify invoices
419
+ end
420
+
421
+ def destroy?
422
+ false # Customers can't delete invoices
423
+ end
424
+
425
+ def read?
426
+ record.organization == user.organization # Only their org's invoices
427
+ end
428
+ end
429
+
430
+ # Public portal - only published content
431
+ class PublicPortal::Blog::PostPolicy < Plutonium::Resource::Policy
432
+ def create?
433
+ false # No creation in public portal
434
+ end
435
+
436
+ def update?
437
+ false # No editing in public portal
438
+ end
439
+
440
+ def destroy?
441
+ false # No deletion in public portal
442
+ end
443
+
444
+ def read?
445
+ record.published? && record.public? # Only published, public posts
446
+ end
447
+ end
448
+ ```
449
+
450
+ ### Portal-Specific Policy Inheritance
451
+
452
+ Create portal-specific policy variations:
453
+
454
+ ```ruby
455
+ # Admin portal - enhanced permissions for admins
456
+ module AdminPortal
457
+ class UserPolicy < ::UserPolicy
458
+ def create?
459
+ user.super_admin? # Only super admins can create users
460
+ end
461
+
462
+ def destroy?
463
+ user.super_admin? && record != user # Can't delete themselves
464
+ end
465
+
466
+ def impersonate?
467
+ user.super_admin? && Rails.env.development?
468
+ end
469
+ end
470
+ end
471
+
472
+ # Customer portal - restricted permissions
473
+ module CustomerPortal
474
+ class ProjectPolicy < ::ProjectPolicy
475
+ def index?
476
+ true # Can list their projects
477
+ end
478
+
479
+ def show?
480
+ record.organization == user.organization # Only their org's projects
481
+ end
482
+
483
+ def create?
484
+ user.can_create_projects? && user.organization.active?
485
+ end
486
+
487
+ def destroy?
488
+ false # Customers can't delete projects
489
+ end
490
+ end
491
+ end
492
+ ```
493
+
494
+ ## Advanced Portal Customization
495
+
496
+ ### Portal-Specific Layouts and Styling
497
+
498
+ Each portal can have completely different visual designs:
499
+
500
+ ```erb
501
+ <!-- packages/admin_portal/app/views/layouts/admin_portal.html.erb -->
502
+ <!DOCTYPE html>
503
+ <html>
504
+ <head>
505
+ <title>Admin Portal - <%= @page_title || "Dashboard" %></title>
506
+ <%= csrf_meta_tags %>
507
+ <%= csp_meta_tag %>
508
+
509
+ <%= stylesheet_link_tag "admin_portal", "data-turbo-track": "reload" %>
510
+ <%= javascript_include_tag "admin_portal", "data-turbo-track": "reload", defer: true %>
511
+ </head>
512
+
513
+ <body class="admin-theme dark-mode">
514
+ <!-- Admin-specific navigation -->
515
+ <nav class="admin-nav">
516
+ <%= link_to "Dashboard", admin_portal.root_path, class: "nav-link" %>
517
+ <%= link_to "Users", admin_portal.users_path, class: "nav-link" %>
518
+ <%= link_to "Organizations", admin_portal.organizations_path, class: "nav-link" %>
519
+
520
+ <div class="nav-user">
521
+ <%= current_user.name %>
522
+ <%= link_to "Logout", admin_portal.logout_path, method: :delete %>
523
+ </div>
524
+ </nav>
525
+
526
+ <main class="admin-content">
527
+ <!-- Flash messages with admin styling -->
528
+ <% flash.each do |type, message| %>
529
+ <div class="alert alert-<%= type %> admin-alert">
530
+ <%= message %>
531
+ </div>
532
+ <% end %>
533
+
534
+ <%= yield %>
535
+ </main>
536
+ </body>
537
+ </html>
538
+ ```
539
+
540
+ ### Portal-Specific Components
541
+
542
+ Create reusable components tailored to each portal:
543
+
544
+ ```ruby
545
+ # packages/admin_portal/app/components/admin_portal/sidebar_component.rb
546
+ module AdminPortal
547
+ class SidebarComponent < Plutonium::UI::Component::Base
548
+ def view_template
549
+ aside(class: "admin-sidebar") do
550
+ nav do
551
+ ul(class: "nav-menu") do
552
+ li { link_to "Dashboard", root_path, class: nav_link_class("dashboard") }
553
+ li { link_to "Users", users_path, class: nav_link_class("users") }
554
+ li { link_to "Organizations", organizations_path, class: nav_link_class("organizations") }
555
+
556
+ # Conditional navigation based on permissions
557
+ if current_user.super_admin?
558
+ li { link_to "System Logs", system_logs_path, class: nav_link_class("logs") }
559
+ li { link_to "Analytics", analytics_path, class: nav_link_class("analytics") }
560
+ end
561
+
562
+ # Feature-flagged navigation
563
+ if FeatureFlag.enabled?(:billing_portal)
564
+ li { link_to "Billing", billing_path, class: nav_link_class("billing") }
565
+ end
566
+ end
567
+ end
568
+ end
569
+ end
570
+
571
+ private
572
+
573
+ def nav_link_class(section)
574
+ base_class = "nav-link"
575
+ base_class += " active" if current_section == section
576
+ base_class
577
+ end
578
+ end
579
+ end
580
+ ```
581
+
582
+ ## Portal Generation and Setup
583
+
584
+ ### Using Generators
585
+
586
+ Plutonium provides comprehensive generators for portal creation:
587
+
588
+ ```bash
589
+ # Generate a full-featured admin portal
590
+ rails generate pu:pkg:portal admin
591
+
592
+ # Generate a customer portal
593
+ rails generate pu:pkg:portal customer
594
+
595
+ # Generate a public portal
596
+ rails generate pu:pkg:portal public
597
+
598
+ # Connect existing resources to portals
599
+ rails generate pu:res:conn post --dest=admin_portal
600
+ rails generate pu:res:conn project --dest=customer_portal
601
+ ```
602
+
603
+ ### Generated Portal Structure
604
+
605
+ Generators create a well-organized portal structure:
606
+
607
+ ```
608
+ packages/admin_portal/
609
+ ├── app/
610
+ │ ├── controllers/
611
+ │ │ └── admin_portal/
612
+ │ │ ├── concerns/
613
+ │ │ │ └── controller.rb # Portal-wide controller logic
614
+ │ │ ├── dashboard_controller.rb # Portal dashboard
615
+ │ │ ├── plutonium_controller.rb # Base controller
616
+ │ │ └── resource_controller.rb # Resource controller base
617
+ │ ├── policies/
618
+ │ │ └── admin_portal/ # Portal-specific policies
619
+ │ ├── definitions/
620
+ │ │ └── admin_portal/ # Portal-specific resource definitions
621
+ │ └── views/
622
+ │ └── layouts/
623
+ │ └── admin_portal.html.erb # Portal-specific layout
624
+ ├── config/
625
+ │ └── routes.rb # Portal routes
626
+ └── lib/
627
+ └── engine.rb # Portal engine configuration
628
+ ```
629
+
630
+ ## Best Practices
631
+
632
+ ### Multi-Tenancy Best Practices
633
+
634
+ **Entity Modeling**
635
+ Design clear entity relationships:
636
+
637
+ ```ruby
638
+ # ✅ Good - clear entity hierarchy
639
+ class Organization < ApplicationRecord
640
+ has_many :users
641
+ has_many :projects
642
+ has_many :invoices
643
+ end
644
+
645
+ class User < ApplicationRecord
646
+ belongs_to :organization
647
+ has_many :projects
648
+ end
649
+
650
+ class Project < ApplicationRecord
651
+ belongs_to :organization
652
+ belongs_to :user
653
+ end
654
+ ```
655
+
656
+ **Consistent Scoping**
657
+ Use the same scoping strategy throughout your portal:
658
+
659
+ ```ruby
660
+ # ✅ Good - consistent scoping
661
+ class AdminPortal::Engine < Rails::Engine
662
+ scope_to_entity Organization, strategy: :path
663
+ end
664
+
665
+ # All controllers automatically scope to organization
666
+ # All policies receive the scoped organization context
667
+ ```
668
+
669
+ ### Security First
670
+
671
+ **Portal-Specific Authentication**
672
+ Use appropriate authentication for each portal:
673
+
674
+ ```ruby
675
+ # ✅ Good - tailored authentication
676
+ module AdminPortal::Concerns::Controller
677
+ include Plutonium::Auth::Rodauth(:admin)
678
+ end
679
+
680
+ module CustomerPortal::Concerns::Controller
681
+ include Plutonium::Auth::Rodauth(:customer)
682
+ end
683
+ ```
684
+
685
+ **Route Constraints**
686
+ Enforce authentication at the routing level:
687
+
688
+ ```ruby
689
+ # ✅ Good - route-level security
690
+ Rails.application.routes.draw do
691
+ constraints Rodauth::Rails.authenticate(:admin) do
692
+ mount AdminPortal::Engine, at: "/admin"
693
+ end
694
+
695
+ constraints Rodauth::Rails.authenticate(:customer) do
696
+ mount CustomerPortal::Engine, at: "/app"
697
+ end
698
+ end
699
+ ```
700
+
701
+ ## Integration with Other Modules
702
+
703
+ The Portal module works seamlessly with other Plutonium components:
704
+
705
+ - **[Core](./core.md)**: Provides base controller functionality and entity scoping capabilities
706
+ - **[Authentication](./authentication.md)**: Portal-specific authentication strategies and session management
707
+ - **[Policy](./policy.md)**: Entity-aware authorization and portal-specific access control
708
+ - **[Package](./package.md)**: Package-based organization and resource registration
709
+ - **[Resource Record](./resource_record.md)**: Resource controllers work seamlessly within portal contexts
710
+ - **[Routing](./routing.md)**: Automatic route generation with entity scoping and portal isolation