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,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "rails/generators"
|
|
5
|
+
require "seams"
|
|
6
|
+
require "generators/seams/engine/engine_generator"
|
|
7
|
+
require "seams/generators/host_injector"
|
|
8
|
+
require "seams/generators/eject_aware"
|
|
9
|
+
require "seams/generators/dummy_app_writer"
|
|
10
|
+
|
|
11
|
+
module Seams
|
|
12
|
+
module Generators
|
|
13
|
+
# Generates a canonical Accounts engine on top of the generic engine
|
|
14
|
+
# scaffold. Adds:
|
|
15
|
+
#
|
|
16
|
+
# - Accounts::Account model. Tenant boundary. UUID PK.
|
|
17
|
+
# - Accounts::Membership model. Joins Auth::Identity to Account
|
|
18
|
+
# with a role enum (owner/admin/member/system); identity_id is
|
|
19
|
+
# nullable for system actors used by audit-log writes.
|
|
20
|
+
# - Accounts::Current per-request namespace.
|
|
21
|
+
# - Accounts::AccountScoped model concern (default_scope to
|
|
22
|
+
# Current.account, opt-out via .unscoped).
|
|
23
|
+
# - Accounts::Authorization controller concern (default-on
|
|
24
|
+
# ensure_account_access; opt out via disallow_account_scope or
|
|
25
|
+
# require_access_without_membership; helpers ensure_admin /
|
|
26
|
+
# ensure_staff).
|
|
27
|
+
# - Migrations for accounts + accounts_memberships (pgcrypto).
|
|
28
|
+
# - lib/accounts/engine.rb registers the canonical events:
|
|
29
|
+
# account.created.accounts, account.cancelled.accounts,
|
|
30
|
+
# membership.created.accounts, membership.role_changed.accounts,
|
|
31
|
+
# membership.removed.accounts.
|
|
32
|
+
#
|
|
33
|
+
# The engine ships NO controllers in Wave 9 — hosts drive their
|
|
34
|
+
# own account-creation flows; this engine is the model + concern
|
|
35
|
+
# layer.
|
|
36
|
+
#
|
|
37
|
+
# Run with: bin/rails generate seams:accounts
|
|
38
|
+
class AccountsGenerator < Rails::Generators::Base
|
|
39
|
+
include Seams::Generators::HostInjector
|
|
40
|
+
include Seams::Generators::EjectAware
|
|
41
|
+
|
|
42
|
+
source_root File.expand_path("templates", __dir__)
|
|
43
|
+
|
|
44
|
+
ENGINE_NAME = "accounts"
|
|
45
|
+
|
|
46
|
+
def create_engine_skeleton
|
|
47
|
+
EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def overwrite_engine_entry_point
|
|
51
|
+
# engine.rb / lib/accounts.rb stay framework-managed.
|
|
52
|
+
template "lib/engine.rb.tt", engine_path("lib/accounts/engine.rb"), force: true
|
|
53
|
+
template "lib/accounts.rb.tt", engine_path("lib/accounts.rb"), force: true
|
|
54
|
+
template_unless_ejected "lib/configuration.rb.tt",
|
|
55
|
+
engine_path("lib/accounts/configuration.rb")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def overwrite_routes
|
|
59
|
+
template_unless_ejected "config/routes.rb.tt", engine_path("config/routes.rb"), force: true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def create_models
|
|
63
|
+
template_unless_ejected "app/models/application_record.rb.tt",
|
|
64
|
+
engine_path("app/models/accounts/application_record.rb")
|
|
65
|
+
template_unless_ejected "app/models/account.rb.tt",
|
|
66
|
+
engine_path("app/models/accounts/account.rb")
|
|
67
|
+
template_unless_ejected "app/models/membership.rb.tt",
|
|
68
|
+
engine_path("app/models/accounts/membership.rb")
|
|
69
|
+
template_unless_ejected "app/models/current.rb.tt",
|
|
70
|
+
engine_path("app/models/accounts/current.rb")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create_concerns
|
|
74
|
+
template_unless_ejected "lib/concerns/account_scoped.rb.tt",
|
|
75
|
+
engine_path("lib/accounts/concerns/account_scoped.rb")
|
|
76
|
+
template_unless_ejected "lib/concerns/authorization.rb.tt",
|
|
77
|
+
engine_path("lib/accounts/concerns/authorization.rb")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def create_migrations
|
|
81
|
+
template "db/migrate/create_accounts.rb.tt",
|
|
82
|
+
engine_path("db/migrate/#{timestamp(0)}_create_accounts.rb")
|
|
83
|
+
template "db/migrate/create_accounts_memberships.rb.tt",
|
|
84
|
+
engine_path("db/migrate/#{timestamp(1)}_create_accounts_memberships.rb")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_factories
|
|
88
|
+
template_unless_ejected "spec/factories/accounts.rb.tt",
|
|
89
|
+
engine_path("spec/factories/accounts.rb")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def create_unit_specs
|
|
93
|
+
template_unless_ejected "spec/models/accounts/account_spec.rb.tt",
|
|
94
|
+
engine_path("spec/models/accounts/account_spec.rb")
|
|
95
|
+
template_unless_ejected "spec/models/accounts/membership_spec.rb.tt",
|
|
96
|
+
engine_path("spec/models/accounts/membership_spec.rb")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def overwrite_readme
|
|
100
|
+
template "README.md.tt", engine_path("README.md"), force: true
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def update_exposed_concerns
|
|
104
|
+
rubocop_path = engine_path(".rubocop.yml")
|
|
105
|
+
return unless File.exist?(rubocop_path)
|
|
106
|
+
|
|
107
|
+
contents = File.read(rubocop_path)
|
|
108
|
+
replacement = " ExposedConcerns:\n - Accounts::AccountScoped\n - Accounts::Authorization"
|
|
109
|
+
contents.sub!(/^ ExposedConcerns: \[\]$/, replacement)
|
|
110
|
+
File.write(rubocop_path, contents)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def create_dummy_app
|
|
114
|
+
# Post Wave 9: the dummy app does NOT ship a host User model.
|
|
115
|
+
# Auth::Identity is the canonical human; accounts membership
|
|
116
|
+
# is the per-tenant role row. The accounts engine specs DO
|
|
117
|
+
# exercise Auth::Identity directly (a Membership without an
|
|
118
|
+
# Identity to point at is meaningless), so we ship a slim
|
|
119
|
+
# stub at app/models/auth/identity.rb the same way the
|
|
120
|
+
# notifications engine does.
|
|
121
|
+
Seams::Generators::DummyAppWriter.write!(
|
|
122
|
+
engine_path: File.join(destination_root, "engines", ENGINE_NAME),
|
|
123
|
+
engine_module: "Accounts",
|
|
124
|
+
mount_at: "/accounts",
|
|
125
|
+
schema: dummy_schema,
|
|
126
|
+
host_user: dummy_host_identity,
|
|
127
|
+
host_user_path: "app/models/auth/identity.rb"
|
|
128
|
+
)
|
|
129
|
+
write_auth_current_stub
|
|
130
|
+
template "spec/runtime/accounts_boot_spec.rb.tt",
|
|
131
|
+
engine_path("spec/runtime/accounts_boot_spec.rb")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Write a tiny `Auth::Current` stub so the accounts engine specs
|
|
135
|
+
# (which read Current.identity) can run without pulling in the
|
|
136
|
+
# full auth engine.
|
|
137
|
+
def write_auth_current_stub
|
|
138
|
+
path = File.join(destination_root, "engines", ENGINE_NAME,
|
|
139
|
+
"spec/dummy/app/models/auth/current.rb")
|
|
140
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
141
|
+
File.write(path, <<~RB)
|
|
142
|
+
# frozen_string_literal: true
|
|
143
|
+
# Slim Auth::Current stub for the accounts dummy app. Stands in
|
|
144
|
+
# for the real Auth::Current (which lives in the auth engine,
|
|
145
|
+
# not loaded by the dummy) so accounts specs can wire
|
|
146
|
+
# `Current.identity = identity` against the same surface area
|
|
147
|
+
# the canonical seams host uses.
|
|
148
|
+
module Auth
|
|
149
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
150
|
+
attribute :identity
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
RB
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def create_runtime_specs
|
|
157
|
+
# Currently a single runtime boot spec covers events, schema,
|
|
158
|
+
# create_with_owner, and Accounts::Current. Split into multiple
|
|
159
|
+
# files in a later phase if the file grows past ~120 lines.
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def wire_into_host
|
|
163
|
+
# factory_bot_rails powers spec/factories/accounts.rb. Lives
|
|
164
|
+
# in the host's test group only.
|
|
165
|
+
host_inject_gem("factory_bot_rails", "~> 6.4", group: :test)
|
|
166
|
+
host_inject_mount(engine_class: "Accounts::Engine", at: "/accounts")
|
|
167
|
+
# NB: no host_inject_include_in_user — the host User is going
|
|
168
|
+
# away in Wave 9. Hosts that DO keep a User model wire it up
|
|
169
|
+
# themselves.
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def report_summary
|
|
173
|
+
say ""
|
|
174
|
+
say " Accounts engine generated at engines/accounts/", :green
|
|
175
|
+
say ""
|
|
176
|
+
say " Next steps:", :yellow
|
|
177
|
+
say " 1. bin/rails db:migrate"
|
|
178
|
+
say " 2. Include Accounts::Authorization in your ApplicationController"
|
|
179
|
+
say " 3. Wire Accounts::Current.account in a before_action"
|
|
180
|
+
say " 4. Run the engine specs: bin/rails seams:test[accounts]"
|
|
181
|
+
say ""
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def engine_path(relative)
|
|
187
|
+
File.join(destination_root, "engines", ENGINE_NAME, relative)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def timestamp(offset)
|
|
191
|
+
# Microsecond-resolution timestamp so migrations generated
|
|
192
|
+
# back-to-back don't collide. Offset by +50 so accounts'
|
|
193
|
+
# `accounts` + `accounts_memberships` tables migrate AFTER
|
|
194
|
+
# auth's `auth_identities` (+0..+3, since memberships address
|
|
195
|
+
# an Identity at the application layer) but BEFORE engines
|
|
196
|
+
# whose schemas depend on `accounts.id` semantically:
|
|
197
|
+
# notifications +100, billing +200, teams +300. Without this,
|
|
198
|
+
# billing's `subscriptions.account_id` would migrate before
|
|
199
|
+
# the `accounts` table existed — no DB-level FK so it's
|
|
200
|
+
# silent, but ordering matters if a host ever tightens to a
|
|
201
|
+
# real foreign-key constraint.
|
|
202
|
+
base = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
|
203
|
+
(base + 50 + offset).to_s
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Slim Auth::Identity stub for the dummy app. Stands in for the
|
|
207
|
+
# real Auth::Identity (which lives in the auth engine, not loaded
|
|
208
|
+
# by the dummy) so accounts specs can build an Identity for the
|
|
209
|
+
# owner-membership join. Includes has_secure_password so spec
|
|
210
|
+
# fixtures can pass `password:` like the real Identity accepts.
|
|
211
|
+
def dummy_host_identity
|
|
212
|
+
<<~RB
|
|
213
|
+
# frozen_string_literal: true
|
|
214
|
+
module Auth
|
|
215
|
+
class Identity < ApplicationRecord
|
|
216
|
+
self.table_name = "auth_identities"
|
|
217
|
+
has_secure_password
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
RB
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def dummy_schema
|
|
224
|
+
# Includes auth_identities so factories that link memberships
|
|
225
|
+
# to an Identity can `create(:auth_identity)` against a real
|
|
226
|
+
# row. Match the auth engine's schema for that table so
|
|
227
|
+
# cross-engine specs don't drift.
|
|
228
|
+
<<~SCHEMA
|
|
229
|
+
enable_extension "pgcrypto"
|
|
230
|
+
|
|
231
|
+
create_table :auth_identities do |t|
|
|
232
|
+
t.text :email, null: false
|
|
233
|
+
t.string :password_digest, null: false
|
|
234
|
+
t.boolean :staff, null: false, default: false
|
|
235
|
+
t.timestamps
|
|
236
|
+
end
|
|
237
|
+
add_index :auth_identities, :email, unique: true
|
|
238
|
+
add_index :auth_identities, :staff, where: "staff = true"
|
|
239
|
+
|
|
240
|
+
create_table :accounts, id: :uuid do |t|
|
|
241
|
+
t.string :name, null: false
|
|
242
|
+
t.bigint :external_account_id, null: false
|
|
243
|
+
t.datetime :cancelled_at
|
|
244
|
+
t.datetime :incinerated_at
|
|
245
|
+
t.timestamps
|
|
246
|
+
end
|
|
247
|
+
add_index :accounts, :external_account_id, unique: true
|
|
248
|
+
add_index :accounts, :cancelled_at
|
|
249
|
+
|
|
250
|
+
create_table :accounts_memberships, id: :uuid do |t|
|
|
251
|
+
t.references :account, type: :uuid, null: false,
|
|
252
|
+
foreign_key: { to_table: :accounts }, index: false
|
|
253
|
+
t.bigint :identity_id, null: true
|
|
254
|
+
t.string :name, null: false
|
|
255
|
+
t.string :role, null: false, default: "member"
|
|
256
|
+
t.boolean :active, null: false, default: true
|
|
257
|
+
t.datetime :verified_at
|
|
258
|
+
t.timestamps
|
|
259
|
+
end
|
|
260
|
+
add_index :accounts_memberships, %i[account_id identity_id], unique: true,
|
|
261
|
+
name: "index_accounts_memberships_unique"
|
|
262
|
+
add_index :accounts_memberships, %i[account_id role]
|
|
263
|
+
add_index :accounts_memberships, :identity_id
|
|
264
|
+
# Wave 9 invariant: exactly one system actor per Account.
|
|
265
|
+
add_index :accounts_memberships, :account_id, unique: true,
|
|
266
|
+
where: "role = 'system'",
|
|
267
|
+
name: "index_accounts_memberships_one_system_per_account"
|
|
268
|
+
SCHEMA
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Accounts
|
|
2
|
+
|
|
3
|
+
> Tenant boundary for a Seams-powered host application. Owns the
|
|
4
|
+
> `Accounts::Account` (the workspace), `Accounts::Membership`
|
|
5
|
+
> (Identity ↔ Account, with role), per-request `Accounts::Current`,
|
|
6
|
+
> and the two concerns hosts need to scope their own data and
|
|
7
|
+
> controllers to a tenant. Identity stays in the auth engine — this
|
|
8
|
+
> engine never owns credentials.
|
|
9
|
+
|
|
10
|
+
**Requires:** the `auth` engine. `Accounts::Membership.identity_id`
|
|
11
|
+
joins to `auth_identities`; `Accounts::Current.account=` reads
|
|
12
|
+
`Auth::Current.identity` to derive the matching Membership. Install
|
|
13
|
+
auth before accounts.
|
|
14
|
+
|
|
15
|
+
## Identity, Account, and Membership
|
|
16
|
+
|
|
17
|
+
Three peer concepts, three tables, one clear responsibility each:
|
|
18
|
+
|
|
19
|
+
| Model | Owns | Lives in |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `Auth::Identity` | The human. Credentials, sessions, OAuth, API tokens. | `auth_identities` (auth engine) |
|
|
22
|
+
| `Accounts::Account`| The workspace / tenant. Name + soft-cancel state. | `accounts` (this engine) |
|
|
23
|
+
| `Accounts::Membership` | The join. Says "Identity X is a Y in Account Z". | `accounts_memberships` (this engine) |
|
|
24
|
+
|
|
25
|
+
The same `Auth::Identity` can have memberships in multiple Accounts
|
|
26
|
+
with different roles. A Membership belongs to exactly one Account.
|
|
27
|
+
|
|
28
|
+
## Why is `identity_id` nullable on Membership?
|
|
29
|
+
|
|
30
|
+
System actors. Every Account ships with a `role: "system"` row whose
|
|
31
|
+
`identity_id` is NULL. That row is the audit-log writer for changes
|
|
32
|
+
that don't have a human behind them — background jobs syncing data
|
|
33
|
+
from a webhook, scheduled tasks expiring trials, billing-engine
|
|
34
|
+
hooks reconciling a subscription, etc.
|
|
35
|
+
|
|
36
|
+
Without a system actor, every audit entry would either need a
|
|
37
|
+
nullable `actor_id` (which is what the system row neatly avoids) or
|
|
38
|
+
a fake "robot" Identity that lives nowhere else and confuses
|
|
39
|
+
everything from email lookup to billing customer mapping.
|
|
40
|
+
|
|
41
|
+
## Roles
|
|
42
|
+
|
|
43
|
+
| Role | identity_id | Powers |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `owner` | required | Full admin. Can manage members, billing, cancel the account. |
|
|
46
|
+
| `admin` | required | Manages members and most settings; cannot remove an owner. |
|
|
47
|
+
| `member` | required | Default role. Read + write within the account's data scope. |
|
|
48
|
+
| `system` | NULL | Audit-log actor only. Never logs in. One per account. |
|
|
49
|
+
|
|
50
|
+
Every Account has exactly one system row (created automatically by
|
|
51
|
+
`Account.create_with_owner`); humans get `owner` / `admin` /
|
|
52
|
+
`member` rows.
|
|
53
|
+
|
|
54
|
+
## `staff` vs `admin` — platform admin vs in-account admin
|
|
55
|
+
|
|
56
|
+
These are different powers:
|
|
57
|
+
|
|
58
|
+
- `Auth::Identity#staff?` — a boolean on the Identity row. Platform-level
|
|
59
|
+
super-user. Bypasses account scoping for support tooling
|
|
60
|
+
(impersonation, cross-account search). Set by an out-of-band admin
|
|
61
|
+
process; never via sign-up params.
|
|
62
|
+
- `Accounts::Membership#admin?` — true when role is `owner` OR `admin`.
|
|
63
|
+
In-account admin. Manages the workspace's members and settings
|
|
64
|
+
but has no power outside this account.
|
|
65
|
+
|
|
66
|
+
Use `ensure_staff` in controllers that span accounts.
|
|
67
|
+
Use `ensure_admin` in controllers that manage one account.
|
|
68
|
+
|
|
69
|
+
## Events emitted
|
|
70
|
+
|
|
71
|
+
| Event name | Payload |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `account.created.accounts` | `{ account_id:, owner_identity_id: }` |
|
|
74
|
+
| `account.cancelled.accounts` | `{ account_id:, cancelled_by_identity_id: }` |
|
|
75
|
+
| `membership.created.accounts` | `{ account_id:, membership_id:, identity_id:, role: }` |
|
|
76
|
+
| `membership.role_changed.accounts` | `{ account_id:, membership_id:, from_role:, to_role:, changed_by_identity_id: }` |
|
|
77
|
+
| `membership.removed.accounts` | `{ account_id:, membership_id:, identity_id:, removed_by_identity_id: }` |
|
|
78
|
+
|
|
79
|
+
`identity_id` is the row id in the auth engine's `auth_identities`
|
|
80
|
+
table. Subscribers (notifications, billing, etc.) resolve the human
|
|
81
|
+
via Identity, never via a host User.
|
|
82
|
+
|
|
83
|
+
## Events consumed
|
|
84
|
+
|
|
85
|
+
This engine does not subscribe to any other engine's events.
|
|
86
|
+
Downstream engines (notifications, billing, teams) publish and
|
|
87
|
+
subscribe via `account_id` / `identity_id` carried on their event
|
|
88
|
+
payloads.
|
|
89
|
+
|
|
90
|
+
## Exposed concerns
|
|
91
|
+
|
|
92
|
+
### `Accounts::AccountScoped` — model concern
|
|
93
|
+
|
|
94
|
+
Mix into any model whose rows belong to a single Account:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class AuditEntry < ApplicationRecord
|
|
98
|
+
include Accounts::AccountScoped
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
Accounts::Current.account = account
|
|
102
|
+
AuditEntry.create!(action: "...") # account_id auto-assigned
|
|
103
|
+
AuditEntry.all # only this account's rows (default_scope)
|
|
104
|
+
AuditEntry.unscoped.all # opt out for cross-tenant queries
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The default_scope is a no-op when `Accounts::Current.account` is
|
|
108
|
+
unset, so background jobs that haven't bound a Current account see
|
|
109
|
+
every row — wire `Accounts::Current.account =` into your job's
|
|
110
|
+
`#perform` if you need scoping there.
|
|
111
|
+
|
|
112
|
+
### `Accounts::Authorization` — controller concern
|
|
113
|
+
|
|
114
|
+
Mix into the host's `ApplicationController`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
class ApplicationController < ActionController::Base
|
|
118
|
+
include Accounts::Authorization
|
|
119
|
+
# default-on `before_action :ensure_account_access`
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then opt out per-controller:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
class PublicPagesController < ApplicationController
|
|
127
|
+
disallow_account_scope # public marketing
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class OnboardingController < ApplicationController
|
|
131
|
+
require_access_without_membership # signed in, no membership yet
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Plus two opt-in helpers any controller can call from its own `before_action`:
|
|
136
|
+
|
|
137
|
+
- `ensure_admin` — checks `Accounts::Current.membership&.admin?`
|
|
138
|
+
(in-account owner OR admin)
|
|
139
|
+
- `ensure_staff` — checks `Auth::Current.identity&.staff?`
|
|
140
|
+
(platform admin)
|
|
141
|
+
|
|
142
|
+
## Per-request state — `Accounts::Current`
|
|
143
|
+
|
|
144
|
+
The host wires `Accounts::Current.account` in a controller
|
|
145
|
+
before_action. Setting `account=` automatically derives the
|
|
146
|
+
matching `Membership` for the currently signed-in identity (read
|
|
147
|
+
from `Auth::Current.identity`):
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class ApplicationController < ActionController::Base
|
|
151
|
+
before_action :resolve_current_account
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def resolve_current_account
|
|
156
|
+
Accounts::Current.account = current_account_from_url_or_session
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Anywhere downstream:
|
|
161
|
+
Accounts::Current.account # => Accounts::Account
|
|
162
|
+
Accounts::Current.membership # => Accounts::Membership for the signed-in human
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Account creation
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
identity = Auth::Identity.create!(email: "ada@example.com", password: "...")
|
|
169
|
+
owner = Struct.new(:identity, :name).new(identity, "Ada Lovelace")
|
|
170
|
+
|
|
171
|
+
account = Accounts::Account.create_with_owner(
|
|
172
|
+
account: { name: "Acme Corp" },
|
|
173
|
+
owner: owner
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
account.memberships.pluck(:role) # => ["system", "owner"]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The system membership is mandatory — it's the audit-log actor for
|
|
180
|
+
non-human changes. The owner membership is the human creating the
|
|
181
|
+
account. Wrapped in a transaction; if any of the three inserts
|
|
182
|
+
fail, all roll back.
|
|
183
|
+
|
|
184
|
+
## Migration / setup steps
|
|
185
|
+
|
|
186
|
+
1. Run the generator:
|
|
187
|
+
```bash
|
|
188
|
+
bin/rails generate seams:accounts
|
|
189
|
+
```
|
|
190
|
+
2. Bundle install (no new gems, but locks the engine into the host).
|
|
191
|
+
3. Migrate:
|
|
192
|
+
```bash
|
|
193
|
+
bin/rails db:migrate
|
|
194
|
+
```
|
|
195
|
+
4. Mount in `config/routes.rb` (the generator does this automatically):
|
|
196
|
+
```ruby
|
|
197
|
+
mount Accounts::Engine, at: "/accounts"
|
|
198
|
+
```
|
|
199
|
+
5. Wire `Accounts::Authorization` into your `ApplicationController`.
|
|
200
|
+
|
|
201
|
+
## Mounting
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
# config/routes.rb (host application)
|
|
205
|
+
Rails.application.routes.draw do
|
|
206
|
+
mount Accounts::Engine, at: "/accounts"
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This engine ships **no controllers** intentionally. Hosts drive
|
|
211
|
+
account-creation flows themselves (the shape of the sign-up wizard
|
|
212
|
+
varies too much to template). Use `Accounts::Account.create_with_owner`
|
|
213
|
+
from your own controller.
|
|
214
|
+
|
|
215
|
+
## Running the specs
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
bin/rails seams:test[accounts]
|
|
219
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Accounts
|
|
4
|
+
# The tenant boundary. One Account per "workspace" / "company" /
|
|
5
|
+
# "tenant" — every other engine that wants to scope its data to a
|
|
6
|
+
# tenant binds to this row.
|
|
7
|
+
#
|
|
8
|
+
# Identity (Auth::Identity) is the human; Account is the workspace;
|
|
9
|
+
# Membership joins them. The same human (Identity) can have
|
|
10
|
+
# memberships in multiple Accounts.
|
|
11
|
+
#
|
|
12
|
+
# UUID primary key (not bigint) so account ids can appear in
|
|
13
|
+
# shareable URLs without leaking row counts. The host User is gone
|
|
14
|
+
# post-Wave-9; downstream engines reference Identity (`identity_id`)
|
|
15
|
+
# or Account (`account_id`).
|
|
16
|
+
class Account < ApplicationRecord
|
|
17
|
+
self.table_name = "accounts"
|
|
18
|
+
self.primary_key = "id"
|
|
19
|
+
self.implicit_order_column = "created_at"
|
|
20
|
+
|
|
21
|
+
has_many :memberships, class_name: "Accounts::Membership",
|
|
22
|
+
foreign_key: :account_id, dependent: :destroy
|
|
23
|
+
|
|
24
|
+
validates :name, presence: true
|
|
25
|
+
|
|
26
|
+
scope :active, -> { where(cancelled_at: nil) }
|
|
27
|
+
scope :cancelled, -> { where.not(cancelled_at: nil) }
|
|
28
|
+
|
|
29
|
+
before_create :assign_external_account_id
|
|
30
|
+
|
|
31
|
+
after_create_commit :publish_account_created
|
|
32
|
+
after_update_commit :publish_account_cancelled, if: :saved_change_to_cancelled_at?
|
|
33
|
+
|
|
34
|
+
# Returns self so polymorphic helpers that expect `record.account`
|
|
35
|
+
# work uniformly across an Account row and any account-scoped row.
|
|
36
|
+
def account
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def system_membership
|
|
41
|
+
memberships.find_by(role: "system")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def active?
|
|
45
|
+
cancelled_at.nil? && incinerated_at.nil?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cancelled?
|
|
49
|
+
cancelled_at.present?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Hard-delete grace period: a cancelled account becomes
|
|
53
|
+
# incinerated when the host job decides the grace window is up.
|
|
54
|
+
def incinerated?
|
|
55
|
+
incinerated_at.present?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Wraps Account creation + System + Owner membership in a single
|
|
59
|
+
# transaction so the audit trail always has a System actor and the
|
|
60
|
+
# human creating the account has an Owner row.
|
|
61
|
+
#
|
|
62
|
+
# Accounts::Account.create_with_owner(
|
|
63
|
+
# account: { name: "Acme" },
|
|
64
|
+
# owner: identity_or_owner_struct
|
|
65
|
+
# )
|
|
66
|
+
#
|
|
67
|
+
# `owner` must respond to `identity` and `name`. The Identity link
|
|
68
|
+
# is the human's row in `auth_identities`.
|
|
69
|
+
def self.create_with_owner(account:, owner:)
|
|
70
|
+
transaction do
|
|
71
|
+
record = create!(**account)
|
|
72
|
+
record.memberships.create!(
|
|
73
|
+
role: "system",
|
|
74
|
+
name: "System",
|
|
75
|
+
identity_id: nil,
|
|
76
|
+
active: true
|
|
77
|
+
)
|
|
78
|
+
record.memberships.create!(
|
|
79
|
+
role: "owner",
|
|
80
|
+
name: owner.name,
|
|
81
|
+
identity_id: owner.identity.id,
|
|
82
|
+
verified_at: Time.current,
|
|
83
|
+
active: true
|
|
84
|
+
)
|
|
85
|
+
record
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def assign_external_account_id
|
|
92
|
+
self.external_account_id ||= self.class.next_external_account_id
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Per-class sequence so external_account_id stays unique without a
|
|
96
|
+
# DB-side sequence (UUID PKs make per-row IDs cheap, but
|
|
97
|
+
# external_account_id is a slug-friendly bigint). Hosts that want
|
|
98
|
+
# a different sequence can override this method.
|
|
99
|
+
def self.next_external_account_id
|
|
100
|
+
loop do
|
|
101
|
+
candidate = SecureRandom.random_number(2**62)
|
|
102
|
+
break candidate unless exists?(external_account_id: candidate)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def publish_account_created
|
|
107
|
+
Seams::Events::Publisher.publish(
|
|
108
|
+
"account.created.accounts",
|
|
109
|
+
account_id: id,
|
|
110
|
+
owner_identity_id: memberships.where(role: "owner").pick(:identity_id)
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def publish_account_cancelled
|
|
115
|
+
return if cancelled_at.nil?
|
|
116
|
+
|
|
117
|
+
Seams::Events::Publisher.publish(
|
|
118
|
+
"account.cancelled.accounts",
|
|
119
|
+
account_id: id,
|
|
120
|
+
cancelled_by_identity_id: Accounts::Current.membership&.identity_id
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Accounts
|
|
4
|
+
# ActiveSupport::CurrentAttributes namespace for the Accounts engine.
|
|
5
|
+
# Set once per request by the host (typically a before_action that
|
|
6
|
+
# resolves the account from the URL or session); readable from
|
|
7
|
+
# anywhere downstream without explicit threading.
|
|
8
|
+
#
|
|
9
|
+
# Setting `account=` automatically derives the matching Membership
|
|
10
|
+
# for the currently signed-in identity (read from `Auth::Current`).
|
|
11
|
+
# The cross-engine read into `Auth::Current.identity` is intentional:
|
|
12
|
+
# the auth and accounts engines each own a per-request namespace;
|
|
13
|
+
# the accounts engine reads identity to figure out which membership
|
|
14
|
+
# is "current" but never writes to `Auth::Current`.
|
|
15
|
+
#
|
|
16
|
+
# Contract: `Auth::Current.identity` MUST be set before
|
|
17
|
+
# `Accounts::Current.account =`. Hosts wire this in their
|
|
18
|
+
# ApplicationController after their authentication concern runs.
|
|
19
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
20
|
+
attribute :account, :membership
|
|
21
|
+
|
|
22
|
+
def account=(value)
|
|
23
|
+
super(value)
|
|
24
|
+
self.membership =
|
|
25
|
+
if value && defined?(Auth::Current) && Auth::Current.identity
|
|
26
|
+
value.memberships.find_by(identity_id: Auth::Current.identity.id)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def with_account(value, &)
|
|
31
|
+
with(account: value, &)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def without_account(&)
|
|
35
|
+
with(account: nil, membership: nil, &)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|