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,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "administrate/base_dashboard"
|
|
4
|
+
|
|
5
|
+
# Administrate dashboard for Teams::Team — the unit of multi-tenant
|
|
6
|
+
# ownership inside the teams engine. Teams are peers to Accounts in
|
|
7
|
+
# the Wave 9 architecture; the join from Team to Identity is direct
|
|
8
|
+
# (Teams::Membership), not via Accounts::Membership.
|
|
9
|
+
module Admin
|
|
10
|
+
class TeamDashboard < Administrate::BaseDashboard
|
|
11
|
+
ATTRIBUTE_TYPES = {
|
|
12
|
+
id: Field::Number,
|
|
13
|
+
name: Field::String,
|
|
14
|
+
slug: Field::String,
|
|
15
|
+
memberships: Field::HasMany.with_options(class_name: "Teams::Membership"),
|
|
16
|
+
invitations: Field::HasMany.with_options(class_name: "Teams::Invitation"),
|
|
17
|
+
created_at: Field::DateTime,
|
|
18
|
+
updated_at: Field::DateTime
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
COLLECTION_ATTRIBUTES = %i[id name slug created_at].freeze
|
|
22
|
+
|
|
23
|
+
SHOW_PAGE_ATTRIBUTES = %i[
|
|
24
|
+
id
|
|
25
|
+
name
|
|
26
|
+
slug
|
|
27
|
+
memberships
|
|
28
|
+
invitations
|
|
29
|
+
created_at
|
|
30
|
+
updated_at
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
FORM_ATTRIBUTES = %i[name slug].freeze
|
|
34
|
+
|
|
35
|
+
def display_resource(team)
|
|
36
|
+
team.name.presence || "Team ##{team.id}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "administrate/base_dashboard"
|
|
4
|
+
|
|
5
|
+
# Administrate dashboard for Teams::Membership — joins a Teams::Team
|
|
6
|
+
# to an Auth::Identity with a role.
|
|
7
|
+
#
|
|
8
|
+
# Class name is `Admin::TeamsMembershipDashboard` to disambiguate from
|
|
9
|
+
# `Admin::AccountsMembershipDashboard`; both engines ship a Membership
|
|
10
|
+
# model and Administrate's dashboard lookup resolves by class name.
|
|
11
|
+
#
|
|
12
|
+
# `identity_id` is a bare bigint FK — cross-engine model access is
|
|
13
|
+
# forbidden by the NoCrossEngineModelAccess cop, so the admin surface
|
|
14
|
+
# treats it as a typed integer reference rather than a belongs_to.
|
|
15
|
+
module Admin
|
|
16
|
+
class TeamsMembershipDashboard < Administrate::BaseDashboard
|
|
17
|
+
ATTRIBUTE_TYPES = {
|
|
18
|
+
id: Field::Number,
|
|
19
|
+
team: Field::BelongsTo.with_options(class_name: "Teams::Team"),
|
|
20
|
+
identity_id: Field::Number,
|
|
21
|
+
role: Field::Select.with_options(collection: %w[owner admin member]),
|
|
22
|
+
created_at: Field::DateTime,
|
|
23
|
+
updated_at: Field::DateTime
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
COLLECTION_ATTRIBUTES = %i[id team role created_at].freeze
|
|
27
|
+
|
|
28
|
+
SHOW_PAGE_ATTRIBUTES = %i[
|
|
29
|
+
id
|
|
30
|
+
team
|
|
31
|
+
identity_id
|
|
32
|
+
role
|
|
33
|
+
created_at
|
|
34
|
+
updated_at
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
FORM_ATTRIBUTES = %i[team identity_id role].freeze
|
|
38
|
+
|
|
39
|
+
def display_resource(membership)
|
|
40
|
+
"Team Membership ##{membership.id} (#{membership.role})"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Platform
|
|
5
|
+
# Abstract base for every platform-mode admin policy. Platform mode
|
|
6
|
+
# gates on `Auth::Identity#staff?` — a single boolean on the
|
|
7
|
+
# credential-only Identity model. There is no per-tenant scoping;
|
|
8
|
+
# the Scope inner class returns `scope.all`.
|
|
9
|
+
#
|
|
10
|
+
# `user` here is the value returned from
|
|
11
|
+
# `Seams::Admin::ApplicationController#pundit_user`. Phase 3 chose
|
|
12
|
+
# the `Seams::Admin::Context` Struct (identity + membership) as the
|
|
13
|
+
# pundit_user — both fields are nil-safe so the platform policies
|
|
14
|
+
# only need to read `user.identity&.staff?`.
|
|
15
|
+
#
|
|
16
|
+
# Hosts that need granular per-action gates (e.g. "non-staff support
|
|
17
|
+
# users may read but not destroy") eject the relevant per-model
|
|
18
|
+
# policy and override `destroy?` etc. there. The default surface
|
|
19
|
+
# here is intentionally uniform: any staff Identity can do anything.
|
|
20
|
+
class ApplicationPolicy
|
|
21
|
+
attr_reader :user, :record
|
|
22
|
+
|
|
23
|
+
def initialize(user, record)
|
|
24
|
+
@user = user
|
|
25
|
+
@record = record
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def index?
|
|
29
|
+
staff?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def show?
|
|
33
|
+
staff?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def new?
|
|
37
|
+
staff?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create?
|
|
41
|
+
staff?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def edit?
|
|
45
|
+
staff?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def update?
|
|
49
|
+
staff?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def destroy?
|
|
53
|
+
staff?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class Scope
|
|
57
|
+
attr_reader :user, :scope
|
|
58
|
+
|
|
59
|
+
def initialize(user, scope)
|
|
60
|
+
@user = user
|
|
61
|
+
@scope = scope
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Platform admins see every tenant's data — no account_id
|
|
65
|
+
# filter. The returned scope is the same `scope` Pundit handed
|
|
66
|
+
# in (typically `Model.all` or an Administrate-prepared scope),
|
|
67
|
+
# untouched.
|
|
68
|
+
def resolve
|
|
69
|
+
scope.all
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Reads `staff?` from the wrapped Identity. The pundit_user is a
|
|
76
|
+
# `Seams::Admin::Context` Struct in Phase 3 — `user.identity` is
|
|
77
|
+
# the actual Auth::Identity. Nil-safe so an unauthenticated
|
|
78
|
+
# request (which the gate already rejected) returns false instead
|
|
79
|
+
# of NoMethodError.
|
|
80
|
+
def staff?
|
|
81
|
+
user&.identity&.staff?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Platform
|
|
5
|
+
# Platform-mode policy for Auth::Identity.
|
|
6
|
+
#
|
|
7
|
+
# Defaults from Admin::Platform::ApplicationPolicy: every predicate
|
|
8
|
+
# gates on `staff?`. Scope returns `scope.all`. Platform admins see
|
|
9
|
+
# every Identity in the system — there is no per-tenant filter
|
|
10
|
+
# because Identity is global.
|
|
11
|
+
#
|
|
12
|
+
# Hosts that need a finer gate (e.g. "only senior-staff Identities
|
|
13
|
+
# can destroy other Identities") eject this file and override
|
|
14
|
+
# `destroy?`.
|
|
15
|
+
class IdentityPolicy < ApplicationPolicy
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Platform
|
|
5
|
+
# Platform-mode policy for Billing::Plan. Plans are global product
|
|
6
|
+
# rows (not account-scoped), so even tenant mode generally treats
|
|
7
|
+
# them as read-only globals.
|
|
8
|
+
class PlanPolicy < ApplicationPolicy
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Tenant-mode policy for Accounts::Account. The single Account row
|
|
6
|
+
# the admin can see is their own. Filter on `id`, not `account_id`,
|
|
7
|
+
# because Account is the tenant root.
|
|
8
|
+
#
|
|
9
|
+
# `record_in_tenant_scope?` is overridden here because the parent
|
|
10
|
+
# default reads `record.account_id`, which doesn't exist on
|
|
11
|
+
# Account. The matching column on Account is `id`.
|
|
12
|
+
class AccountPolicy < ApplicationPolicy
|
|
13
|
+
class Scope < ApplicationPolicy::Scope
|
|
14
|
+
def resolve
|
|
15
|
+
return scope.all if staff?
|
|
16
|
+
return scope.none if account_id.nil?
|
|
17
|
+
|
|
18
|
+
scope.where(id: account_id)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def record_in_tenant_scope?
|
|
25
|
+
return true if user&.identity&.staff?
|
|
26
|
+
return true if record.nil? || record.is_a?(Class)
|
|
27
|
+
return false if account_id.nil?
|
|
28
|
+
|
|
29
|
+
record.id == account_id
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Tenant-mode policy for Accounts::Membership. Filter by
|
|
6
|
+
# `account_id` so admins only see Memberships in their own Account.
|
|
7
|
+
#
|
|
8
|
+
# `accounts_memberships` is one of the canonical seams tables that
|
|
9
|
+
# carries `account_id` directly. The column-presence guard is
|
|
10
|
+
# defensive: a host whose schema differs gets `scope.none` rather
|
|
11
|
+
# than `PG::UndefinedColumn`.
|
|
12
|
+
class AccountsMembershipPolicy < ApplicationPolicy
|
|
13
|
+
class Scope < ApplicationPolicy::Scope
|
|
14
|
+
def resolve
|
|
15
|
+
return scope.all if staff?
|
|
16
|
+
return scope.none if account_id.nil?
|
|
17
|
+
return scope.none unless account_id_column_present?
|
|
18
|
+
|
|
19
|
+
scope.where(account_id: account_id)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Abstract base for every tenant-mode admin policy. Tenant mode
|
|
6
|
+
# gates on `Accounts::Membership.role` for the currently-resolved
|
|
7
|
+
# membership (`Seams::Admin::Context#membership`). Staff Identities
|
|
8
|
+
# always pass the gate too — a platform staff member can drop into
|
|
9
|
+
# any tenant's admin without rejoining as an account-admin.
|
|
10
|
+
#
|
|
11
|
+
# The `Scope` inner class is overridden per-policy; this base
|
|
12
|
+
# returns `scope.none` so a subclass that forgets to override gets
|
|
13
|
+
# an empty list rather than leaking another tenant's data. Fail
|
|
14
|
+
# closed.
|
|
15
|
+
#
|
|
16
|
+
# `user` is `Seams::Admin::Context.new(identity, membership)`.
|
|
17
|
+
# `user.identity` — Auth::Identity (carries `staff?`)
|
|
18
|
+
# `user.membership` — Accounts::Membership for the current account
|
|
19
|
+
# (carries `account_id` and `role`)
|
|
20
|
+
#
|
|
21
|
+
# ## Two-stage gate (`permitted?` + Scope)
|
|
22
|
+
#
|
|
23
|
+
# `permitted?` here returns true when the user holds an
|
|
24
|
+
# admin/owner membership in ANY account; the per-resource `Scope`
|
|
25
|
+
# then filters the visible record set to that user's `account_id`.
|
|
26
|
+
# The split is intentional:
|
|
27
|
+
# - `permitted?` answers "is this human allowed in the admin
|
|
28
|
+
# surface at all?" (gate semantics).
|
|
29
|
+
# - `Scope` answers "which rows can this human see?" (row-level
|
|
30
|
+
# filter).
|
|
31
|
+
#
|
|
32
|
+
# The user-visible consequence: a tenant admin requesting
|
|
33
|
+
# `/admin/accounts/<other_account_id>` passes `permitted?` but the
|
|
34
|
+
# Scope filters out that row, so the controller raises
|
|
35
|
+
# `ActiveRecord::RecordNotFound` (rendered as a 404). The trade-off
|
|
36
|
+
# is that the admin surface doesn't leak the existence of another
|
|
37
|
+
# tenant's data via a different status code — both "row doesn't
|
|
38
|
+
# exist" and "row belongs to another tenant" produce a 404.
|
|
39
|
+
class ApplicationPolicy
|
|
40
|
+
attr_reader :user, :record
|
|
41
|
+
|
|
42
|
+
def initialize(user, record)
|
|
43
|
+
@user = user
|
|
44
|
+
@record = record
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def index?
|
|
48
|
+
permitted?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def show?
|
|
52
|
+
permitted? && record_in_tenant_scope?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def new?
|
|
56
|
+
permitted?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create?
|
|
60
|
+
permitted? && record_in_tenant_scope?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def edit?
|
|
64
|
+
permitted? && record_in_tenant_scope?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def update?
|
|
68
|
+
permitted? && record_in_tenant_scope?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def destroy?
|
|
72
|
+
permitted? && record_in_tenant_scope?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class Scope
|
|
76
|
+
attr_reader :user, :scope
|
|
77
|
+
|
|
78
|
+
def initialize(user, scope)
|
|
79
|
+
@user = user
|
|
80
|
+
@scope = scope
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Default fail-closed scope. Every concrete tenant policy
|
|
84
|
+
# overrides this to filter by `account_id`. Returning `none`
|
|
85
|
+
# here means a forgotten override never leaks another tenant's
|
|
86
|
+
# data — the worst case is "admin sees an empty list" rather
|
|
87
|
+
# than "admin sees everyone's data".
|
|
88
|
+
def resolve
|
|
89
|
+
scope.none
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def account_id
|
|
95
|
+
user&.membership&.account_id
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def staff?
|
|
99
|
+
user&.identity&.staff?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Helper for subclasses: hosts may have ejected the
|
|
103
|
+
# `account_id` column off a model (or never had one — the
|
|
104
|
+
# canonical seams schema doesn't put `account_id` on
|
|
105
|
+
# `teams`, `team_memberships`, `team_invitations`,
|
|
106
|
+
# `notifications`, or `notification_preferences`).
|
|
107
|
+
# Subclasses that filter by `account_id` should consult this
|
|
108
|
+
# before issuing the where-clause; if the column is absent the
|
|
109
|
+
# safe fall-back is `scope.none` (refusing to leak rows we
|
|
110
|
+
# can't tenant-scope).
|
|
111
|
+
def account_id_column_present?
|
|
112
|
+
model = scope.respond_to?(:klass) ? scope.klass : scope
|
|
113
|
+
return false unless model.respond_to?(:column_names)
|
|
114
|
+
|
|
115
|
+
model.column_names.include?("account_id")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# Permitted when (a) the Identity is staff (platform admins drop
|
|
122
|
+
# into every tenant), or (b) the resolved Accounts::Membership
|
|
123
|
+
# carries an admin/owner role for the current Account.
|
|
124
|
+
def permitted?
|
|
125
|
+
return true if user&.identity&.staff?
|
|
126
|
+
|
|
127
|
+
membership = user&.membership
|
|
128
|
+
return false if membership.nil?
|
|
129
|
+
|
|
130
|
+
%w[owner admin].include?(membership.role.to_s)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def account_id
|
|
134
|
+
user&.membership&.account_id
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Per-record tenant guard. The `Scope` filters list views, but a
|
|
138
|
+
# show/update/destroy lookup of a record from another tenant
|
|
139
|
+
# (constructed by id-tampering) bypasses the Scope unless we
|
|
140
|
+
# check the record itself. Subclasses that filter by
|
|
141
|
+
# `record.account_id` get this default; subclasses with custom
|
|
142
|
+
# tenant boundaries (Account itself, Plan, Identity) override
|
|
143
|
+
# this method.
|
|
144
|
+
#
|
|
145
|
+
# Staff Identities always satisfy the per-record check — they
|
|
146
|
+
# admin every tenant.
|
|
147
|
+
def record_in_tenant_scope?
|
|
148
|
+
return true if user&.identity&.staff?
|
|
149
|
+
return true if record.nil? || record.is_a?(Class)
|
|
150
|
+
return false if account_id.nil?
|
|
151
|
+
|
|
152
|
+
record_account_id = if record.respond_to?(:account_id)
|
|
153
|
+
record.account_id
|
|
154
|
+
else
|
|
155
|
+
# Record class doesn't carry an
|
|
156
|
+
# account_id column. Fall through to
|
|
157
|
+
# `true` so policies that override
|
|
158
|
+
# `record_in_tenant_scope?` (Account,
|
|
159
|
+
# Identity, Plan) handle this — and so
|
|
160
|
+
# the default doesn't fail-closed for
|
|
161
|
+
# global resources.
|
|
162
|
+
return true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
record_account_id == account_id
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Tenant-mode policy for Auth::Identity.
|
|
6
|
+
#
|
|
7
|
+
# Identity is the only canonical model that is NOT directly
|
|
8
|
+
# account-scoped — it has no `account_id` column, because an
|
|
9
|
+
# Identity can belong to multiple Accounts via Accounts::Membership.
|
|
10
|
+
# In tenant mode the admin should only see the Identities that hold
|
|
11
|
+
# a Membership in the current Account; everyone else is invisible.
|
|
12
|
+
#
|
|
13
|
+
# ## Why a subquery, not an association join
|
|
14
|
+
#
|
|
15
|
+
# The canonical `Auth::Identity` model in seams does NOT declare
|
|
16
|
+
# `has_many :memberships` (the auth engine deliberately keeps
|
|
17
|
+
# Identity credential-only and avoids reaching across the engine
|
|
18
|
+
# boundary into accounts). A policy that joined an association
|
|
19
|
+
# named `memberships` would raise
|
|
20
|
+
# `ActiveRecord::ConfigurationError` on a vanilla seams host.
|
|
21
|
+
#
|
|
22
|
+
# The subquery shape below depends on the `accounts_memberships`
|
|
23
|
+
# table existing and carrying `identity_id` + `account_id` columns
|
|
24
|
+
# — both canonical in the accounts engine since Wave 9 — without
|
|
25
|
+
# depending on any association being declared on `Auth::Identity`.
|
|
26
|
+
#
|
|
27
|
+
# Hosts whose Identity DOES declare an associations join to
|
|
28
|
+
# memberships may eject this policy and prefer an association
|
|
29
|
+
# join for index-friendly SQL; the subquery form is the safe
|
|
30
|
+
# default that works against a fresh seams install.
|
|
31
|
+
class IdentityPolicy < ApplicationPolicy
|
|
32
|
+
class Scope < ApplicationPolicy::Scope
|
|
33
|
+
def resolve
|
|
34
|
+
return scope.all if staff?
|
|
35
|
+
return scope.none if account_id.nil?
|
|
36
|
+
|
|
37
|
+
# Subquery on the accounts_memberships table — works
|
|
38
|
+
# without a `has_many :memberships` declaration on the
|
|
39
|
+
# Identity model. Distinct guards against an Identity
|
|
40
|
+
# appearing twice if the host model schema ever permitted
|
|
41
|
+
# duplicate (account_id, identity_id) pairs.
|
|
42
|
+
membership_subquery = ::Accounts::Membership
|
|
43
|
+
.where(account_id: account_id)
|
|
44
|
+
.select(:identity_id)
|
|
45
|
+
scope.where(id: membership_subquery).distinct
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Identity has no `account_id` column. Per-record tenant guard
|
|
52
|
+
# checks instead that the Identity holds a membership in the
|
|
53
|
+
# current Account.
|
|
54
|
+
def record_in_tenant_scope?
|
|
55
|
+
return true if user&.identity&.staff?
|
|
56
|
+
return true if record.nil? || record.is_a?(Class)
|
|
57
|
+
return false if account_id.nil?
|
|
58
|
+
return false unless record.respond_to?(:id) && record.id
|
|
59
|
+
|
|
60
|
+
::Accounts::Membership.exists?(
|
|
61
|
+
account_id: account_id,
|
|
62
|
+
identity_id: record.id
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Tenant-mode policy for Teams::Invitation. Filters by `account_id`.
|
|
6
|
+
#
|
|
7
|
+
# The canonical seams `team_invitations` table doesn't carry
|
|
8
|
+
# `account_id` directly (invitations are scoped through their
|
|
9
|
+
# Team). The default falls back to `scope.none` if the column is
|
|
10
|
+
# absent — fail-closed. Hosts whose schema differs eject this
|
|
11
|
+
# policy and replace `resolve` with a join through Teams::Team.
|
|
12
|
+
class InvitationPolicy < ApplicationPolicy
|
|
13
|
+
class Scope < ApplicationPolicy::Scope
|
|
14
|
+
def resolve
|
|
15
|
+
return scope.all if staff?
|
|
16
|
+
return scope.none if account_id.nil?
|
|
17
|
+
return scope.none unless account_id_column_present?
|
|
18
|
+
|
|
19
|
+
scope.where(account_id: account_id)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Tenant-mode policy for Billing::Invoice. Filters by `account_id`
|
|
6
|
+
# — `billing_invoices` carries the column directly in the canonical
|
|
7
|
+
# seams schema. The column-presence guard is defensive for hosts
|
|
8
|
+
# whose schema differs.
|
|
9
|
+
class InvoicePolicy < ApplicationPolicy
|
|
10
|
+
class Scope < ApplicationPolicy::Scope
|
|
11
|
+
def resolve
|
|
12
|
+
return scope.all if staff?
|
|
13
|
+
return scope.none if account_id.nil?
|
|
14
|
+
return scope.none unless account_id_column_present?
|
|
15
|
+
|
|
16
|
+
scope.where(account_id: account_id)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
module Tenant
|
|
5
|
+
# Tenant-mode policy for Billing::LifetimePass. Filters by
|
|
6
|
+
# `account_id` — `billing_lifetime_passes` carries the column
|
|
7
|
+
# directly in the canonical seams schema. The column-presence
|
|
8
|
+
# guard is defensive for hosts whose schema differs.
|
|
9
|
+
class LifetimePassPolicy < ApplicationPolicy
|
|
10
|
+
class Scope < ApplicationPolicy::Scope
|
|
11
|
+
def resolve
|
|
12
|
+
return scope.all if staff?
|
|
13
|
+
return scope.none if account_id.nil?
|
|
14
|
+
return scope.none unless account_id_column_present?
|
|
15
|
+
|
|
16
|
+
scope.where(account_id: account_id)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|