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,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubocop"
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Seams
|
|
8
|
+
# Flags references to another engine's data classes from inside an
|
|
9
|
+
# engine. Engines should communicate via events or via
|
|
10
|
+
# explicitly-exposed concerns — never by reaching into another
|
|
11
|
+
# engine's data layer.
|
|
12
|
+
#
|
|
13
|
+
# Configured per-engine via the `OwnEngine`, `OtherEngines`, and
|
|
14
|
+
# `ExposedConcerns` options inside the engine's own .rubocop.yml.
|
|
15
|
+
#
|
|
16
|
+
# The cop deliberately ignores Rails framework constants that
|
|
17
|
+
# every engine exposes (`Engine`, `VERSION`, `ApplicationController`,
|
|
18
|
+
# `ApplicationRecord`, `ApplicationJob`, `ApplicationMailer`) and
|
|
19
|
+
# any class whose name ends in one of the configured suffixes
|
|
20
|
+
# (`Controller`, `Job`, `Mailer`, `Helper`, `Component`, `Engine`).
|
|
21
|
+
# Concerns (`Billing::Billable`, `Billing::Concerns::Billable`)
|
|
22
|
+
# are exempt when listed in `ExposedConcerns`.
|
|
23
|
+
#
|
|
24
|
+
# `<Engine>::Current` is also exempt by design: every engine ships
|
|
25
|
+
# its own `ActiveSupport::CurrentAttributes` namespace
|
|
26
|
+
# (`Auth::Current`, `Accounts::Current`, `Teams::Current`, etc.)
|
|
27
|
+
# and these per-request state holders are intentionally readable
|
|
28
|
+
# from anywhere in the host. Treating them as boundary-violations
|
|
29
|
+
# would force every cross-engine read of per-request identity /
|
|
30
|
+
# account / team to go through a host-defined shim, which defeats
|
|
31
|
+
# the purpose of `CurrentAttributes` as a shared per-request bus.
|
|
32
|
+
# The exception is documented in `doc/CURRENT_ATTRIBUTES.md`.
|
|
33
|
+
class NoCrossEngineModelAccess < Base
|
|
34
|
+
MSG = "Engine `%<own>s` must not access `%<const>s` directly. " \
|
|
35
|
+
"Use an event or a %<other>s-exposed concern instead."
|
|
36
|
+
|
|
37
|
+
DEFAULT_IGNORED_LEAF_NAMES = %w[
|
|
38
|
+
Engine
|
|
39
|
+
VERSION
|
|
40
|
+
ApplicationController
|
|
41
|
+
ApplicationRecord
|
|
42
|
+
ApplicationJob
|
|
43
|
+
ApplicationMailer
|
|
44
|
+
ApplicationHelper
|
|
45
|
+
ApplicationCable
|
|
46
|
+
Routes
|
|
47
|
+
Current
|
|
48
|
+
].freeze
|
|
49
|
+
|
|
50
|
+
DEFAULT_IGNORED_LEAF_SUFFIXES = %w[
|
|
51
|
+
Controller
|
|
52
|
+
Job
|
|
53
|
+
Mailer
|
|
54
|
+
Helper
|
|
55
|
+
Component
|
|
56
|
+
Channel
|
|
57
|
+
Engine
|
|
58
|
+
].freeze
|
|
59
|
+
|
|
60
|
+
def on_const(node)
|
|
61
|
+
return unless flaggable?(node)
|
|
62
|
+
|
|
63
|
+
full_name = node.const_name.to_s
|
|
64
|
+
top_level = full_name.split("::").first
|
|
65
|
+
|
|
66
|
+
assert_own_engine_configured!
|
|
67
|
+
|
|
68
|
+
add_offense(
|
|
69
|
+
node,
|
|
70
|
+
message: format(MSG, own: own_engine, const: full_name, other: top_level)
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def flaggable?(node)
|
|
77
|
+
parts = const_parts_under_other_engine(node)
|
|
78
|
+
return false unless parts
|
|
79
|
+
|
|
80
|
+
full_name = parts.join("::")
|
|
81
|
+
return false if exposed_concern?(full_name)
|
|
82
|
+
return false if framework_constant?(parts)
|
|
83
|
+
return false if ignored_suffix?(parts.last)
|
|
84
|
+
return false if inside_defined_check?(node)
|
|
85
|
+
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# `defined?(Teams::Team)` is a soft existence check — the
|
|
90
|
+
# constant is not actually accessed for value, just probed for
|
|
91
|
+
# presence. Skip the cop in that case so guards like
|
|
92
|
+
# `Teams::Team if defined?(Teams::Team)` don't false-fire.
|
|
93
|
+
# Walks parents up to a few levels so `defined?(Teams::Team.foo)`
|
|
94
|
+
# (where the const's parent is a `send` node, not the `defined?`)
|
|
95
|
+
# is also exempted.
|
|
96
|
+
def inside_defined_check?(node)
|
|
97
|
+
ancestor = node.parent
|
|
98
|
+
5.times do
|
|
99
|
+
return false unless ancestor
|
|
100
|
+
return true if ancestor.defined_type?
|
|
101
|
+
|
|
102
|
+
ancestor = ancestor.parent
|
|
103
|
+
end
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns the segments of the constant if `node` is the
|
|
108
|
+
# outermost reference to a multi-segment constant whose first
|
|
109
|
+
# segment is a sibling engine. Returns nil otherwise.
|
|
110
|
+
def const_parts_under_other_engine(node)
|
|
111
|
+
return nil if other_engines.empty?
|
|
112
|
+
return nil if node.parent&.const_type?
|
|
113
|
+
|
|
114
|
+
parts = node.const_name.to_s.split("::")
|
|
115
|
+
return nil if parts.size < 2
|
|
116
|
+
return nil unless other_engines.include?(parts.first)
|
|
117
|
+
|
|
118
|
+
parts
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def own_engine
|
|
122
|
+
name = cop_config["OwnEngine"]
|
|
123
|
+
name&.to_s
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def assert_own_engine_configured!
|
|
127
|
+
return if own_engine && !own_engine.empty?
|
|
128
|
+
|
|
129
|
+
raise RuboCop::Error,
|
|
130
|
+
"Seams/NoCrossEngineModelAccess requires `OwnEngine` to be set in " \
|
|
131
|
+
".rubocop.yml so it knows which engine the file under inspection belongs to."
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def other_engines
|
|
135
|
+
Array(cop_config["OtherEngines"]).map(&:to_s)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def exposed_concern?(full_name)
|
|
139
|
+
allowlist = Array(cop_config["ExposedConcerns"]).map(&:to_s)
|
|
140
|
+
allowlist.include?(full_name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def framework_constant?(parts)
|
|
144
|
+
DEFAULT_IGNORED_LEAF_NAMES.include?(parts.last)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def ignored_suffix?(leaf_name)
|
|
148
|
+
DEFAULT_IGNORED_LEAF_SUFFIXES.any? { |suffix| leaf_name.end_with?(suffix) }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/seams/cops.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Entry point for the Seams custom RuboCop cops. Add this line to your
|
|
4
|
+
# host application's .rubocop.yml to enable boundary enforcement:
|
|
5
|
+
#
|
|
6
|
+
# require:
|
|
7
|
+
# - seams/cops
|
|
8
|
+
#
|
|
9
|
+
# `require:` (not `plugins:`) is the correct form here because Seams
|
|
10
|
+
# registers its cops via plain `require`, not via the formal RuboCop
|
|
11
|
+
# plugin API. All cops live under the RuboCop::Cop::Seams namespace.
|
|
12
|
+
|
|
13
|
+
require "rubocop"
|
|
14
|
+
|
|
15
|
+
require "seams/cops/no_cross_engine_model_access"
|
|
16
|
+
require "seams/cops/no_cross_engine_dependency"
|
|
17
|
+
require "seams/cops/known_queue_names"
|
|
18
|
+
require "seams/cops/migration_comments"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams/events"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
# Tracks every event name that an engine has declared it emits, along
|
|
7
|
+
# with the engine that owns it. Acts as the single source of truth for
|
|
8
|
+
# `bin/rails seams:list` and prevents two engines from claiming the
|
|
9
|
+
# same event name.
|
|
10
|
+
module EventRegistry
|
|
11
|
+
@registry = {}
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def register(name, emitted_by:)
|
|
16
|
+
Events.assert_valid_name!(name)
|
|
17
|
+
name = name.to_s
|
|
18
|
+
|
|
19
|
+
@mutex.synchronize do
|
|
20
|
+
existing = @registry[name]
|
|
21
|
+
|
|
22
|
+
if existing && existing != emitted_by
|
|
23
|
+
raise Events::DuplicateEventError,
|
|
24
|
+
"Event #{name.inspect} already registered by #{existing.inspect}; " \
|
|
25
|
+
"cannot also be registered by #{emitted_by.inspect}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@registry[name] = emitted_by
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def registered?(name)
|
|
33
|
+
@registry.key?(name.to_s)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def emitter_of(name)
|
|
37
|
+
@registry[name.to_s]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def all
|
|
41
|
+
@registry.dup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reset!
|
|
45
|
+
@mutex.synchronize { @registry.clear }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams/events"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module Events
|
|
7
|
+
# Abstract base class for event-bus adapters. Real adapters wrap a
|
|
8
|
+
# transport (ActiveSupport::Notifications, an external queue, etc.) and
|
|
9
|
+
# implement the three primitives below.
|
|
10
|
+
class Adapter
|
|
11
|
+
def publish(_event_name, _payload)
|
|
12
|
+
raise NotImplementedError, "#{self.class} must implement #publish"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def subscribe(_event_name, &)
|
|
16
|
+
raise NotImplementedError, "#{self.class} must implement #subscribe"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def unsubscribe(_subscriber)
|
|
20
|
+
raise NotImplementedError, "#{self.class} must implement #unsubscribe"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/isolated_execution_state"
|
|
5
|
+
require "active_support/notifications"
|
|
6
|
+
require "seams/events/adapter"
|
|
7
|
+
|
|
8
|
+
module Seams
|
|
9
|
+
module Events
|
|
10
|
+
module Adapters
|
|
11
|
+
# Default in-process adapter, backed by ActiveSupport::Notifications.
|
|
12
|
+
# Suitable for monolithic deployments — every engine in the host app
|
|
13
|
+
# shares the same process, so subscribers fire synchronously after
|
|
14
|
+
# the publisher's call returns.
|
|
15
|
+
class ActiveSupport < Seams::Events::Adapter
|
|
16
|
+
def publish(event_name, payload)
|
|
17
|
+
payload = { payload: payload } unless payload.is_a?(Hash)
|
|
18
|
+
::ActiveSupport::Notifications.instrument(event_name, payload)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def subscribe(event_name, &)
|
|
22
|
+
::ActiveSupport::Notifications.subscribe(event_name, &)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def unsubscribe(subscriber)
|
|
26
|
+
::ActiveSupport::Notifications.unsubscribe(subscriber)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams/events"
|
|
4
|
+
require "seams/event_registry"
|
|
5
|
+
|
|
6
|
+
module Seams
|
|
7
|
+
module Events
|
|
8
|
+
# Public API for publishing and subscribing to inter-engine events.
|
|
9
|
+
#
|
|
10
|
+
# Engines should always go through this module rather than calling the
|
|
11
|
+
# underlying adapter directly — it enforces the naming convention,
|
|
12
|
+
# checks the EventRegistry, and gives subscribers a simple
|
|
13
|
+
# block-takes-payload interface regardless of which adapter is in use.
|
|
14
|
+
#
|
|
15
|
+
# Subscribers run **synchronously** in the publisher's thread (the
|
|
16
|
+
# default ActiveSupport::Notifications adapter has no other mode).
|
|
17
|
+
# They should therefore enqueue background jobs for any side effect
|
|
18
|
+
# that talks to the network or could fail — never perform the side
|
|
19
|
+
# effect inline. Seams does not enforce this; treat it as a
|
|
20
|
+
# convention that the boundary review catches.
|
|
21
|
+
module Publisher
|
|
22
|
+
class << self
|
|
23
|
+
def publish(event_name, payload = {})
|
|
24
|
+
Events.assert_valid_name!(event_name)
|
|
25
|
+
name = event_name.to_s
|
|
26
|
+
|
|
27
|
+
unless EventRegistry.registered?(name)
|
|
28
|
+
raise UnregisteredEventError,
|
|
29
|
+
"Event #{name.inspect} has not been registered. " \
|
|
30
|
+
"Declare it in the engine that emits it via " \
|
|
31
|
+
"Seams::EventRegistry.register(#{name.inspect}, emitted_by: '<EngineName>')."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
adapter.publish(name, payload)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def subscribe(event_name, &)
|
|
38
|
+
Events.assert_valid_name!(event_name)
|
|
39
|
+
name = event_name.to_s
|
|
40
|
+
subscriptions << name unless subscriptions.include?(name)
|
|
41
|
+
|
|
42
|
+
adapter.subscribe(name) do |*args|
|
|
43
|
+
yield(args.last)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Idempotent variant of #subscribe. The first call attaches and
|
|
48
|
+
# remembers the (key, event_name) pair on Seams::Events::Publisher
|
|
49
|
+
# itself — a Rails autoreload that re-evaluates the subscriber
|
|
50
|
+
# class file does NOT lose this state, because Publisher is in
|
|
51
|
+
# the gem and isn't reloaded. Subsequent calls with the same
|
|
52
|
+
# (key, event_name) are no-ops, preventing the "welcome email
|
|
53
|
+
# fires N times after N reloads" bug.
|
|
54
|
+
#
|
|
55
|
+
# Synchronized so concurrent boot threads (e.g. Puma cluster
|
|
56
|
+
# pre-fork) can't race-attach the same subscriber twice.
|
|
57
|
+
#
|
|
58
|
+
# CAVEAT — Rails autoreload staleness:
|
|
59
|
+
# The block passed here closes over its lexical binding, which
|
|
60
|
+
# in practice means the subscriber CLASS object as it existed
|
|
61
|
+
# when +attach!+ first ran. After Rails reloads the subscriber
|
|
62
|
+
# file, the constant points at a fresh class object, but THIS
|
|
63
|
+
# block still calls into the old one — so edits to the
|
|
64
|
+
# subscriber's methods are invisible until a full server
|
|
65
|
+
# restart. For new code, prefer #attach_class which re-resolves
|
|
66
|
+
# the constant on every dispatch and so is reload-safe.
|
|
67
|
+
#
|
|
68
|
+
# Use a per-subscriber-class symbol as the key:
|
|
69
|
+
#
|
|
70
|
+
# Publisher.attach_once(:notifications_auth_subscriber,
|
|
71
|
+
# "identity.signed_up.auth") { |payload| ... }
|
|
72
|
+
def attach_once(key, event_name, &)
|
|
73
|
+
attach_once_mutex.synchronize do
|
|
74
|
+
attached_keys[[key, event_name.to_s]] ||= subscribe(event_name, &)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Reload-safe alternative to #attach_once. Stores the subscriber
|
|
79
|
+
# class as a STRING name and re-resolves +Object.const_get+ on
|
|
80
|
+
# every dispatch — so when Rails autoreload swaps the constant
|
|
81
|
+
# for a freshly-loaded class object, the next event reaches the
|
|
82
|
+
# new code without a server restart.
|
|
83
|
+
#
|
|
84
|
+
# The +class_name+ MUST be a String (e.g. "Notifications::AuthSubscriber").
|
|
85
|
+
# Passing the class object itself defeats the fix: it captures a
|
|
86
|
+
# reference to the pre-reload object and exhibits exactly the
|
|
87
|
+
# staleness bug this method exists to avoid.
|
|
88
|
+
#
|
|
89
|
+
# The named class method is invoked via +send+, so it may be
|
|
90
|
+
# +private+ — keeping subscribers' handlers out of their public
|
|
91
|
+
# surface. Idempotent on (key, event_name) like #attach_once.
|
|
92
|
+
#
|
|
93
|
+
# Example:
|
|
94
|
+
#
|
|
95
|
+
# Publisher.attach_class(
|
|
96
|
+
# :notifications_auth_subscriber,
|
|
97
|
+
# "identity.signed_up.auth",
|
|
98
|
+
# class_name: "Notifications::AuthSubscriber",
|
|
99
|
+
# method_name: :handle_signed_up
|
|
100
|
+
# )
|
|
101
|
+
def attach_class(key, event_name, class_name:, method_name:)
|
|
102
|
+
unless class_name.is_a?(String)
|
|
103
|
+
raise ArgumentError,
|
|
104
|
+
"attach_class requires class_name as a String (got #{class_name.class}). " \
|
|
105
|
+
"Passing the class object captures a stale reference across Rails reloads — " \
|
|
106
|
+
"the very bug this method exists to prevent."
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
method_symbol = method_name.to_sym
|
|
110
|
+
|
|
111
|
+
attach_once_mutex.synchronize do
|
|
112
|
+
attached_keys[[key, event_name.to_s]] ||= subscribe(event_name) do |payload|
|
|
113
|
+
Object.const_get(class_name).send(method_symbol, payload)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def unsubscribe(subscriber)
|
|
119
|
+
adapter.unsubscribe(subscriber)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Internal — exposed for spec teardown only.
|
|
123
|
+
def attached_keys
|
|
124
|
+
@attached_keys ||= {}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def attach_once_mutex
|
|
128
|
+
@attach_once_mutex ||= Mutex.new
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Returns the list of event names that engines have subscribed
|
|
132
|
+
# to during this process's lifetime. Useful for reporting and
|
|
133
|
+
# for the post-boot validation hook below.
|
|
134
|
+
def subscriptions
|
|
135
|
+
@subscriptions ||= []
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Walks every subscription and returns the names that no engine
|
|
139
|
+
# has registered as an emitted event. Hosts can call this from
|
|
140
|
+
# an after_initialize block (or in a CI smoke test) to catch
|
|
141
|
+
# typos like subscribing to "identity.signed_up.atuh".
|
|
142
|
+
def orphan_subscriptions
|
|
143
|
+
subscriptions.reject { |name| EventRegistry.registered?(name) }.uniq
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def adapter
|
|
147
|
+
@adapter ||= build_adapter
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Tears down everything Publisher has registered with the
|
|
151
|
+
# adapter and clears the bookkeeping. Without the unsubscribe
|
|
152
|
+
# step, ActiveSupport::Notifications keeps the prior process's
|
|
153
|
+
# subscribers alive in its global registry — so test runs that
|
|
154
|
+
# call +reset!+ between examples accumulate stale subscribers
|
|
155
|
+
# that fire (and may raise on now-gone constants) on every
|
|
156
|
+
# publish in the next example.
|
|
157
|
+
def reset!
|
|
158
|
+
@attached_keys&.each_value do |subscriber|
|
|
159
|
+
adapter.unsubscribe(subscriber) if subscriber
|
|
160
|
+
end
|
|
161
|
+
@adapter = nil
|
|
162
|
+
@subscriptions = nil
|
|
163
|
+
@attached_keys = nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def build_adapter
|
|
169
|
+
klass_name = Seams.configuration.event_bus_adapter
|
|
170
|
+
Object.const_get(klass_name).new
|
|
171
|
+
rescue NameError => e
|
|
172
|
+
raise Seams::ConfigurationError,
|
|
173
|
+
"Event bus adapter #{klass_name.inspect} could not be loaded: #{e.message}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/seams/events.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Seams
|
|
4
|
+
# Events module — public API for inter-engine communication.
|
|
5
|
+
#
|
|
6
|
+
# Engines publish domain events through Seams::Events::Publisher and
|
|
7
|
+
# subscribe to events from other engines via the same module. Subscribers
|
|
8
|
+
# are expected to enqueue background jobs rather than perform side
|
|
9
|
+
# effects synchronously, so that the publisher's transaction can commit
|
|
10
|
+
# quickly and side effects can retry independently.
|
|
11
|
+
module Events
|
|
12
|
+
class Error < Seams::Error; end
|
|
13
|
+
|
|
14
|
+
# Raised when an event name is published that no engine has registered.
|
|
15
|
+
class UnregisteredEventError < Error; end
|
|
16
|
+
|
|
17
|
+
# Raised when two engines try to register the same event name.
|
|
18
|
+
class DuplicateEventError < Error; end
|
|
19
|
+
|
|
20
|
+
# Raised when an event name doesn't follow the resource.action.engine
|
|
21
|
+
# convention (e.g., "subscription.created.billing").
|
|
22
|
+
class InvalidEventNameError < Error; end
|
|
23
|
+
|
|
24
|
+
# Three dot-separated segments, lowercase, snake_case allowed.
|
|
25
|
+
NAME_PATTERN = /\A[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*\z/
|
|
26
|
+
|
|
27
|
+
def self.valid_name?(name)
|
|
28
|
+
NAME_PATTERN.match?(name.to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.assert_valid_name!(name)
|
|
32
|
+
return if valid_name?(name)
|
|
33
|
+
|
|
34
|
+
raise InvalidEventNameError,
|
|
35
|
+
"Event name #{name.inspect} must follow resource.action.engine " \
|
|
36
|
+
"(e.g. subscription.created.billing)"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|