seams 0.1.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 +7 -0
- data/CHANGELOG.md +335 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
- data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
- data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
- data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
- data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
- data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
- data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
- data/lib/generators/seams/admin/admin_generator.rb +852 -0
- data/lib/generators/seams/admin/templates/README.md.tt +266 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
- data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
- data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
- data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
- data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
- data/lib/generators/seams/auth/auth_generator.rb +311 -0
- data/lib/generators/seams/auth/templates/README.md.tt +289 -0
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
- data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
- data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
- data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
- data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
- data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
- data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
- data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
- data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
- data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
- data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
- data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
- data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
- data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
- data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
- data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
- data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
- data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
- data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
- data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
- data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
- data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
- data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
- data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
- data/lib/generators/seams/billing/billing_generator.rb +476 -0
- data/lib/generators/seams/billing/templates/README.md.tt +355 -0
- data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
- data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
- data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
- data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
- data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
- data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
- data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
- data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
- data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
- data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
- data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
- data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
- data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
- data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
- data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
- data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
- data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
- data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
- data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
- data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
- data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
- data/lib/generators/seams/core/core_generator.rb +191 -0
- data/lib/generators/seams/core/templates/README.md.tt +45 -0
- data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
- data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
- data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
- data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
- data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
- data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
- data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
- data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
- data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
- data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
- data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
- data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
- data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
- data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
- data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
- data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
- data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
- data/lib/generators/seams/engine/engine_generator.rb +165 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
- data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
- data/lib/generators/seams/engine/templates/README.md.tt +40 -0
- data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
- data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
- data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
- data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
- data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
- data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
- data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
- data/lib/generators/seams/install/install_generator.rb +211 -0
- data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
- data/lib/generators/seams/install/templates/Procfile.tt +14 -0
- data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
- data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
- data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
- data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
- data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
- data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
- data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
- data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
- data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
- data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
- data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
- data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
- data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
- data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
- data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
- data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
- data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
- data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
- data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
- data/lib/generators/seams/remove/remove_generator.rb +259 -0
- data/lib/generators/seams/teams/teams_generator.rb +298 -0
- data/lib/generators/seams/teams/templates/README.md.tt +88 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
- data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
- data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
- data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
- data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
- data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
- data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
- data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
- data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
- data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
- data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
- data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
- data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
- data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
- data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
- data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
- data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
- data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
- data/lib/seams/cli/list.rb +111 -0
- data/lib/seams/cli/quality.rb +99 -0
- data/lib/seams/cli/resolve.rb +276 -0
- data/lib/seams/cli/test_changed.rb +116 -0
- data/lib/seams/cli.rb +48 -0
- data/lib/seams/configuration.rb +19 -0
- data/lib/seams/cops/known_queue_names.rb +42 -0
- data/lib/seams/cops/migration_comments.rb +68 -0
- data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
- data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
- data/lib/seams/cops.rb +18 -0
- data/lib/seams/event_registry.rb +49 -0
- data/lib/seams/events/adapter.rb +24 -0
- data/lib/seams/events/adapters/active_support.rb +31 -0
- data/lib/seams/events/publisher.rb +178 -0
- data/lib/seams/events.rb +39 -0
- data/lib/seams/generators/dummy_app_writer.rb +424 -0
- data/lib/seams/generators/eject_aware.rb +102 -0
- data/lib/seams/generators/follow_up_generator.rb +148 -0
- data/lib/seams/generators/host_injector.rb +124 -0
- data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
- data/lib/seams/generators/splicer.rb +217 -0
- data/lib/seams/observability/adapter.rb +33 -0
- data/lib/seams/observability/adapters/rails_logger.rb +59 -0
- data/lib/seams/observability.rb +34 -0
- data/lib/seams/runtime.rb +23 -0
- data/lib/seams/version.rb +5 -0
- data/lib/seams.rb +23 -0
- metadata +493 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Seams Admin
|
|
2
|
+
|
|
3
|
+
> Mountable admin engine for a Seams-powered host. Built on
|
|
4
|
+
> [Administrate](https://github.com/thoughtbot/administrate) for the
|
|
5
|
+
> dashboard surface and [Pundit](https://github.com/varvet/pundit) for
|
|
6
|
+
> per-record authorisation. Read-only over the existing seams tables —
|
|
7
|
+
> ships no migrations of its own.
|
|
8
|
+
|
|
9
|
+
## 30-second elevator pitch
|
|
10
|
+
|
|
11
|
+
`bin/rails generate seams:admin` writes an `engines/admin/` engine into
|
|
12
|
+
your host that mounts at `/admin` and ships:
|
|
13
|
+
|
|
14
|
+
- Twelve Administrate dashboards covering the canonical seams models
|
|
15
|
+
(Identity, Account, Membership × 2, Team, TeamMembership, Invitation,
|
|
16
|
+
Notification, NotificationPreference, Plan, Subscription, Invoice,
|
|
17
|
+
LifetimePass).
|
|
18
|
+
- Two operating modes — **platform** (admins see everything) and
|
|
19
|
+
**tenant** (admins are scoped to their own Account) — selected by a
|
|
20
|
+
single config knob.
|
|
21
|
+
- Pundit policies under `Admin::Platform::*` and `Admin::Tenant::*`,
|
|
22
|
+
selected at request time by `Seams::Admin.config.tenancy_scope`.
|
|
23
|
+
- Audit-log auto-write — every successful create/update/destroy emits a
|
|
24
|
+
`Core::AuditLog` row keyed on `Auth::Current.identity`.
|
|
25
|
+
- Four config knobs (`authenticator`, `tenancy_scope`, `theme_css_path`,
|
|
26
|
+
`before_admin_action`) for the predictable customisation surface.
|
|
27
|
+
|
|
28
|
+
No migrations, no new tables, no admin-only User model — see
|
|
29
|
+
"[Why `staff?` on Identity, not a separate `AdminUser` table?](#why-staff-on-identity-not-a-separate-adminuser-table)"
|
|
30
|
+
below.
|
|
31
|
+
|
|
32
|
+
## First sign-in (do this first)
|
|
33
|
+
|
|
34
|
+
After `bundle install` picks up `administrate` + `pundit`, promote
|
|
35
|
+
yourself to a platform admin from the Rails console (or via runner):
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
Auth::Identity.find_by(email: "you@example.com").update!(staff: true)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then boot the host and visit `/admin`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bin/rails server
|
|
45
|
+
open http://localhost:3000/admin
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
You should land on the Identities index with sidebar entries for all
|
|
49
|
+
twelve canonical models. If you see "Access denied" instead, the
|
|
50
|
+
authenticator returned a falsey value — double-check `staff` is set on
|
|
51
|
+
your Identity row (`Auth::Identity.find_by(email: ...).staff?` should
|
|
52
|
+
return `true`).
|
|
53
|
+
|
|
54
|
+
## Two-mode operation
|
|
55
|
+
|
|
56
|
+
The admin surface supports two tenancy modes, switched via a single
|
|
57
|
+
config knob:
|
|
58
|
+
|
|
59
|
+
### `:platform` mode (default)
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# config/initializers/seams_admin.rb
|
|
63
|
+
Seams::Admin.configure { |c| c.tenancy_scope = :platform }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **Gate:** `Auth::Identity#staff?` (boolean column on
|
|
67
|
+
`auth_identities`).
|
|
68
|
+
- **Visibility:** every Account's data, unfiltered.
|
|
69
|
+
- **Use case:** internal operator console — support staff
|
|
70
|
+
impersonation, cross-account search, billing reconciliation.
|
|
71
|
+
- **Policies:** `Admin::Platform::*Policy` namespace.
|
|
72
|
+
|
|
73
|
+
### `:tenant` mode
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# config/initializers/seams_admin.rb
|
|
77
|
+
Seams::Admin.configure { |c| c.tenancy_scope = :tenant }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- **Gate:** `Accounts::Membership#role == "admin"` (read off
|
|
81
|
+
`Accounts::Current.membership`).
|
|
82
|
+
- **Visibility:** only the current Account's data — every Pundit
|
|
83
|
+
`Scope` filters by `account_id`.
|
|
84
|
+
- **Use case:** a tenant-facing admin surface where each customer's
|
|
85
|
+
admin sees their own users / invoices / teams only.
|
|
86
|
+
- **Policies:** `Admin::Tenant::*Policy` namespace.
|
|
87
|
+
- **Requires:** the `accounts` engine. Without it the admin engine
|
|
88
|
+
fails fast at boot with a clear error message.
|
|
89
|
+
|
|
90
|
+
Switching modes is a config flip — no schema change, no regenerate.
|
|
91
|
+
The `policy_namespace` lookup on `Seams::Admin::ApplicationController`
|
|
92
|
+
reads `Seams::Admin.config.tenancy_scope` on every request.
|
|
93
|
+
|
|
94
|
+
## Configuration
|
|
95
|
+
|
|
96
|
+
Four knobs, deliberately small:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# config/initializers/seams_admin.rb
|
|
100
|
+
Seams::Admin.configure do |c|
|
|
101
|
+
# 1. authenticator — Callable taking the controller, returning truthy
|
|
102
|
+
# when the request is allowed in. Default: ->(ctrl) { ctrl.current_identity&.staff? }
|
|
103
|
+
c.authenticator = ->(ctrl) { ctrl.current_identity&.staff? }
|
|
104
|
+
|
|
105
|
+
# 2. tenancy_scope — :platform | :tenant. Selects the policy namespace
|
|
106
|
+
# at request time. See "Two-mode operation" above.
|
|
107
|
+
c.tenancy_scope = :platform
|
|
108
|
+
|
|
109
|
+
# 3. theme_css_path — Optional path to a host-supplied CSS file loaded
|
|
110
|
+
# on top of Administrate's stock styling. nil = use Administrate's
|
|
111
|
+
# defaults.
|
|
112
|
+
c.theme_css_path = nil
|
|
113
|
+
|
|
114
|
+
# 4. before_admin_action — Optional callable that runs before every
|
|
115
|
+
# admin action. The recommended hook for 2FA enforcement, IP
|
|
116
|
+
# allow-listing, audit-trail entry-point logging, etc. Receives the
|
|
117
|
+
# controller; raise to halt the request.
|
|
118
|
+
c.before_admin_action = nil
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Follow-up generators that ship new knobs splice their `attr_accessor`
|
|
123
|
+
into the `admin.configuration.attributes` insertion point in
|
|
124
|
+
`engines/admin/lib/admin/configuration.rb` and the matching default
|
|
125
|
+
into `admin.configuration.defaults`.
|
|
126
|
+
|
|
127
|
+
## Why `staff?` on Identity, not a separate `AdminUser` table?
|
|
128
|
+
|
|
129
|
+
Classic Rails apps separate `User` and `AdminUser` because `User`
|
|
130
|
+
carries customer-facing concerns (orders, billing, notifications, etc.)
|
|
131
|
+
that you do not want admins to inherit. **In seams Wave 9
|
|
132
|
+
`Auth::Identity` is *already* the credential-only object** — it has no
|
|
133
|
+
customer concerns to inherit from. Customer-facing data lives on
|
|
134
|
+
`Accounts::Membership` (account-scoped) and the host's own domain
|
|
135
|
+
models. A boolean `staff?` flag on Identity is the right granularity.
|
|
136
|
+
|
|
137
|
+
The seams "User and AdminUser must live on separate tables" rule is
|
|
138
|
+
**reinterpreted, not violated**: the rule's intent (no customer state
|
|
139
|
+
on the admin authentication object) is fully satisfied by the Wave 9
|
|
140
|
+
credential-only Identity. Flagging it explicitly here so future
|
|
141
|
+
contributors don't read this engine and assume the rule was forgotten.
|
|
142
|
+
|
|
143
|
+
Hosts that need hard isolation (regulatory compliance, separate auth
|
|
144
|
+
flow, dedicated SSO for admins) override the authenticator:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
Seams::Admin.configure do |c|
|
|
148
|
+
c.authenticator = ->(ctrl) { ctrl.current_admin_user.present? }
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
…and bring their own `current_admin_user` resolution from a separate
|
|
153
|
+
auth surface.
|
|
154
|
+
|
|
155
|
+
## Customising a dashboard (Ejecting)
|
|
156
|
+
|
|
157
|
+
The seams pattern: **eject the dashboard, then edit your local copy**.
|
|
158
|
+
Future `bin/seams admin` runs leave ejected files alone.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bin/seams resolve --eject admin/app/dashboards/admin/identity_dashboard.rb
|
|
162
|
+
# Now edit engines/admin/app/dashboards/admin/identity_dashboard.rb freely.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For new dashboards covering host-specific models, generate via
|
|
166
|
+
Administrate, then move the result under the `Admin::*` namespace:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
bin/rails generate administrate:dashboard MyHost::Project
|
|
170
|
+
# Move app/dashboards/my_host/project_dashboard.rb to
|
|
171
|
+
# engines/admin/app/dashboards/admin/project_dashboard.rb
|
|
172
|
+
# Move the controller similarly under app/controllers/admin/.
|
|
173
|
+
# Add `resources :projects, controller: "admin/projects"` to
|
|
174
|
+
# engines/admin/config/routes.rb (or splice via the
|
|
175
|
+
# admin.routes.after_resources insertion point in a follow-up generator).
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Administrate's full dashboard / field / controller surface is documented
|
|
179
|
+
at <https://administrate-demo.herokuapp.com/>.
|
|
180
|
+
|
|
181
|
+
## Error messages a host operator will see
|
|
182
|
+
|
|
183
|
+
| Symptom | Likely cause | Fix |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| `[seams admin] missing required dependency: Auth::Identity ...` at boot | The auth engine isn't installed. | `bin/rails generate seams:auth` |
|
|
186
|
+
| `[seams admin] missing required dependency: Administrate ...` at boot | `gem "administrate"` not in the host Gemfile. | `bundle install` (the admin generator wires it; if you regenerated and skipped, add `gem "administrate", "~> 1.0"` manually). |
|
|
187
|
+
| `[seams admin] missing required dependency: Pundit ...` at boot | `gem "pundit"` not in the host Gemfile. | Same fix — the generator wires it. |
|
|
188
|
+
| 403 "Access denied. The seams admin surface is gated on `Seams::Admin.config.authenticator`..." on every request | The configured gate returned falsey for the signed-in Identity. | Default gate is `current_identity&.staff?`. Set `staff: true` on your Identity row, or override the authenticator. |
|
|
189
|
+
| `Pundit::NotAuthorizedError` mid-request | A per-action policy denied the request. | Eject the relevant policy under `engines/admin/app/policies/admin/{platform,tenant}/<resource>_policy.rb` and override the action predicate (e.g. `def destroy?; false; end`). |
|
|
190
|
+
|
|
191
|
+
## What's not shipped (deferred)
|
|
192
|
+
|
|
193
|
+
- **2FA / IP allow-list for `/admin`.** Not shipped. Wire your own via
|
|
194
|
+
`Seams::Admin.config.before_admin_action`.
|
|
195
|
+
- **Branded UI.** Stock Administrate CSS only in v1. Override via
|
|
196
|
+
`theme_css_path`.
|
|
197
|
+
- **Search beyond Administrate's stock.** Administrate ships a basic
|
|
198
|
+
Ransack-backed search; rich filtering / faceted search is your job.
|
|
199
|
+
- **Batch actions.** Not in Administrate's surface; not shipped here.
|
|
200
|
+
Override the relevant controller's `index` action and emit your own
|
|
201
|
+
bulk-action button if you need them.
|
|
202
|
+
|
|
203
|
+
## Insertion-point markers
|
|
204
|
+
|
|
205
|
+
This engine ships five Wave-10 insertion-point markers that follow-up
|
|
206
|
+
generators target:
|
|
207
|
+
|
|
208
|
+
| Marker | File | Purpose |
|
|
209
|
+
| --- | --- | --- |
|
|
210
|
+
| `admin.engine.events` | `lib/admin/engine.rb` | Register new admin events (e.g. `admin.action.taken.admin`). |
|
|
211
|
+
| `admin.routes.before_resources` | `config/routes.rb` | Ahead-of-resource routes (impersonation entry points, bulk-action endpoints). |
|
|
212
|
+
| `admin.routes.after_resources` | `config/routes.rb` | New top-level admin routes (custom collection routes, JSON-only endpoints). |
|
|
213
|
+
| `admin.configuration.attributes` | `lib/admin/configuration.rb` | New configuration knobs. |
|
|
214
|
+
| `admin.configuration.defaults` | `lib/admin/configuration.rb` | Defaults for the new knobs. |
|
|
215
|
+
|
|
216
|
+
`bin/seams resolve --list-markers admin` prints the live list against
|
|
217
|
+
the engine source.
|
|
218
|
+
|
|
219
|
+
## Audit log
|
|
220
|
+
|
|
221
|
+
Every successful create/update/destroy on an admin dashboard writes a
|
|
222
|
+
`Core::AuditLog` row via the `record_admin_audit` after_action. The
|
|
223
|
+
row carries:
|
|
224
|
+
|
|
225
|
+
- `action` — `"create"`, `"update"`, or `"destroy"`.
|
|
226
|
+
- `auditable_type` / `auditable_id` — the resource Administrate
|
|
227
|
+
resolved.
|
|
228
|
+
- `actor_id` — `Auth::Current.identity&.id`.
|
|
229
|
+
- `payload` — for updates, `record.saved_changes.transform_values(&:last)`;
|
|
230
|
+
for create/destroy, attributes minus timestamps.
|
|
231
|
+
|
|
232
|
+
If the core engine isn't installed (and `Core::AuditLog` is therefore
|
|
233
|
+
undefined), the after_action no-ops cleanly — admin still works,
|
|
234
|
+
audit log is silently absent.
|
|
235
|
+
|
|
236
|
+
## Why Administrate?
|
|
237
|
+
|
|
238
|
+
Considered: ActiveAdmin, Avo, Trestle, Motor Admin, RailsAdmin, and
|
|
239
|
+
hand-rolling on top of Rails 8 scaffolds. Administrate wins because:
|
|
240
|
+
|
|
241
|
+
- Dashboards are plain Ruby classes (`Admin::IdentityDashboard <
|
|
242
|
+
Administrate::BaseDashboard`). No DSL, no Arbre. Matches seams' house
|
|
243
|
+
style.
|
|
244
|
+
- Custom fields are `rails g administrate:field foo` — a class + an ERB
|
|
245
|
+
partial. Perfect for masking encrypted columns and rendering Stripe
|
|
246
|
+
IDs.
|
|
247
|
+
- ORM-agnostic.
|
|
248
|
+
- Pundit-aware via documented `policy_namespace` integration. Clean
|
|
249
|
+
surface for the platform-vs-tenant policy split.
|
|
250
|
+
- thoughtbot maintains it.
|
|
251
|
+
- Easiest of the surveyed frameworks to embed inside another engine —
|
|
252
|
+
no assumption that the host owns `/admin`.
|
|
253
|
+
|
|
254
|
+
The full per-framework comparison lives in
|
|
255
|
+
`proposals/admin_engine_administrate.md` (the Wave 11A research
|
|
256
|
+
report).
|
|
257
|
+
|
|
258
|
+
## Running the specs
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
bin/rails seams:test[admin]
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The boot spec asserts the engine loads, the four config knobs default
|
|
265
|
+
correctly, every dashboard + policy class is reachable, and both
|
|
266
|
+
tenancy modes route to the right policy namespace.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the Accounts::Account dashboard.
|
|
4
|
+
# Subclasses Seams::Admin::ApplicationController so the gate, the
|
|
5
|
+
# pundit_user hook, and Phase 3's audit-log auto-write apply.
|
|
6
|
+
module Admin
|
|
7
|
+
class AccountsController < ::Seams::Admin::ApplicationController
|
|
8
|
+
def resource_class
|
|
9
|
+
::Accounts::Account
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def dashboard
|
|
13
|
+
@dashboard ||= AccountDashboard.new
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the Accounts::Membership dashboard.
|
|
4
|
+
# Routed under `accounts_memberships` (engine-prefixed) to disambiguate
|
|
5
|
+
# from `teams_memberships` — both engines ship a Membership model.
|
|
6
|
+
module Admin
|
|
7
|
+
class AccountsMembershipsController < ::Seams::Admin::ApplicationController
|
|
8
|
+
def resource_class
|
|
9
|
+
::Accounts::Membership
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def dashboard
|
|
13
|
+
@dashboard ||= AccountsMembershipDashboard.new
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Seams
|
|
4
|
+
module Admin
|
|
5
|
+
# Base controller for the seams admin engine. Subclasses
|
|
6
|
+
# Administrate::ApplicationController so dashboards (Phase 2) get
|
|
7
|
+
# the framework's defaults — index/show/new/create/edit/update/destroy,
|
|
8
|
+
# search, filtering, pagination — for free.
|
|
9
|
+
#
|
|
10
|
+
# ## Three layered responsibilities
|
|
11
|
+
#
|
|
12
|
+
# 1. **Authentication gate** — Phase 1's `Seams::Admin::Authenticator`
|
|
13
|
+
# concern installs `before_action :authenticate_admin!`. Default
|
|
14
|
+
# gate: `current_identity&.staff?`. Configurable via
|
|
15
|
+
# `Seams::Admin.config.authenticator`.
|
|
16
|
+
#
|
|
17
|
+
# 2. **Resource-level authorisation** — Phase 3 wires Pundit via
|
|
18
|
+
# Administrate's canonical `Administrate::Punditize` concern.
|
|
19
|
+
# `Punditize` is the integration thoughtbot ships and documents:
|
|
20
|
+
# it overrides Administrate's `authorized_action?` and
|
|
21
|
+
# `scoped_resource` to delegate to Pundit, so a per-action
|
|
22
|
+
# permission check (`policy.update?`) AND the list-view
|
|
23
|
+
# `policy_scope` both fire automatically. Without including
|
|
24
|
+
# `Punditize`, Administrate's default `authorized_action?`
|
|
25
|
+
# returns true for everything — Pundit policies would be loaded
|
|
26
|
+
# but never consulted.
|
|
27
|
+
#
|
|
28
|
+
# The platform-vs-tenant split lives in `policy_namespace`:
|
|
29
|
+
# Pundit's array-form lookup (`Pundit.policy!(user, [ns, record])`)
|
|
30
|
+
# resolves to `Admin::Platform::*Policy` or
|
|
31
|
+
# `Admin::Tenant::*Policy` depending on the active mode.
|
|
32
|
+
#
|
|
33
|
+
# 3. **Audit-log auto-write** — Phase 3 emits a `Core::AuditLog` row
|
|
34
|
+
# for every successful create/update/destroy via the
|
|
35
|
+
# `record_admin_audit` after_action. The actor is
|
|
36
|
+
# `Auth::Current.identity` (the human driving the request); the
|
|
37
|
+
# auditable is Administrate's `requested_resource`.
|
|
38
|
+
#
|
|
39
|
+
# The order here is deliberate: gate, then authorise, then act,
|
|
40
|
+
# then audit. A request that fails the gate never reaches a
|
|
41
|
+
# policy; a request that fails a policy never reaches the action;
|
|
42
|
+
# the audit row only writes after a successful action.
|
|
43
|
+
class ApplicationController < ::Administrate::ApplicationController
|
|
44
|
+
include ::Seams::Admin::Authenticator
|
|
45
|
+
# Administrate's canonical Pundit integration. Internally:
|
|
46
|
+
# - includes Pundit::Authorization
|
|
47
|
+
# - overrides `authorized_action?` to call
|
|
48
|
+
# `Pundit.policy!(pundit_user, [*policy_namespace, resource]).public_send("#{action}?")`
|
|
49
|
+
# - overrides `scoped_resource` to apply `Pundit.policy_scope!`
|
|
50
|
+
# using the same array-form namespace
|
|
51
|
+
# - overrides `authorize_resource` to use `authorize` with the
|
|
52
|
+
# namespaced array
|
|
53
|
+
# Defining our own `policy_namespace` is the supported way to
|
|
54
|
+
# switch between Admin::Platform::* and Admin::Tenant::*.
|
|
55
|
+
include ::Administrate::Punditize
|
|
56
|
+
|
|
57
|
+
# Pundit raises `Pundit::NotAuthorizedError` when a policy denies a
|
|
58
|
+
# request. Without an explicit rescue the host inherits the
|
|
59
|
+
# exception and renders a 500. Treat it as a 403 and surface a
|
|
60
|
+
# clear message; the after_action audit hook also runs (response
|
|
61
|
+
# is set), so audit_log records the rejected attempt.
|
|
62
|
+
rescue_from ::Pundit::NotAuthorizedError, with: :respond_with_admin_unauthorised
|
|
63
|
+
|
|
64
|
+
# Catch the parallel exception Administrate raises from inside its
|
|
65
|
+
# own `authorize_resource` when (for whatever reason) Punditize is
|
|
66
|
+
# bypassed. Same handling — present the operator with a 403.
|
|
67
|
+
rescue_from ::Administrate::NotAuthorizedError, with: :respond_with_admin_unauthorised
|
|
68
|
+
|
|
69
|
+
# Phase 3 audit-log auto-write. Runs after successful create /
|
|
70
|
+
# update / destroy actions. Wrapped in `if defined?(Core::AuditLog)`
|
|
71
|
+
# so the engine is usable without the seams core engine —
|
|
72
|
+
# operators who don't ship core get no audit log, which is the
|
|
73
|
+
# correct behaviour when the table doesn't exist.
|
|
74
|
+
after_action :record_admin_audit, only: %i[create update destroy]
|
|
75
|
+
|
|
76
|
+
# Pundit ships `verify_authorized` / `verify_policy_scoped`
|
|
77
|
+
# after_actions that raise if a controller didn't call
|
|
78
|
+
# `authorize` / `policy_scope`. Administrate's `Punditize`
|
|
79
|
+
# already calls them on every standard action, so any future
|
|
80
|
+
# custom action added to a subclass that forgets the call gets
|
|
81
|
+
# caught loudly in development. `:only` excludes `:index`
|
|
82
|
+
# (covered by `verify_policy_scoped` instead) and the rescue
|
|
83
|
+
# responders themselves (which never authorise — they 403).
|
|
84
|
+
after_action :verify_authorized, except: %i[index respond_with_admin_unauthorised]
|
|
85
|
+
after_action :verify_policy_scoped, only: %i[index]
|
|
86
|
+
|
|
87
|
+
# Helper exposed to dashboards so they can read the active
|
|
88
|
+
# tenancy mode (:platform vs :tenant). Phase 3 reads this in
|
|
89
|
+
# policy scopes; dashboards may also branch on it for
|
|
90
|
+
# account_id columns.
|
|
91
|
+
helper_method :seams_admin_tenancy_scope
|
|
92
|
+
|
|
93
|
+
def seams_admin_tenancy_scope
|
|
94
|
+
::Seams::Admin.config.tenancy_scope
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Pundit hook. Returns a `Seams::Admin::Context` Struct wrapping
|
|
98
|
+
# the current Identity and the current Accounts::Membership so
|
|
99
|
+
# tenant policies can read both signals (staff? + role/account_id)
|
|
100
|
+
# without each policy reaching into the controller. Returning a
|
|
101
|
+
# raw Identity would lose the membership; returning the
|
|
102
|
+
# controller would expose too much. The Struct is the smallest
|
|
103
|
+
# surface that lets every policy stay decoupled from request
|
|
104
|
+
# state.
|
|
105
|
+
#
|
|
106
|
+
# `current_membership` is read defensively: hosts that don't
|
|
107
|
+
# ship the accounts engine (platform-only deployments) leave it
|
|
108
|
+
# nil, and the platform policies don't need it.
|
|
109
|
+
def pundit_user
|
|
110
|
+
identity = ::Auth::Current.identity if defined?(::Auth::Current)
|
|
111
|
+
membership = current_membership_for_admin
|
|
112
|
+
::Seams::Admin::Context.new(identity, membership)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# The Pundit policy namespace for the active tenancy mode.
|
|
116
|
+
# `Administrate::Punditize` calls `Pundit.policy!(pundit_user,
|
|
117
|
+
# [*policy_namespace, resource])` — Pundit's array-form lookup
|
|
118
|
+
# walks the namespace tree (`Admin::Platform::IdentityPolicy`
|
|
119
|
+
# for an `Auth::Identity` resource under `[Admin, Platform]`).
|
|
120
|
+
# Returning an Array of constants (NOT a single Module) is what
|
|
121
|
+
# Punditize expects.
|
|
122
|
+
#
|
|
123
|
+
# The `Admin` segment is the engine's policy module root; the
|
|
124
|
+
# second segment switches on tenancy mode. Hosts switching modes
|
|
125
|
+
# at runtime per-request (rare; typically configured once at boot)
|
|
126
|
+
# override this method on a subclass and consult their own request
|
|
127
|
+
# state.
|
|
128
|
+
def policy_namespace
|
|
129
|
+
case ::Seams::Admin.config.tenancy_scope
|
|
130
|
+
when :tenant then [::Admin::Tenant]
|
|
131
|
+
else [::Admin::Platform]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# Resolve the current Accounts::Membership for tenant-mode
|
|
138
|
+
# policy decisions. Delegates to
|
|
139
|
+
# `Seams::Admin.config.current_membership_resolver` (a callable
|
|
140
|
+
# taking the controller). The default reads
|
|
141
|
+
# `Accounts::Current.membership` (Wave 9's CurrentAttributes
|
|
142
|
+
# object). Hosts using a different membership-resolution shape
|
|
143
|
+
# set their own resolver in `config/initializers/seams_admin.rb`
|
|
144
|
+
# rather than ejecting this controller.
|
|
145
|
+
def current_membership_for_admin
|
|
146
|
+
resolver = ::Seams::Admin.config.current_membership_resolver
|
|
147
|
+
return nil unless resolver.respond_to?(:call)
|
|
148
|
+
|
|
149
|
+
resolver.call(self)
|
|
150
|
+
rescue StandardError => e
|
|
151
|
+
Rails.logger.warn("[seams admin] membership resolver raised: #{e.class}: #{e.message}") if defined?(Rails)
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Phase 3 audit-log auto-write. Records a `Core::AuditLog` row
|
|
156
|
+
# for every successful create/update/destroy.
|
|
157
|
+
#
|
|
158
|
+
# Three guards keep the row from writing in cases that would
|
|
159
|
+
# either crash or duplicate:
|
|
160
|
+
#
|
|
161
|
+
# - `defined?(::Core::AuditLog)` — the engine is usable
|
|
162
|
+
# without the seams core engine; no constant means no row.
|
|
163
|
+
# - `response.successful? || response.redirect?` — only audit
|
|
164
|
+
# actions whose response is in the 2xx/3xx range. A failed
|
|
165
|
+
# update (4xx) skipped through the after_action shouldn't
|
|
166
|
+
# leave a misleading "update happened" row.
|
|
167
|
+
# - `record.respond_to?(:audit_logs)` — when the host model
|
|
168
|
+
# already includes `Core::Auditable`, that concern's
|
|
169
|
+
# `after_commit` hook ALREADY wrote a row. Writing a second
|
|
170
|
+
# row from this controller would double-log. We detect the
|
|
171
|
+
# `audit_logs` association the concern installs and skip.
|
|
172
|
+
#
|
|
173
|
+
# `requested_resource` is Administrate's resolved record. On
|
|
174
|
+
# `create` it's the just-saved record; on `update` it's the
|
|
175
|
+
# just-updated record; on `destroy` it's the record that was just
|
|
176
|
+
# destroyed (frozen but still readable for `id` / `class.name`).
|
|
177
|
+
def record_admin_audit
|
|
178
|
+
return unless defined?(::Core::AuditLog)
|
|
179
|
+
return unless response.successful? || response.redirect?
|
|
180
|
+
|
|
181
|
+
record = requested_resource_for_audit
|
|
182
|
+
return if record.nil?
|
|
183
|
+
return if auditable_via_concern?(record)
|
|
184
|
+
|
|
185
|
+
::Core::AuditLog.create!(
|
|
186
|
+
action: action_name,
|
|
187
|
+
auditable_type: record.class.name,
|
|
188
|
+
auditable_id: record.id,
|
|
189
|
+
actor_id: admin_audit_actor_id,
|
|
190
|
+
payload: admin_audit_payload(record)
|
|
191
|
+
)
|
|
192
|
+
rescue StandardError => e
|
|
193
|
+
# Audit-log failures must not break the admin response. A
|
|
194
|
+
# missing column, a transient DB hiccup, or a unique-constraint
|
|
195
|
+
# violation should be logged and swallowed — the admin action
|
|
196
|
+
# itself already succeeded by the time after_action runs.
|
|
197
|
+
# Operators wanting a structured signal can subscribe to
|
|
198
|
+
# ActiveSupport::Notifications "seams.admin.audit_failed".
|
|
199
|
+
Rails.logger.warn("[seams admin] audit log write failed: #{e.class}: #{e.message}") if defined?(Rails)
|
|
200
|
+
if defined?(::ActiveSupport::Notifications)
|
|
201
|
+
::ActiveSupport::Notifications.instrument(
|
|
202
|
+
"seams.admin.audit_failed",
|
|
203
|
+
error_class: e.class.name, message: e.message,
|
|
204
|
+
action: action_name, resource_class: record&.class&.name
|
|
205
|
+
)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Read `requested_resource` defensively. On a destroy action
|
|
210
|
+
# Administrate's `requested_resource` is the just-destroyed
|
|
211
|
+
# (frozen) record; reading `.id` and `.class.name` still works.
|
|
212
|
+
# On other actions Administrate caches the resource in
|
|
213
|
+
# `@requested_resource`, so calling the public method is cheap.
|
|
214
|
+
# Wrap in a rescue: if the action never resolved a resource (a
|
|
215
|
+
# subclass that overrode `requested_resource` to raise) we don't
|
|
216
|
+
# want the audit hook to take down the whole response.
|
|
217
|
+
def requested_resource_for_audit
|
|
218
|
+
respond_to?(:requested_resource, true) ? requested_resource : nil
|
|
219
|
+
rescue StandardError
|
|
220
|
+
nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# `Core::Auditable` adds `has_many :audit_logs` to the host model
|
|
224
|
+
# AND wires `after_commit` callbacks that write a row directly.
|
|
225
|
+
# If the model includes the concern, the row was already written
|
|
226
|
+
# by the model's own callback — writing another from here would
|
|
227
|
+
# double-log. The `audit_logs` association is the cheapest
|
|
228
|
+
# detection signal that doesn't require requiring `Core::Auditable`
|
|
229
|
+
# itself (which lives in the core engine, not loaded by the
|
|
230
|
+
# admin engine's dummy app).
|
|
231
|
+
def auditable_via_concern?(record)
|
|
232
|
+
return false unless record.class.respond_to?(:reflect_on_association)
|
|
233
|
+
|
|
234
|
+
reflection = record.class.reflect_on_association(:audit_logs)
|
|
235
|
+
return false if reflection.nil?
|
|
236
|
+
|
|
237
|
+
reflection.options[:as] == :auditable
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def admin_audit_actor_id
|
|
241
|
+
return nil unless defined?(::Auth::Current)
|
|
242
|
+
|
|
243
|
+
::Auth::Current.identity&.id
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def admin_audit_payload(record)
|
|
247
|
+
case action_name
|
|
248
|
+
when "update"
|
|
249
|
+
# `saved_changes` is the canonical Rails surface for "what
|
|
250
|
+
# this update actually wrote", available on the just-saved
|
|
251
|
+
# record. Empty for a no-op update.
|
|
252
|
+
record.respond_to?(:saved_changes) ? record.saved_changes.transform_values(&:last) : {}
|
|
253
|
+
else
|
|
254
|
+
# create + destroy snapshot the attributes minus timestamps
|
|
255
|
+
# so the row is readable without the audit-log consumer
|
|
256
|
+
# having to filter timestamp noise.
|
|
257
|
+
record.respond_to?(:attributes) ? record.attributes.except("created_at", "updated_at") : {}
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Pundit / Administrate authorisation failure handler. Renders a
|
|
262
|
+
# plain 403 — same shape as the authenticator concern's
|
|
263
|
+
# `respond_with_admin_forbidden` to keep the operator-visible
|
|
264
|
+
# surface consistent.
|
|
265
|
+
def respond_with_admin_unauthorised(_exception = nil)
|
|
266
|
+
message = <<~MSG.strip
|
|
267
|
+
Access denied. The seams admin engine's Pundit policy denied
|
|
268
|
+
this request. The active tenancy mode is
|
|
269
|
+
`Seams::Admin.config.tenancy_scope = #{::Seams::Admin.config.tenancy_scope.inspect}`;
|
|
270
|
+
if you expected access, check the matching policy under
|
|
271
|
+
`Admin::#{::Seams::Admin.config.tenancy_scope.to_s.camelize}::*Policy`.
|
|
272
|
+
MSG
|
|
273
|
+
|
|
274
|
+
if respond_to?(:render)
|
|
275
|
+
render plain: message, status: :forbidden
|
|
276
|
+
else
|
|
277
|
+
head :forbidden
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the Auth::Identity dashboard.
|
|
4
|
+
#
|
|
5
|
+
# Subclasses Seams::Admin::ApplicationController (Phase 1's
|
|
6
|
+
# gate-bearing parent) so the authenticator concern, the
|
|
7
|
+
# `pundit_user` hook, and Phase 3's audit-log auto-write apply
|
|
8
|
+
# uniformly across every dashboard. Do NOT inherit directly from
|
|
9
|
+
# Administrate::ApplicationController — that bypasses the gate.
|
|
10
|
+
#
|
|
11
|
+
# Administrate resolves `resource_class` from the controller name
|
|
12
|
+
# automatically via its inflector hook, but we override it explicitly
|
|
13
|
+
# here because the Identity model lives under the Auth namespace
|
|
14
|
+
# (Auth::Identity) and the default inflection would resolve to a
|
|
15
|
+
# top-level Identity constant that does not exist.
|
|
16
|
+
module Admin
|
|
17
|
+
class IdentitiesController < ::Seams::Admin::ApplicationController
|
|
18
|
+
def resource_class
|
|
19
|
+
::Auth::Identity
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dashboard
|
|
23
|
+
@dashboard ||= IdentityDashboard.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the Teams::Invitation dashboard.
|
|
4
|
+
module Admin
|
|
5
|
+
class InvitationsController < ::Seams::Admin::ApplicationController
|
|
6
|
+
def resource_class
|
|
7
|
+
::Teams::Invitation
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def dashboard
|
|
11
|
+
@dashboard ||= InvitationDashboard.new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the Billing::Invoice dashboard.
|
|
4
|
+
module Admin
|
|
5
|
+
class InvoicesController < ::Seams::Admin::ApplicationController
|
|
6
|
+
def resource_class
|
|
7
|
+
::Billing::Invoice
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def dashboard
|
|
11
|
+
@dashboard ||= InvoiceDashboard.new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the Billing::LifetimePass dashboard.
|
|
4
|
+
module Admin
|
|
5
|
+
class LifetimePassesController < ::Seams::Admin::ApplicationController
|
|
6
|
+
def resource_class
|
|
7
|
+
::Billing::LifetimePass
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def dashboard
|
|
11
|
+
@dashboard ||= LifetimePassDashboard.new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Administrate controller for the
|
|
4
|
+
# Notifications::NotificationPreference dashboard.
|
|
5
|
+
module Admin
|
|
6
|
+
class NotificationPreferencesController < ::Seams::Admin::ApplicationController
|
|
7
|
+
def resource_class
|
|
8
|
+
::Notifications::NotificationPreference
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def dashboard
|
|
12
|
+
@dashboard ||= NotificationPreferenceDashboard.new
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|