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,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "rails/generators"
|
|
5
|
+
require "seams"
|
|
6
|
+
require "seams/generators/sibling_rubocop_writer"
|
|
7
|
+
require "seams/generators/host_injector"
|
|
8
|
+
|
|
9
|
+
module Seams
|
|
10
|
+
module Generators
|
|
11
|
+
# Removes an engine generated by `seams:engine`. Prompts for
|
|
12
|
+
# confirmation unless --force is passed. Cleans up the surviving
|
|
13
|
+
# engines' .rubocop.yml so OtherEngines no longer references the
|
|
14
|
+
# engine that was just removed. Reverses host file edits made by
|
|
15
|
+
# the canonical generators (mount line, includes) — leaves the
|
|
16
|
+
# Gemfile alone since other engines may share gem deps.
|
|
17
|
+
#
|
|
18
|
+
# Run with: bin/rails generate seams:remove billing [--force]
|
|
19
|
+
class RemoveGenerator < Rails::Generators::NamedBase
|
|
20
|
+
include Seams::Generators::HostInjector
|
|
21
|
+
|
|
22
|
+
# Same constraints as EngineGenerator — keeps `seams:remove ../../etc`
|
|
23
|
+
# from being interpreted as a relative path the destructive
|
|
24
|
+
# FileUtils.rm_rf at line 80 would happily follow with --force.
|
|
25
|
+
NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
|
|
26
|
+
|
|
27
|
+
class_option :force, type: :boolean, default: false,
|
|
28
|
+
desc: "Skip the confirmation prompt"
|
|
29
|
+
|
|
30
|
+
def validate_name
|
|
31
|
+
return if NAME_PATTERN.match?(name)
|
|
32
|
+
|
|
33
|
+
raise Seams::GeneratorError,
|
|
34
|
+
"Engine name #{name.inspect} must be lowercase letters, digits, " \
|
|
35
|
+
"and underscores, starting with a letter."
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Maps engine name -> { mount: <Class>, includes: { user: [...], application_controller: [...] } }
|
|
39
|
+
# Lets remove know what to undo for the canonical engines. Generic
|
|
40
|
+
# engines aren't in this table and just get the directory deleted.
|
|
41
|
+
CANONICAL_HOST_EDITS = {
|
|
42
|
+
"auth" => {
|
|
43
|
+
mount: "Auth::Engine",
|
|
44
|
+
user_includes: %w[Auth::Authenticatable],
|
|
45
|
+
application_controller_includes: %w[Auth::Authentication]
|
|
46
|
+
},
|
|
47
|
+
"notifications" => {
|
|
48
|
+
mount: "Notifications::Engine",
|
|
49
|
+
user_includes: %w[Notifications::Notifiable]
|
|
50
|
+
},
|
|
51
|
+
# Post-Wave-9: billing no longer injects Billing::Billable
|
|
52
|
+
# into the host User — the engine includes it into the
|
|
53
|
+
# configured tenant class (default Accounts::Account) at
|
|
54
|
+
# boot via Billing.configuration.billable_class. The only
|
|
55
|
+
# host-side edit billing makes is the mount line, so unmount
|
|
56
|
+
# is the only reverse-edit we need.
|
|
57
|
+
"billing" => {
|
|
58
|
+
mount: "Billing::Engine",
|
|
59
|
+
user_includes: %w[]
|
|
60
|
+
},
|
|
61
|
+
"teams" => {
|
|
62
|
+
# Wave 9 removed Teams::Teamable — there's no canonical host
|
|
63
|
+
# User concern to remove. The `mount Teams::Engine` line is
|
|
64
|
+
# the only host edit the teams generator makes, so unmount
|
|
65
|
+
# is the only reverse-edit we need here.
|
|
66
|
+
mount: "Teams::Engine",
|
|
67
|
+
user_includes: %w[]
|
|
68
|
+
},
|
|
69
|
+
# Wave 9 — accounts engine. Like billing/teams post-Wave-9, the
|
|
70
|
+
# accounts generator does NOT inject anything into the host User
|
|
71
|
+
# (the canonical demo doesn't have one). Mount is the only
|
|
72
|
+
# host-side edit, so unmount is the only reverse-edit we need.
|
|
73
|
+
"accounts" => {
|
|
74
|
+
mount: "Accounts::Engine",
|
|
75
|
+
user_includes: %w[]
|
|
76
|
+
},
|
|
77
|
+
# Wave 11A — admin engine. Mounts under Seams::Admin::Engine.
|
|
78
|
+
# The generator injects `gem "administrate"` and `gem "pundit"`
|
|
79
|
+
# into the host Gemfile but the remover does NOT prune them
|
|
80
|
+
# (the host may have other dashboards depending on either).
|
|
81
|
+
# Unmounting + dropping the engine dir is enough.
|
|
82
|
+
"admin" => {
|
|
83
|
+
mount: "Seams::Admin::Engine",
|
|
84
|
+
user_includes: %w[]
|
|
85
|
+
}
|
|
86
|
+
}.freeze
|
|
87
|
+
|
|
88
|
+
# Read the engine's create_table calls before its directory is
|
|
89
|
+
# deleted. Used by write_drop_table_migration to generate the
|
|
90
|
+
# reversal. Pattern-matches the literal ActiveRecord::Migration
|
|
91
|
+
# call so we drop exactly what the engine created — never
|
|
92
|
+
# host-side tables that happen to share the prefix.
|
|
93
|
+
def capture_engine_tables
|
|
94
|
+
engine_path = File.join(destination_root, "engines", name)
|
|
95
|
+
return @engine_tables = [] unless File.directory?(engine_path)
|
|
96
|
+
|
|
97
|
+
migrate_dir = File.join(engine_path, "db/migrate")
|
|
98
|
+
return @engine_tables = [] unless File.directory?(migrate_dir)
|
|
99
|
+
|
|
100
|
+
@engine_tables = Dir.glob(File.join(migrate_dir, "*.rb")).flat_map do |file|
|
|
101
|
+
# Strip line comments before scanning so a stray
|
|
102
|
+
# `# create_table :backup_table do |t|` in a migration
|
|
103
|
+
# doesn't end up in the drop-table list.
|
|
104
|
+
source = File.read(file).gsub(/^\s*#.*$/, "")
|
|
105
|
+
source.scan(/^\s*create_table\s+:(\w+)/).flatten
|
|
106
|
+
end.uniq
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def remove_engine_directory
|
|
110
|
+
engine_path = File.join(destination_root, "engines", name)
|
|
111
|
+
|
|
112
|
+
unless File.directory?(engine_path)
|
|
113
|
+
@engine_was_present = false
|
|
114
|
+
say " skip engines/#{name}/ (not found)", :yellow
|
|
115
|
+
return
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
return unless options[:force] || confirm_removal?
|
|
119
|
+
|
|
120
|
+
FileUtils.rm_rf(engine_path)
|
|
121
|
+
@engine_was_present = true
|
|
122
|
+
say " remove engines/#{name}/", :red
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Phase 1.7 — generate a drop-table migration in the host's
|
|
126
|
+
# db/migrate so the host can run `bin/rails db:migrate` to
|
|
127
|
+
# reclaim the engine's tables. Idempotent if-table-exists
|
|
128
|
+
# check so re-running doesn't blow up on already-dropped tables.
|
|
129
|
+
def write_drop_table_migration
|
|
130
|
+
return unless @engine_was_present
|
|
131
|
+
return if @engine_tables.nil? || @engine_tables.empty?
|
|
132
|
+
|
|
133
|
+
migrate_dir = File.join(destination_root, "db/migrate")
|
|
134
|
+
FileUtils.mkdir_p(migrate_dir)
|
|
135
|
+
|
|
136
|
+
filename = "#{drop_migration_timestamp}_drop_#{name}_tables.rb"
|
|
137
|
+
migration_path = File.join(migrate_dir, filename)
|
|
138
|
+
File.write(migration_path, drop_table_migration_body)
|
|
139
|
+
|
|
140
|
+
say " create db/migrate/#{filename}", :green
|
|
141
|
+
say " → run `bin/rails db:migrate` to drop #{@engine_tables.size} table(s).", :yellow
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Phase 1.7 — re-run `bundle install` so the host's lockfile no
|
|
145
|
+
# longer references the removed engine's gem deps (the canonical
|
|
146
|
+
# generators each inject their own — bcrypt, faraday, etc; the
|
|
147
|
+
# remover doesn't touch the Gemfile because deps are usually
|
|
148
|
+
# shared, but a fresh `bundle install` keeps lockfile + Gemfile
|
|
149
|
+
# in sync if the host did prune anything by hand). Skipped when
|
|
150
|
+
# the engine was never present (no-op remove) or when there's
|
|
151
|
+
# no Gemfile to bundle against.
|
|
152
|
+
def run_bundle_install
|
|
153
|
+
return unless @engine_was_present
|
|
154
|
+
return unless File.exist?(File.join(destination_root, "Gemfile"))
|
|
155
|
+
|
|
156
|
+
say " run bundle install (post-remove sync)", :green
|
|
157
|
+
Dir.chdir(destination_root) do
|
|
158
|
+
system("bundle", "install", "--quiet") || say(" → bundle install failed; resolve manually.", :red)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def update_sibling_engines
|
|
163
|
+
return unless @engine_was_present
|
|
164
|
+
|
|
165
|
+
engines_root = File.join(destination_root, "engines")
|
|
166
|
+
return unless Dir.exist?(engines_root)
|
|
167
|
+
|
|
168
|
+
survivors = Dir.children(engines_root)
|
|
169
|
+
.select { |c| File.directory?(File.join(engines_root, c)) }
|
|
170
|
+
.reject { |c| c.start_with?(".") }
|
|
171
|
+
.sort
|
|
172
|
+
|
|
173
|
+
return if survivors.empty?
|
|
174
|
+
|
|
175
|
+
Seams::Generators::SiblingRubocopWriter.rewrite!(engines_root: engines_root, dirs: survivors)
|
|
176
|
+
say " update .rubocop.yml of #{survivors.size} sibling engine(s)", :green
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def unwire_from_host
|
|
180
|
+
return unless @engine_was_present
|
|
181
|
+
|
|
182
|
+
unwire_generic_host_edits
|
|
183
|
+
unwire_canonical_host_edits(CANONICAL_HOST_EDITS[name])
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
def unwire_generic_host_edits
|
|
189
|
+
# Generic uninject — applies to every engine. The mount line +
|
|
190
|
+
# initializer were created by the generic engine generator.
|
|
191
|
+
host_uninject_mount(engine_class: "#{generic_module_name}::Engine")
|
|
192
|
+
host_remove_initializer
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def unwire_canonical_host_edits(edits)
|
|
196
|
+
return unless edits
|
|
197
|
+
|
|
198
|
+
host_uninject_mount(engine_class: edits[:mount]) if edits[:mount]
|
|
199
|
+
Array(edits[:user_includes]).each do |concern|
|
|
200
|
+
host_uninject_include("app/models/user.rb", concern)
|
|
201
|
+
end
|
|
202
|
+
Array(edits[:application_controller_includes]).each do |concern|
|
|
203
|
+
host_uninject_include("app/controllers/application_controller.rb", concern)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def confirm_removal?
|
|
208
|
+
yes?("Remove engine `#{name}` and everything under engines/#{name}? [y/N]")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def generic_module_name
|
|
212
|
+
name.split("_").map(&:capitalize).join
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def host_remove_initializer
|
|
216
|
+
path = File.join(destination_root, "config/initializers/#{name}.rb")
|
|
217
|
+
return unless File.exist?(path)
|
|
218
|
+
|
|
219
|
+
FileUtils.rm(path)
|
|
220
|
+
say " remove config/initializers/#{name}.rb", :red
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def drop_migration_timestamp
|
|
224
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def drop_table_migration_body
|
|
228
|
+
drops = @engine_tables.map do |table|
|
|
229
|
+
%( drop_table :#{table}, force: :cascade if table_exists?(:#{table}))
|
|
230
|
+
end.join("\n")
|
|
231
|
+
|
|
232
|
+
rails_version = Rails::VERSION::STRING.split(".")[0, 2].join(".") if defined?(Rails::VERSION::STRING)
|
|
233
|
+
rails_version ||= "7.1"
|
|
234
|
+
|
|
235
|
+
<<~RB
|
|
236
|
+
# frozen_string_literal: true
|
|
237
|
+
#
|
|
238
|
+
# Drops every table created by the #{name} engine. Generated by
|
|
239
|
+
# `bin/rails generate seams:remove #{name}`. Run with:
|
|
240
|
+
#
|
|
241
|
+
# bin/rails db:migrate
|
|
242
|
+
#
|
|
243
|
+
# The if-table-exists guards make re-running safe.
|
|
244
|
+
class Drop#{name.classify}Tables < ActiveRecord::Migration[#{rails_version}]
|
|
245
|
+
def up
|
|
246
|
+
#{drops}
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def down
|
|
250
|
+
raise ActiveRecord::IrreversibleMigration,
|
|
251
|
+
"Cannot recreate #{name} engine tables — re-run " \\
|
|
252
|
+
"`bin/rails generate seams:#{name}` to recreate the engine."
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
RB
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,298 @@
|
|
|
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 Teams engine on top of the generic engine
|
|
14
|
+
# scaffold. Models cover Team, Membership, Invitation; controllers
|
|
15
|
+
# cover team CRUD + membership management + invitation send/accept.
|
|
16
|
+
#
|
|
17
|
+
# Wave 9 model: Teams is a peer to Accounts (not nested). A
|
|
18
|
+
# Teams::Membership joins Auth::Identity directly to a Teams::Team.
|
|
19
|
+
# The host-User Teamable concern is gone — Wave 9 dropped the
|
|
20
|
+
# canonical demo's host User, so there's nowhere to mix it into.
|
|
21
|
+
#
|
|
22
|
+
# Run with: bin/rails generate seams:teams
|
|
23
|
+
class TeamsGenerator < Rails::Generators::Base
|
|
24
|
+
include Seams::Generators::HostInjector
|
|
25
|
+
include Seams::Generators::EjectAware
|
|
26
|
+
|
|
27
|
+
source_root File.expand_path("templates", __dir__)
|
|
28
|
+
|
|
29
|
+
ENGINE_NAME = "teams"
|
|
30
|
+
DEFAULT_FEATURES = %w[invitations roles].freeze
|
|
31
|
+
|
|
32
|
+
class_option :with, type: :string, default: "all",
|
|
33
|
+
desc: "Comma-separated features to enable: invitations,roles (or 'all')"
|
|
34
|
+
|
|
35
|
+
def create_base_engine
|
|
36
|
+
EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def overwrite_engine_entry_point
|
|
40
|
+
# engine.rb / lib/teams.rb stay framework-managed.
|
|
41
|
+
template "lib/engine.rb.tt", engine_path("lib/teams/engine.rb"), force: true
|
|
42
|
+
template "lib/teams.rb.tt", engine_path("lib/teams.rb"), force: true
|
|
43
|
+
template_unless_ejected "lib/configuration.rb.tt",
|
|
44
|
+
engine_path("lib/teams/configuration.rb")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def overwrite_routes
|
|
48
|
+
template_unless_ejected "config/routes.rb.tt", engine_path("config/routes.rb"), force: true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def create_models
|
|
52
|
+
template_unless_ejected "app/models/application_record.rb.tt",
|
|
53
|
+
engine_path("app/models/teams/application_record.rb")
|
|
54
|
+
template_unless_ejected "app/models/team.rb.tt",
|
|
55
|
+
engine_path("app/models/teams/team.rb")
|
|
56
|
+
template_unless_ejected "app/models/membership.rb.tt",
|
|
57
|
+
engine_path("app/models/teams/membership.rb")
|
|
58
|
+
template_unless_ejected "app/models/current.rb.tt",
|
|
59
|
+
engine_path("app/models/teams/current.rb")
|
|
60
|
+
return unless features.include?("invitations")
|
|
61
|
+
|
|
62
|
+
template_unless_ejected "app/models/invitation.rb.tt",
|
|
63
|
+
engine_path("app/models/teams/invitation.rb")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def create_controllers
|
|
67
|
+
template_unless_ejected "app/controllers/teams_controller.rb.tt",
|
|
68
|
+
engine_path("app/controllers/teams/teams_controller.rb")
|
|
69
|
+
template_unless_ejected "app/controllers/memberships_controller.rb.tt",
|
|
70
|
+
engine_path("app/controllers/teams/memberships_controller.rb")
|
|
71
|
+
return unless features.include?("invitations")
|
|
72
|
+
|
|
73
|
+
template_unless_ejected "app/controllers/invitations_controller.rb.tt",
|
|
74
|
+
engine_path("app/controllers/teams/invitations_controller.rb")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Phase 4A (2/2) — bare-bones views so the engine renders out
|
|
78
|
+
# of the box. Hosts override by dropping files at
|
|
79
|
+
# app/views/teams/teams/* in their own tree.
|
|
80
|
+
def create_views
|
|
81
|
+
%w[index show new edit].each do |action|
|
|
82
|
+
template_unless_ejected "app/views/teams/#{action}.html.erb.tt",
|
|
83
|
+
engine_path("app/views/teams/teams/#{action}.html.erb")
|
|
84
|
+
end
|
|
85
|
+
template_unless_ejected "app/views/memberships/index.html.erb.tt",
|
|
86
|
+
engine_path("app/views/teams/memberships/index.html.erb")
|
|
87
|
+
return unless features.include?("invitations")
|
|
88
|
+
|
|
89
|
+
template_unless_ejected "app/views/invitations/index.html.erb.tt",
|
|
90
|
+
engine_path("app/views/teams/invitations/index.html.erb")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def create_concerns
|
|
94
|
+
# Phase 4A — account scoping helper that pairs with Core's
|
|
95
|
+
# TenantScoped. Mix into models that belong to a single team.
|
|
96
|
+
template_unless_ejected "lib/concerns/account_scoped.rb.tt",
|
|
97
|
+
engine_path("lib/teams/concerns/account_scoped.rb")
|
|
98
|
+
# `--with=roles` ships role-based controller filters.
|
|
99
|
+
return unless features.include?("roles")
|
|
100
|
+
|
|
101
|
+
template_unless_ejected "lib/concerns/authorization.rb.tt",
|
|
102
|
+
engine_path("lib/teams/concerns/authorization.rb")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def create_jobs
|
|
106
|
+
template_unless_ejected "app/jobs/application_job.rb.tt",
|
|
107
|
+
engine_path("app/jobs/teams/application_job.rb")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def create_mailer_and_subscriber
|
|
111
|
+
return unless features.include?("invitations")
|
|
112
|
+
|
|
113
|
+
template_unless_ejected "app/mailers/invitation_mailer.rb.tt",
|
|
114
|
+
engine_path("app/mailers/teams/invitation_mailer.rb")
|
|
115
|
+
template_unless_ejected "app/views/invitation_mailer/invite.text.erb.tt",
|
|
116
|
+
engine_path("app/views/teams/invitation_mailer/invite.text.erb")
|
|
117
|
+
template_unless_ejected "app/subscribers/invitation_subscriber.rb.tt",
|
|
118
|
+
engine_path("app/subscribers/teams/invitation_subscriber.rb")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def create_migrations
|
|
122
|
+
template "db/migrate/create_teams.rb.tt",
|
|
123
|
+
engine_path("db/migrate/#{timestamp(0)}_create_teams.rb")
|
|
124
|
+
template "db/migrate/create_team_memberships.rb.tt",
|
|
125
|
+
engine_path("db/migrate/#{timestamp(1)}_create_team_memberships.rb")
|
|
126
|
+
return unless features.include?("invitations")
|
|
127
|
+
|
|
128
|
+
template "db/migrate/create_team_invitations.rb.tt",
|
|
129
|
+
engine_path("db/migrate/#{timestamp(2)}_create_team_invitations.rb")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def create_specs
|
|
133
|
+
template_unless_ejected "spec/models/team_spec.rb.tt",
|
|
134
|
+
engine_path("spec/models/teams/team_spec.rb")
|
|
135
|
+
template_unless_ejected "spec/models/membership_spec.rb.tt",
|
|
136
|
+
engine_path("spec/models/teams/membership_spec.rb")
|
|
137
|
+
# Phase 4A — factories live alongside the model specs so any
|
|
138
|
+
# spec can `create(:team)` without rolling its own fixture.
|
|
139
|
+
template_unless_ejected "spec/factories/teams.rb.tt",
|
|
140
|
+
engine_path("spec/factories/teams.rb")
|
|
141
|
+
return unless features.include?("invitations")
|
|
142
|
+
|
|
143
|
+
template_unless_ejected "spec/models/invitation_spec.rb.tt",
|
|
144
|
+
engine_path("spec/models/teams/invitation_spec.rb")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def overwrite_readme
|
|
148
|
+
template "README.md.tt", engine_path("README.md"), force: true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def update_exposed_concerns
|
|
152
|
+
rubocop_path = engine_path(".rubocop.yml")
|
|
153
|
+
return unless File.exist?(rubocop_path)
|
|
154
|
+
|
|
155
|
+
contents = File.read(rubocop_path)
|
|
156
|
+
exposed_lines = [" - Teams::AccountScoped"]
|
|
157
|
+
exposed_lines << " - Teams::Authorization" if features.include?("roles")
|
|
158
|
+
replacement = " ExposedConcerns:\n#{exposed_lines.join("\n")}"
|
|
159
|
+
contents.sub!(/^ ExposedConcerns: \[\]$/, replacement)
|
|
160
|
+
File.write(rubocop_path, contents)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def create_dummy_app
|
|
164
|
+
# Wave 9: no host User model in the dummy. The dummy ships a
|
|
165
|
+
# slim Auth::Identity stub at app/models/auth/identity.rb so
|
|
166
|
+
# the teams engine's boot-time dependency assertion (defined?
|
|
167
|
+
# ::Auth::Identity) passes without pulling in the full auth
|
|
168
|
+
# engine. The same stub also lets `create(:auth_identity)`
|
|
169
|
+
# build real AR rows against the auth_identities table baked
|
|
170
|
+
# into dummy_schema.
|
|
171
|
+
Seams::Generators::DummyAppWriter.write!(
|
|
172
|
+
engine_path: File.join(destination_root, "engines", ENGINE_NAME),
|
|
173
|
+
engine_module: "Teams",
|
|
174
|
+
mount_at: "/teams",
|
|
175
|
+
schema: dummy_schema,
|
|
176
|
+
host_user: dummy_host_identity,
|
|
177
|
+
host_user_path: "app/models/auth/identity.rb"
|
|
178
|
+
)
|
|
179
|
+
template "spec/runtime/boot_spec.rb.tt",
|
|
180
|
+
engine_path("spec/runtime/teams_boot_spec.rb")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def wire_into_host
|
|
184
|
+
# factory_bot_rails powers spec/factories/teams.rb. Lives in
|
|
185
|
+
# the host's test group only.
|
|
186
|
+
host_inject_gem("factory_bot_rails", "~> 6.4", group: :test)
|
|
187
|
+
host_inject_mount(engine_class: "Teams::Engine", at: "/teams")
|
|
188
|
+
# NB: no host_inject_include_in_user — the host User is gone
|
|
189
|
+
# post-Wave-9. Hosts that DO keep a User model and want
|
|
190
|
+
# team-membership query helpers wire those up themselves.
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def report_summary
|
|
194
|
+
say ""
|
|
195
|
+
say " Teams engine generated at engines/teams/", :green
|
|
196
|
+
say ""
|
|
197
|
+
say " Next steps:", :yellow
|
|
198
|
+
say " 1. bin/rails db:migrate"
|
|
199
|
+
say " 2. Run the engine specs: bin/rails seams:test[teams]"
|
|
200
|
+
say ""
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
# Resolved feature list from --with. "all" (or empty / unrecognised)
|
|
206
|
+
# → invitations + roles. Garbage / unknown values fall back to all
|
|
207
|
+
# so the engine ships fully wired by default. Memoised so ERB
|
|
208
|
+
# branches stay consistent across the generator run.
|
|
209
|
+
def features
|
|
210
|
+
@features ||= begin
|
|
211
|
+
raw = options[:with].to_s.downcase.strip
|
|
212
|
+
if raw.empty? || raw == "all"
|
|
213
|
+
DEFAULT_FEATURES.dup
|
|
214
|
+
else
|
|
215
|
+
requested = raw.split(",").map(&:strip).reject(&:empty?)
|
|
216
|
+
allowed = requested & DEFAULT_FEATURES
|
|
217
|
+
allowed.empty? ? DEFAULT_FEATURES.dup : allowed
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def engine_path(relative)
|
|
223
|
+
File.join(destination_root, "engines", ENGINE_NAME, relative)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Offset by 300 to avoid collisions with the other canonical
|
|
227
|
+
# engines (auth +0/+1, notifications +100, billing +200/+201/+202).
|
|
228
|
+
def timestamp(offset)
|
|
229
|
+
base = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
|
230
|
+
(base + 300 + offset).to_s
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Slim Auth::Identity stub for the teams dummy app. Stands in
|
|
234
|
+
# for the real Auth::Identity (which lives in the auth engine,
|
|
235
|
+
# not loaded by the dummy) so the teams engine's boot-time
|
|
236
|
+
# cross-engine dependency assertion passes and specs that
|
|
237
|
+
# `create(:auth_identity)` get a real Active Record row backing
|
|
238
|
+
# the auth_identities table.
|
|
239
|
+
def dummy_host_identity
|
|
240
|
+
<<~RB
|
|
241
|
+
# frozen_string_literal: true
|
|
242
|
+
module Auth
|
|
243
|
+
class Identity < ApplicationRecord
|
|
244
|
+
self.table_name = "auth_identities"
|
|
245
|
+
has_secure_password
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
RB
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Includes auth_identities so factories that link memberships to
|
|
252
|
+
# an Identity can `create(:auth_identity)` against a real row.
|
|
253
|
+
# Match the auth engine's schema for that table so cross-engine
|
|
254
|
+
# specs don't drift.
|
|
255
|
+
def dummy_schema
|
|
256
|
+
<<~SCHEMA
|
|
257
|
+
create_table :auth_identities do |t|
|
|
258
|
+
t.text :email, null: false
|
|
259
|
+
t.string :password_digest, null: false
|
|
260
|
+
t.boolean :staff, null: false, default: false
|
|
261
|
+
t.timestamps
|
|
262
|
+
end
|
|
263
|
+
add_index :auth_identities, :email, unique: true
|
|
264
|
+
add_index :auth_identities, :staff, where: "staff = true"
|
|
265
|
+
|
|
266
|
+
create_table :teams do |t|
|
|
267
|
+
t.string :name, null: false
|
|
268
|
+
t.string :slug, null: false
|
|
269
|
+
t.timestamps
|
|
270
|
+
end
|
|
271
|
+
add_index :teams, :slug, unique: true
|
|
272
|
+
|
|
273
|
+
create_table :team_memberships do |t|
|
|
274
|
+
t.references :team, null: false
|
|
275
|
+
t.bigint :identity_id, null: false
|
|
276
|
+
t.string :role, null: false, default: "member"
|
|
277
|
+
t.timestamps
|
|
278
|
+
end
|
|
279
|
+
add_index :team_memberships, %i[team_id identity_id], unique: true
|
|
280
|
+
add_index :team_memberships, :identity_id
|
|
281
|
+
|
|
282
|
+
create_table :team_invitations do |t|
|
|
283
|
+
t.references :team, null: false
|
|
284
|
+
t.string :email, null: false
|
|
285
|
+
t.string :token, null: false
|
|
286
|
+
t.string :role, null: false, default: "member"
|
|
287
|
+
t.datetime :expires_at, null: false
|
|
288
|
+
t.datetime :accepted_at
|
|
289
|
+
t.timestamps
|
|
290
|
+
end
|
|
291
|
+
add_index :team_invitations, :token, unique: true
|
|
292
|
+
add_index :team_invitations, %i[team_id email], unique: true,
|
|
293
|
+
where: "accepted_at IS NULL"
|
|
294
|
+
SCHEMA
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Teams
|
|
2
|
+
|
|
3
|
+
> Multi-tenant teams + memberships + invitations for a Seams-powered host.
|
|
4
|
+
|
|
5
|
+
**Requires:** the `auth` engine. `Teams::Membership.identity_id`
|
|
6
|
+
joins to `auth_identities`. The `Teams::Authorization` concern
|
|
7
|
+
reads `Auth::Current.identity` for membership checks; without auth
|
|
8
|
+
installed, `current_identity_id` returns nil and team-admin gates
|
|
9
|
+
return 403 unconditionally. Install auth before teams.
|
|
10
|
+
|
|
11
|
+
## Model
|
|
12
|
+
|
|
13
|
+
A `Teams::Team` is a peer to `Accounts::Account` — **not** nested
|
|
14
|
+
inside one. A `Teams::Membership` is a `(team_id, identity_id, role)`
|
|
15
|
+
join row that links an `Auth::Identity` directly to a `Teams::Team`.
|
|
16
|
+
|
|
17
|
+
If your application wants "Team belongs to Account" semantics, wire
|
|
18
|
+
that yourself with a host-side migration that adds an `account_id`
|
|
19
|
+
column to `teams`. The Teams engine deliberately stays out of the
|
|
20
|
+
Account/Tenant question: hosts that don't have Accounts (e.g. a B2C
|
|
21
|
+
SaaS that uses Teams as standalone groups) work without any further
|
|
22
|
+
plumbing.
|
|
23
|
+
|
|
24
|
+
`Auth::Identity` is referenced by id only (`identity_id` is a bare
|
|
25
|
+
bigint column on `team_memberships`, no `belongs_to :identity`). The
|
|
26
|
+
Teams engine never joins to `auth_identities` at the ActiveRecord
|
|
27
|
+
level — cross-engine integrity is enforced at the application layer
|
|
28
|
+
so Teams can move to a separate database in the future.
|
|
29
|
+
|
|
30
|
+
## Events emitted
|
|
31
|
+
|
|
32
|
+
| Event name | Payload | Emitted when |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `team.created.teams` | `{ team_id:, creator_identity_id: }` | TeamsController#create succeeds |
|
|
35
|
+
| `team.member_joined.teams` | `{ team_id:, identity_id:, role: }` | MembershipsController#create succeeds |
|
|
36
|
+
| `team.member_left.teams` | `{ team_id:, identity_id: }` | MembershipsController#destroy runs |
|
|
37
|
+
| `invitation.sent.teams` | `{ invitation_id:, team_id:, email:, role:, token: }` | InvitationsController#create succeeds |
|
|
38
|
+
| `invitation.accepted.teams` | `{ team_id:, identity_id:, invitation_id: }` | InvitationsController#accept succeeds |
|
|
39
|
+
|
|
40
|
+
## Events consumed
|
|
41
|
+
|
|
42
|
+
| Event name | Subscriber | What it does |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `invitation.sent.teams` | `Teams::InvitationSubscriber` | Looks up the invitation by id and enqueues `Teams::InvitationMailer.invite(invitation_id).deliver_later`. The host overrides the email body at `app/views/teams/invitation_mailer/invite.text.erb`. |
|
|
45
|
+
|
|
46
|
+
## Exposed concerns
|
|
47
|
+
|
|
48
|
+
| Concern | Purpose |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `Teams::Authorization` | Mixed into engine controllers; provides `require_team_member!` and `require_team_admin!`. Resolves the current human via `current_identity_id`, which by default reads `Auth::Current.identity` (the Auth engine's per-request namespace). Override `current_identity_id` to plug in a different resolver. |
|
|
51
|
+
| `Teams::AccountScoped` | Mix into host models that belong to a single team. Sets up `belongs_to :team` + a `default_scope` on `Teams::Current.team`. |
|
|
52
|
+
|
|
53
|
+
> **Wave 9 note.** The `Teams::Teamable` host-User concern was removed.
|
|
54
|
+
> Wave 9 dropped the canonical demo's host User model: hosts that
|
|
55
|
+
> still maintain one are responsible for adding any
|
|
56
|
+
> `team_memberships`-keyed helper methods themselves (querying by
|
|
57
|
+
> `Teams::Membership.where(identity_id: …)`).
|
|
58
|
+
|
|
59
|
+
## Roles
|
|
60
|
+
|
|
61
|
+
| Role | Capabilities |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `owner` | Everything an admin can, plus deleting the team. |
|
|
64
|
+
| `admin` | Manage memberships and invitations. |
|
|
65
|
+
| `member` | Read-only by default. |
|
|
66
|
+
|
|
67
|
+
Teams roles are intentionally independent of Accounts roles — a Team
|
|
68
|
+
is its own RBAC unit. Hosts that want a single role across both
|
|
69
|
+
should denormalise that themselves.
|
|
70
|
+
|
|
71
|
+
The engine ships role enforcement at the model level (`Membership#admin?`).
|
|
72
|
+
Authorization in controllers is the host's responsibility — Seams gives
|
|
73
|
+
you the data and the events; opinion-free on which authz library you use.
|
|
74
|
+
|
|
75
|
+
## Mounting
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# config/routes.rb (host application)
|
|
79
|
+
Rails.application.routes.draw do
|
|
80
|
+
mount Teams::Engine, at: "/teams"
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Running the specs
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bin/rails seams:test[teams]
|
|
88
|
+
```
|