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,852 @@
|
|
|
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 Admin engine on top of the generic engine
|
|
14
|
+
# scaffold. Wave 11A — Phase 1 (foundation) + Phase 2 (dashboards).
|
|
15
|
+
# Phase 3 wires Pundit policies + the audit-log auto-write.
|
|
16
|
+
#
|
|
17
|
+
# Phase 1 (foundation) ships:
|
|
18
|
+
# - lib/admin/engine.rb that registers events, mounts under the
|
|
19
|
+
# host, and asserts Auth::Identity + Administrate are present
|
|
20
|
+
# at boot.
|
|
21
|
+
# - lib/admin.rb + lib/admin/configuration.rb exposing four
|
|
22
|
+
# configuration knobs:
|
|
23
|
+
# - authenticator (callable; default: staff? on current_identity)
|
|
24
|
+
# - tenancy_scope (:platform | :tenant; default :platform)
|
|
25
|
+
# - theme_css_path (nil; host-supplied admin restyle)
|
|
26
|
+
# - before_admin_action (callable; hook for 2FA / IP allow-list)
|
|
27
|
+
# - app/controllers/seams/admin/application_controller.rb
|
|
28
|
+
# subclassing Administrate::ApplicationController, gating via
|
|
29
|
+
# the authenticator concern.
|
|
30
|
+
# - lib/admin/concerns/authenticator.rb — the gate concern.
|
|
31
|
+
# - config/routes.rb scoped to the engine, with insertion-point
|
|
32
|
+
# markers + the twelve canonical resource declarations.
|
|
33
|
+
#
|
|
34
|
+
# Phase 2 (dashboards) ships:
|
|
35
|
+
# - app/dashboards/admin/<name>_dashboard.rb — twelve Administrate
|
|
36
|
+
# dashboards covering Identity, Account, Membership (x2),
|
|
37
|
+
# Team, TeamMembership, Invitation, Notification,
|
|
38
|
+
# NotificationPreference, Plan, Subscription, Invoice,
|
|
39
|
+
# LifetimePass. Each subclasses Administrate::BaseDashboard.
|
|
40
|
+
# - app/controllers/admin/<plural>_controller.rb — twelve
|
|
41
|
+
# thin Administrate controllers, each subclassing
|
|
42
|
+
# Seams::Admin::ApplicationController (NOT Administrate's
|
|
43
|
+
# directly) so they inherit the gate, the pundit_user hook,
|
|
44
|
+
# and Phase 3's audit-log auto-write.
|
|
45
|
+
# - Dummy app schema + slim model stubs for each of the twelve
|
|
46
|
+
# so the engine's runtime spec can boot Administrate against
|
|
47
|
+
# a real ActiveRecord schema.
|
|
48
|
+
#
|
|
49
|
+
# The engine ships NO migrations. It is read-only over existing
|
|
50
|
+
# tables; Phase 3 may revisit if the audit-log table needs an
|
|
51
|
+
# extra column.
|
|
52
|
+
#
|
|
53
|
+
# Run with: bin/rails generate seams:admin
|
|
54
|
+
# rubocop:disable Metrics/ClassLength
|
|
55
|
+
class AdminGenerator < Rails::Generators::Base
|
|
56
|
+
include Seams::Generators::HostInjector
|
|
57
|
+
include Seams::Generators::EjectAware
|
|
58
|
+
|
|
59
|
+
source_root File.expand_path("templates", __dir__)
|
|
60
|
+
|
|
61
|
+
ENGINE_NAME = "admin"
|
|
62
|
+
|
|
63
|
+
# Phase 2 dashboard catalogue. Each entry carries:
|
|
64
|
+
# - the snake-cased dashboard name (filename + dashboard class)
|
|
65
|
+
# - the full model class name (used inside the controller via
|
|
66
|
+
# `def resource_class`)
|
|
67
|
+
# - the owning engine (informational; useful for documentation
|
|
68
|
+
# and future Pundit policy splits in Phase 3)
|
|
69
|
+
#
|
|
70
|
+
# The order here is the order routes + dashboards appear in
|
|
71
|
+
# the generated files; Identity comes first so the engine root
|
|
72
|
+
# (`root to: "admin/identities#index"`) lands on something
|
|
73
|
+
# meaningful.
|
|
74
|
+
DASHBOARD_MODELS = [
|
|
75
|
+
# [dashboard_basename, model_class, owning_engine]
|
|
76
|
+
["identity", "Auth::Identity", "auth"],
|
|
77
|
+
["account", "Accounts::Account", "accounts"],
|
|
78
|
+
["accounts_membership", "Accounts::Membership", "accounts"],
|
|
79
|
+
["team", "Teams::Team", "teams"],
|
|
80
|
+
["teams_membership", "Teams::Membership", "teams"],
|
|
81
|
+
["invitation", "Teams::Invitation", "teams"],
|
|
82
|
+
["notification", "Notifications::Notification", "notifications"],
|
|
83
|
+
["notification_preference", "Notifications::NotificationPreference", "notifications"],
|
|
84
|
+
["plan", "Billing::Plan", "billing"],
|
|
85
|
+
["subscription", "Billing::Subscription", "billing"],
|
|
86
|
+
["invoice", "Billing::Invoice", "billing"],
|
|
87
|
+
["lifetime_pass", "Billing::LifetimePass", "billing"]
|
|
88
|
+
].freeze
|
|
89
|
+
|
|
90
|
+
def create_base_engine
|
|
91
|
+
EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def overwrite_engine_entry_point
|
|
95
|
+
# engine.rb / lib/admin.rb stay framework-managed.
|
|
96
|
+
template "lib/engine.rb.tt", engine_path("lib/admin/engine.rb"), force: true
|
|
97
|
+
template "lib/admin.rb.tt", engine_path("lib/admin.rb"), force: true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def overwrite_routes
|
|
101
|
+
template_unless_ejected "config/routes.rb.tt", engine_path("config/routes.rb"), force: true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The base EngineGenerator creates app/controllers/admin/ and
|
|
105
|
+
# app/models/admin/ files because it assumes a single-segment
|
|
106
|
+
# namespace (`Admin::*`). The admin engine uses the two-segment
|
|
107
|
+
# `Seams::Admin::*` namespace for its own ApplicationController,
|
|
108
|
+
# but the per-dashboard controllers + dashboards live under
|
|
109
|
+
# the single-segment `Admin::*` namespace (Administrate's
|
|
110
|
+
# convention). We delete only the leftover ApplicationController
|
|
111
|
+
# / ApplicationRecord files; Phase 2 templates re-populate
|
|
112
|
+
# app/controllers/admin/.
|
|
113
|
+
def remove_single_namespace_leftovers
|
|
114
|
+
%w[
|
|
115
|
+
app/controllers/admin/application_controller.rb
|
|
116
|
+
app/models/admin/application_record.rb
|
|
117
|
+
spec/admin_spec.rb
|
|
118
|
+
].each do |relative|
|
|
119
|
+
full = engine_path(relative)
|
|
120
|
+
next unless File.exist?(full)
|
|
121
|
+
|
|
122
|
+
FileUtils.rm(full)
|
|
123
|
+
say " remove #{relative} (single-namespace leftover)", :red
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Best-effort: clean up the now-empty parent dir for app/models/admin.
|
|
127
|
+
# (app/controllers/admin gets repopulated by Phase 2 dashboards.)
|
|
128
|
+
full = engine_path("app/models/admin")
|
|
129
|
+
return unless File.directory?(full) && Dir.empty?(full)
|
|
130
|
+
|
|
131
|
+
Dir.rmdir(full)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def create_application_controller
|
|
135
|
+
# Zeitwerk-friendly path: lives at app/controllers/seams/admin/
|
|
136
|
+
# so the constant `Seams::Admin::ApplicationController` resolves
|
|
137
|
+
# without explicit requires.
|
|
138
|
+
template_unless_ejected "app/controllers/admin/application_controller.rb.tt",
|
|
139
|
+
engine_path("app/controllers/seams/admin/application_controller.rb")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def create_configuration
|
|
143
|
+
template_unless_ejected "lib/configuration.rb.tt",
|
|
144
|
+
engine_path("lib/admin/configuration.rb")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def create_authenticator_concern
|
|
148
|
+
template_unless_ejected "lib/concerns/authenticator.rb.tt",
|
|
149
|
+
engine_path("lib/admin/concerns/authenticator.rb")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Phase 3 — Seams::Admin::Context Struct (the value pundit_user
|
|
153
|
+
# returns). Wraps the current Identity + Membership so policies
|
|
154
|
+
# can read both signals (staff? on Identity, role/account_id on
|
|
155
|
+
# Membership) without each policy reaching into the controller.
|
|
156
|
+
def create_context
|
|
157
|
+
template_unless_ejected "lib/context.rb.tt",
|
|
158
|
+
engine_path("lib/admin/context.rb")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Phase 3 — Pundit policies. Two namespaces (`Admin::Platform`
|
|
162
|
+
# and `Admin::Tenant`) selected at request time by
|
|
163
|
+
# `Seams::Admin.config.tenancy_scope`. Each namespace ships an
|
|
164
|
+
# ApplicationPolicy base + one policy per entry in
|
|
165
|
+
# DASHBOARD_MODELS. `template_unless_ejected` so a host that
|
|
166
|
+
# ejects an individual policy (e.g. to override `destroy?`)
|
|
167
|
+
# keeps their version on the next generator run.
|
|
168
|
+
POLICY_NAMESPACES = %w[platform tenant].freeze
|
|
169
|
+
private_constant :POLICY_NAMESPACES
|
|
170
|
+
|
|
171
|
+
def create_policies
|
|
172
|
+
POLICY_NAMESPACES.each do |namespace|
|
|
173
|
+
template_unless_ejected(
|
|
174
|
+
"app/policies/admin/#{namespace}/application_policy.rb.tt",
|
|
175
|
+
engine_path("app/policies/admin/#{namespace}/application_policy.rb")
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
DASHBOARD_MODELS.each do |basename, _model_class, _engine|
|
|
180
|
+
POLICY_NAMESPACES.each do |namespace|
|
|
181
|
+
template_unless_ejected(
|
|
182
|
+
"app/policies/admin/#{namespace}/#{basename}_policy.rb.tt",
|
|
183
|
+
engine_path("app/policies/admin/#{namespace}/#{basename}_policy.rb")
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Phase 2: emit one dashboard + one controller per entry in
|
|
190
|
+
# DASHBOARD_MODELS. `template_unless_ejected` so a host that
|
|
191
|
+
# ejects an individual dashboard (e.g. to restyle the Identity
|
|
192
|
+
# form) keeps their version on the next generator run.
|
|
193
|
+
def create_dashboards
|
|
194
|
+
DASHBOARD_MODELS.each do |basename, _model_class, _engine|
|
|
195
|
+
template_unless_ejected(
|
|
196
|
+
"app/dashboards/admin/#{basename}_dashboard.rb.tt",
|
|
197
|
+
engine_path("app/dashboards/admin/#{basename}_dashboard.rb")
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
template_unless_ejected(
|
|
201
|
+
"app/controllers/admin/#{basename.pluralize}_controller.rb.tt",
|
|
202
|
+
engine_path("app/controllers/admin/#{basename.pluralize}_controller.rb")
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def create_dummy_app
|
|
208
|
+
# Phase 2 dashboards target every canonical seams model, so
|
|
209
|
+
# the dummy schema covers every table they read. Slim model
|
|
210
|
+
# stubs ship alongside so Administrate's Zeitwerk-driven class
|
|
211
|
+
# resolution finds an `Auth::Identity` / `Billing::Plan` /
|
|
212
|
+
# etc. constant when the dashboard class loads.
|
|
213
|
+
Seams::Generators::DummyAppWriter.write!(
|
|
214
|
+
engine_path: File.join(destination_root, "engines", ENGINE_NAME),
|
|
215
|
+
engine_module: "Admin",
|
|
216
|
+
mount_at: "/admin",
|
|
217
|
+
schema: dummy_schema,
|
|
218
|
+
host_user: dummy_host_identity,
|
|
219
|
+
host_user_path: "app/models/auth/identity.rb"
|
|
220
|
+
)
|
|
221
|
+
write_auth_current_stub
|
|
222
|
+
write_accounts_current_stub
|
|
223
|
+
write_dummy_model_stubs
|
|
224
|
+
amend_dummy_application_rb
|
|
225
|
+
rewrite_dummy_routes_for_namespaced_engine
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# DummyAppWriter renders `mount Admin::Engine, at: "/admin"` from
|
|
229
|
+
# the `engine_module: "Admin"` argument — but our engine lives at
|
|
230
|
+
# `Seams::Admin::Engine` (two-namespace, matching the gem layout).
|
|
231
|
+
# We can't pass `engine_module: "Seams::Admin"` because DummyAppWriter
|
|
232
|
+
# also uses that value to `require "<downcase>"` the engine's lib
|
|
233
|
+
# entry point, and `require "seams::admin"` isn't a thing. Cheapest
|
|
234
|
+
# fix: rewrite the dummy's routes.rb after DummyAppWriter writes it.
|
|
235
|
+
def rewrite_dummy_routes_for_namespaced_engine
|
|
236
|
+
path = File.join(destination_root, "engines", ENGINE_NAME, "spec/dummy/config/routes.rb")
|
|
237
|
+
return unless File.exist?(path)
|
|
238
|
+
|
|
239
|
+
contents = File.read(path)
|
|
240
|
+
File.write(path, contents.sub("mount Admin::Engine", "mount Seams::Admin::Engine"))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def append_administrate_to_engine_gemfile
|
|
244
|
+
# The engine's standalone Gemfile (engine_path("Gemfile"))
|
|
245
|
+
# gets `administrate` appended so that running engine specs
|
|
246
|
+
# in isolation (cd engines/admin && bundle exec rspec) can
|
|
247
|
+
# require the gem. The host-side Gemfile is updated separately
|
|
248
|
+
# by `wire_into_host`.
|
|
249
|
+
gemfile = engine_path("Gemfile")
|
|
250
|
+
return unless File.exist?(gemfile)
|
|
251
|
+
|
|
252
|
+
contents = File.read(gemfile)
|
|
253
|
+
return if contents.include?('gem "administrate"')
|
|
254
|
+
|
|
255
|
+
File.write(gemfile, contents.rstrip + <<~RB)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# Phase 2 dashboards subclass Administrate::BaseDashboard; the
|
|
259
|
+
# gem must be available when the engine runs its own specs.
|
|
260
|
+
gem "administrate", "~> 1.0"
|
|
261
|
+
RB
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Phase 3 — append `pundit` to the engine's standalone Gemfile so
|
|
265
|
+
# `cd engines/admin && bundle exec rspec` can require it. The
|
|
266
|
+
# host-side Gemfile already gets pundit via `wire_into_host`
|
|
267
|
+
# (added in Phase 2 ahead of Phase 3 wiring).
|
|
268
|
+
def append_pundit_to_engine_gemfile
|
|
269
|
+
gemfile = engine_path("Gemfile")
|
|
270
|
+
return unless File.exist?(gemfile)
|
|
271
|
+
|
|
272
|
+
contents = File.read(gemfile)
|
|
273
|
+
return if contents.include?('gem "pundit"')
|
|
274
|
+
|
|
275
|
+
File.write(gemfile, contents.rstrip + <<~RB)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Phase 3 ApplicationController includes Pundit::Authorization
|
|
279
|
+
# and the per-model policies live under Admin::Platform::*
|
|
280
|
+
# and Admin::Tenant::*. The gem must be available when the
|
|
281
|
+
# engine runs its own specs.
|
|
282
|
+
gem "pundit", "~> 2.4"
|
|
283
|
+
RB
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def create_factories
|
|
287
|
+
template_unless_ejected "spec/factories/admin.rb.tt",
|
|
288
|
+
engine_path("spec/factories/admin.rb")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def create_unit_specs
|
|
292
|
+
# Per-dashboard specs are deferred to Phase 3 (where the
|
|
293
|
+
# Pundit policies introduce real behaviour worth covering).
|
|
294
|
+
# Phase 2's coverage lives in the runtime boot spec.
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def create_runtime_specs
|
|
298
|
+
template "spec/runtime/admin_boot_spec.rb.tt",
|
|
299
|
+
engine_path("spec/runtime/admin_boot_spec.rb")
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def overwrite_readme
|
|
303
|
+
template "README.md.tt", engine_path("README.md"), force: true
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def wire_into_host
|
|
307
|
+
# Administrate is the dashboard framework; Pundit is the
|
|
308
|
+
# authorisation layer Phase 3 will wire policies through.
|
|
309
|
+
# Both go into the host's main bundle (admin runs in-app, not
|
|
310
|
+
# a separate process).
|
|
311
|
+
host_inject_gem("administrate", "~> 1.0")
|
|
312
|
+
host_inject_gem("pundit", "~> 2.4")
|
|
313
|
+
host_inject_mount(engine_class: "Seams::Admin::Engine", at: "/admin")
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def report_summary
|
|
317
|
+
say report_summary_text, :green
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def report_summary_text
|
|
321
|
+
<<~TXT
|
|
322
|
+
|
|
323
|
+
Admin engine generated at engines/admin/
|
|
324
|
+
|
|
325
|
+
Next steps:
|
|
326
|
+
1. bundle install
|
|
327
|
+
(picks up administrate + pundit, both injected into the host Gemfile)
|
|
328
|
+
|
|
329
|
+
2. Promote yourself to a platform admin:
|
|
330
|
+
bin/rails runner 'Auth::Identity.find_by(email: "you@example.com").update!(staff: true)'
|
|
331
|
+
No migration is needed — admin is read-only over the
|
|
332
|
+
existing seams tables, and `staff` already lives on
|
|
333
|
+
auth_identities (Wave 9).
|
|
334
|
+
|
|
335
|
+
3. Boot the host:
|
|
336
|
+
bin/rails server
|
|
337
|
+
|
|
338
|
+
4. Visit /admin in your browser. You should land on the
|
|
339
|
+
Identities index with sidebar entries for all twelve
|
|
340
|
+
canonical seams models.
|
|
341
|
+
|
|
342
|
+
Tenancy modes:
|
|
343
|
+
- :platform (default) — admins see every Account's data.
|
|
344
|
+
Gate: Auth::Identity#staff?.
|
|
345
|
+
- :tenant — admins see only their own Account's data.
|
|
346
|
+
Gate: Accounts::Membership#role == "admin".
|
|
347
|
+
Switch via config/initializers/seams_admin.rb:
|
|
348
|
+
Seams::Admin.configure { |c| c.tenancy_scope = :tenant }
|
|
349
|
+
|
|
350
|
+
Customise a dashboard:
|
|
351
|
+
bin/seams resolve --eject admin/app/dashboards/admin/identity_dashboard.rb
|
|
352
|
+
# Edit your local copy; future `bin/seams admin` runs leave it alone.
|
|
353
|
+
|
|
354
|
+
Audit log:
|
|
355
|
+
Every successful create/update/destroy writes a Core::AuditLog
|
|
356
|
+
row keyed on Auth::Current.identity. No-ops cleanly if the
|
|
357
|
+
core engine isn't installed.
|
|
358
|
+
|
|
359
|
+
Run the engine specs:
|
|
360
|
+
bin/rails seams:test[admin]
|
|
361
|
+
|
|
362
|
+
See engines/admin/README.md for the full configuration
|
|
363
|
+
reference and the four config knobs (authenticator,
|
|
364
|
+
tenancy_scope, theme_css_path, before_admin_action).
|
|
365
|
+
|
|
366
|
+
TXT
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
private
|
|
370
|
+
|
|
371
|
+
def engine_path(relative)
|
|
372
|
+
File.join(destination_root, "engines", ENGINE_NAME, relative)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Slim Auth::Current stub for the dummy app. Stands in for the
|
|
376
|
+
# real Auth::Current (which lives in the auth engine, not loaded
|
|
377
|
+
# by the dummy) so the admin boot spec can wire
|
|
378
|
+
# `Current.identity = identity` against the same surface area
|
|
379
|
+
# the canonical seams host uses.
|
|
380
|
+
def write_auth_current_stub
|
|
381
|
+
path = File.join(destination_root, "engines", ENGINE_NAME,
|
|
382
|
+
"spec/dummy/app/models/auth/current.rb")
|
|
383
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
384
|
+
File.write(path, <<~RB)
|
|
385
|
+
# frozen_string_literal: true
|
|
386
|
+
module Auth
|
|
387
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
388
|
+
attribute :identity
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
RB
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Phase 3 — Accounts::Current stub for the dummy app. Stands in
|
|
395
|
+
# for the real Accounts::Current (Wave 9's CurrentAttributes
|
|
396
|
+
# object) so the admin boot spec can wire
|
|
397
|
+
# `Accounts::Current.membership = membership` against the same
|
|
398
|
+
# surface area the canonical seams host uses.
|
|
399
|
+
#
|
|
400
|
+
# The admin engine's `pundit_user` calls
|
|
401
|
+
# `Accounts::Current.membership` to read the active membership
|
|
402
|
+
# for tenant-mode policy decisions. Without this stub the dummy
|
|
403
|
+
# app would NameError on first request.
|
|
404
|
+
def write_accounts_current_stub
|
|
405
|
+
path = File.join(destination_root, "engines", ENGINE_NAME,
|
|
406
|
+
"spec/dummy/app/models/accounts/current.rb")
|
|
407
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
408
|
+
File.write(path, <<~RB)
|
|
409
|
+
# frozen_string_literal: true
|
|
410
|
+
module Accounts
|
|
411
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
412
|
+
attribute :membership
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
RB
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Phase 2: every dashboard targets a model in another engine
|
|
419
|
+
# (Auth::Identity, Accounts::Account, Billing::Plan, etc.).
|
|
420
|
+
# The dummy app doesn't load those engines, so we ship a slim
|
|
421
|
+
# ApplicationRecord stub per model — just enough that
|
|
422
|
+
# Administrate's Zeitwerk-driven dashboard lookup can resolve
|
|
423
|
+
# the constant and inspect the columns. Each stub is
|
|
424
|
+
# `class Foo < ApplicationRecord; self.table_name = "..."; end`
|
|
425
|
+
# — no associations, no validations, no callbacks. The dummy
|
|
426
|
+
# schema (see #dummy_schema) ships the matching tables.
|
|
427
|
+
def write_dummy_model_stubs
|
|
428
|
+
DUMMY_MODEL_STUBS.each do |relative_path, body|
|
|
429
|
+
full = File.join(destination_root, "engines", ENGINE_NAME,
|
|
430
|
+
"spec/dummy/app/models", relative_path)
|
|
431
|
+
FileUtils.mkdir_p(File.dirname(full))
|
|
432
|
+
File.write(full, body)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Append explicit `require_relative` lines to the dummy
|
|
437
|
+
# application.rb so the model stubs load at boot. Zeitwerk is
|
|
438
|
+
# configured eager_load: false in the dummy (see
|
|
439
|
+
# DummyAppWriter); explicit requires guarantee the constants
|
|
440
|
+
# exist before the runtime spec asks Administrate to look up a
|
|
441
|
+
# dashboard's resource_class.
|
|
442
|
+
def amend_dummy_application_rb
|
|
443
|
+
path = File.join(destination_root, "engines", ENGINE_NAME,
|
|
444
|
+
"spec/dummy/config/application.rb")
|
|
445
|
+
return unless File.exist?(path)
|
|
446
|
+
|
|
447
|
+
contents = File.read(path)
|
|
448
|
+
marker = "# seams:admin dummy stubs"
|
|
449
|
+
return if contents.include?(marker)
|
|
450
|
+
|
|
451
|
+
require_block = <<~RB
|
|
452
|
+
|
|
453
|
+
#{marker}
|
|
454
|
+
# Phase 2 — explicitly require the model stubs the dummy
|
|
455
|
+
# ships for the admin engine's dashboards. Zeitwerk would
|
|
456
|
+
# find them by autoload, but eager_load is off in the dummy
|
|
457
|
+
# and the runtime spec asks Administrate to inspect each
|
|
458
|
+
# dashboard's resource_class at boot — explicit requires
|
|
459
|
+
# guarantee the constants are defined before Administrate
|
|
460
|
+
# introspects them.
|
|
461
|
+
# Load order matters: the require loop runs during application.rb
|
|
462
|
+
# evaluation, BEFORE Zeitwerk is set up, so abstract parents have
|
|
463
|
+
# to be present in the const table by the time a child's
|
|
464
|
+
# `class Foo < ApplicationRecord` line evaluates. Three priority
|
|
465
|
+
# tiers: top-level `app/models/application_record.rb` first, then
|
|
466
|
+
# every `<namespace>/application_record.rb`, then everything else
|
|
467
|
+
# alphabetical.
|
|
468
|
+
stubs = Dir[File.expand_path("../app/models/**/*.rb", __dir__)]
|
|
469
|
+
stubs.sort_by! do |path|
|
|
470
|
+
relative = path.sub(%r{\\A.*?/app/models/}, "")
|
|
471
|
+
tier =
|
|
472
|
+
if relative == "application_record.rb" then 0
|
|
473
|
+
elsif File.basename(path) == "application_record.rb" then 1
|
|
474
|
+
else 2
|
|
475
|
+
end
|
|
476
|
+
[tier, relative]
|
|
477
|
+
end
|
|
478
|
+
stubs.each { |stub| require stub }
|
|
479
|
+
RB
|
|
480
|
+
|
|
481
|
+
File.write(path, "#{contents.rstrip}\n#{require_block}")
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Slim model stubs for each canonical engine model. Each stub is
|
|
485
|
+
# `class Foo < ApplicationRecord; self.table_name = "..."; end` —
|
|
486
|
+
# no associations, no validations, no callbacks. Just enough that
|
|
487
|
+
# Administrate's Zeitwerk-driven dashboard lookup can resolve the
|
|
488
|
+
# constant and inspect the columns. The dummy schema (see
|
|
489
|
+
# #dummy_schema) ships the matching tables.
|
|
490
|
+
DUMMY_MODEL_STUBS = {
|
|
491
|
+
"core/application_record.rb" => <<~RB,
|
|
492
|
+
# frozen_string_literal: true
|
|
493
|
+
module Core
|
|
494
|
+
class ApplicationRecord < ::ApplicationRecord
|
|
495
|
+
self.abstract_class = true
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
RB
|
|
499
|
+
"core/audit_log.rb" => <<~RB,
|
|
500
|
+
# frozen_string_literal: true
|
|
501
|
+
module Core
|
|
502
|
+
# Slim AuditLog stub for the dummy app. Mirrors the
|
|
503
|
+
# canonical seams Core::AuditLog (lives in the core engine,
|
|
504
|
+
# not loaded by the dummy). The admin engine's
|
|
505
|
+
# `record_admin_audit` after_action writes rows here when
|
|
506
|
+
# the constant is defined; without this stub the runtime
|
|
507
|
+
# spec couldn't exercise the audit-log path at all.
|
|
508
|
+
class AuditLog < ApplicationRecord
|
|
509
|
+
self.table_name = "core_audit_logs"
|
|
510
|
+
ACTIONS = %w[create update destroy].freeze
|
|
511
|
+
validates :action, inclusion: { in: ACTIONS }
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
RB
|
|
515
|
+
"accounts/application_record.rb" => <<~RB,
|
|
516
|
+
# frozen_string_literal: true
|
|
517
|
+
module Accounts
|
|
518
|
+
class ApplicationRecord < ::ApplicationRecord
|
|
519
|
+
self.abstract_class = true
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
RB
|
|
523
|
+
"accounts/account.rb" => <<~RB,
|
|
524
|
+
# frozen_string_literal: true
|
|
525
|
+
module Accounts
|
|
526
|
+
class Account < ApplicationRecord
|
|
527
|
+
self.table_name = "accounts"
|
|
528
|
+
self.primary_key = "id"
|
|
529
|
+
has_many :memberships, class_name: "Accounts::Membership",
|
|
530
|
+
foreign_key: :account_id, dependent: :destroy
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
RB
|
|
534
|
+
"accounts/membership.rb" => <<~RB,
|
|
535
|
+
# frozen_string_literal: true
|
|
536
|
+
module Accounts
|
|
537
|
+
class Membership < ApplicationRecord
|
|
538
|
+
self.table_name = "accounts_memberships"
|
|
539
|
+
self.primary_key = "id"
|
|
540
|
+
belongs_to :account, class_name: "Accounts::Account"
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
RB
|
|
544
|
+
"teams/application_record.rb" => <<~RB,
|
|
545
|
+
# frozen_string_literal: true
|
|
546
|
+
module Teams
|
|
547
|
+
class ApplicationRecord < ::ApplicationRecord
|
|
548
|
+
self.abstract_class = true
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
RB
|
|
552
|
+
"teams/team.rb" => <<~RB,
|
|
553
|
+
# frozen_string_literal: true
|
|
554
|
+
module Teams
|
|
555
|
+
class Team < ApplicationRecord
|
|
556
|
+
self.table_name = "teams"
|
|
557
|
+
has_many :memberships, class_name: "Teams::Membership",
|
|
558
|
+
foreign_key: :team_id, dependent: :destroy
|
|
559
|
+
has_many :invitations, class_name: "Teams::Invitation", dependent: :destroy
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
RB
|
|
563
|
+
"teams/membership.rb" => <<~RB,
|
|
564
|
+
# frozen_string_literal: true
|
|
565
|
+
module Teams
|
|
566
|
+
class Membership < ApplicationRecord
|
|
567
|
+
self.table_name = "team_memberships"
|
|
568
|
+
belongs_to :team, class_name: "Teams::Team"
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
RB
|
|
572
|
+
"teams/invitation.rb" => <<~RB,
|
|
573
|
+
# frozen_string_literal: true
|
|
574
|
+
module Teams
|
|
575
|
+
class Invitation < ApplicationRecord
|
|
576
|
+
self.table_name = "team_invitations"
|
|
577
|
+
belongs_to :team, class_name: "Teams::Team"
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
RB
|
|
581
|
+
"notifications/application_record.rb" => <<~RB,
|
|
582
|
+
# frozen_string_literal: true
|
|
583
|
+
module Notifications
|
|
584
|
+
class ApplicationRecord < ::ApplicationRecord
|
|
585
|
+
self.abstract_class = true
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
RB
|
|
589
|
+
"notifications/notification.rb" => <<~RB,
|
|
590
|
+
# frozen_string_literal: true
|
|
591
|
+
module Notifications
|
|
592
|
+
class Notification < ApplicationRecord
|
|
593
|
+
self.table_name = "notifications"
|
|
594
|
+
belongs_to :owner, polymorphic: true
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
RB
|
|
598
|
+
"notifications/notification_preference.rb" => <<~RB,
|
|
599
|
+
# frozen_string_literal: true
|
|
600
|
+
module Notifications
|
|
601
|
+
class NotificationPreference < ApplicationRecord
|
|
602
|
+
self.table_name = "notification_preferences"
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
RB
|
|
606
|
+
"billing/application_record.rb" => <<~RB,
|
|
607
|
+
# frozen_string_literal: true
|
|
608
|
+
module Billing
|
|
609
|
+
class ApplicationRecord < ::ApplicationRecord
|
|
610
|
+
self.abstract_class = true
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
RB
|
|
614
|
+
"billing/plan.rb" => <<~RB,
|
|
615
|
+
# frozen_string_literal: true
|
|
616
|
+
module Billing
|
|
617
|
+
class Plan < ApplicationRecord
|
|
618
|
+
self.table_name = "billing_plans"
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
RB
|
|
622
|
+
"billing/subscription.rb" => <<~RB,
|
|
623
|
+
# frozen_string_literal: true
|
|
624
|
+
module Billing
|
|
625
|
+
class Subscription < ApplicationRecord
|
|
626
|
+
self.table_name = "billing_subscriptions"
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
RB
|
|
630
|
+
"billing/invoice.rb" => <<~RB,
|
|
631
|
+
# frozen_string_literal: true
|
|
632
|
+
module Billing
|
|
633
|
+
class Invoice < ApplicationRecord
|
|
634
|
+
self.table_name = "billing_invoices"
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
RB
|
|
638
|
+
"billing/lifetime_pass.rb" => <<~RB
|
|
639
|
+
# frozen_string_literal: true
|
|
640
|
+
module Billing
|
|
641
|
+
class LifetimePass < ApplicationRecord
|
|
642
|
+
self.table_name = "billing_lifetime_passes"
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
RB
|
|
646
|
+
}.freeze
|
|
647
|
+
private_constant :DUMMY_MODEL_STUBS
|
|
648
|
+
|
|
649
|
+
# Slim Auth::Identity stub for the dummy app — mirrors the
|
|
650
|
+
# canonical seams Auth::Identity (auth/templates/app/models/
|
|
651
|
+
# identity.rb.tt). The canonical Identity does NOT declare
|
|
652
|
+
# `has_many :memberships` (the auth engine deliberately stays
|
|
653
|
+
# credential-only and avoids reaching into accounts), so the
|
|
654
|
+
# dummy doesn't either. Phase 4 rewrote the tenant
|
|
655
|
+
# IdentityPolicy to use a subquery on `accounts_memberships`
|
|
656
|
+
# instead of an association join, so the policy works against
|
|
657
|
+
# this exact shape — and the runtime spec exercises the same
|
|
658
|
+
# path real seams hosts will hit.
|
|
659
|
+
def dummy_host_identity
|
|
660
|
+
<<~RB
|
|
661
|
+
# frozen_string_literal: true
|
|
662
|
+
module Auth
|
|
663
|
+
class Identity < ApplicationRecord
|
|
664
|
+
self.table_name = "auth_identities"
|
|
665
|
+
has_secure_password
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
RB
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Schema covers every table the Phase 2 dashboards target.
|
|
672
|
+
# Mirrors the canonical engine migrations:
|
|
673
|
+
# - auth_identities (auth engine)
|
|
674
|
+
# - accounts, accounts_memberships (accounts engine)
|
|
675
|
+
# - teams, team_memberships, team_invitations (teams engine)
|
|
676
|
+
# - notifications, notification_preferences,
|
|
677
|
+
# notification_deliveries (notifications engine)
|
|
678
|
+
# - billing_plans, billing_subscriptions, billing_invoices,
|
|
679
|
+
# billing_lifetime_passes, billing_webhook_events (billing)
|
|
680
|
+
# - core_audit_logs (core engine; Phase 3 reads it)
|
|
681
|
+
def dummy_schema
|
|
682
|
+
<<~SCHEMA
|
|
683
|
+
enable_extension "pgcrypto"
|
|
684
|
+
|
|
685
|
+
create_table :auth_identities do |t|
|
|
686
|
+
t.text :email, null: false
|
|
687
|
+
t.string :password_digest, null: false
|
|
688
|
+
t.boolean :staff, null: false, default: false
|
|
689
|
+
t.timestamps
|
|
690
|
+
end
|
|
691
|
+
add_index :auth_identities, :email, unique: true
|
|
692
|
+
add_index :auth_identities, :staff, where: "staff = true"
|
|
693
|
+
|
|
694
|
+
create_table :accounts, id: :uuid do |t|
|
|
695
|
+
t.string :name, null: false
|
|
696
|
+
t.bigint :external_account_id, null: false
|
|
697
|
+
t.datetime :cancelled_at
|
|
698
|
+
t.datetime :incinerated_at
|
|
699
|
+
t.timestamps
|
|
700
|
+
end
|
|
701
|
+
add_index :accounts, :external_account_id, unique: true
|
|
702
|
+
add_index :accounts, :cancelled_at
|
|
703
|
+
|
|
704
|
+
create_table :accounts_memberships, id: :uuid do |t|
|
|
705
|
+
t.references :account, type: :uuid, null: false,
|
|
706
|
+
foreign_key: { to_table: :accounts }, index: false
|
|
707
|
+
t.bigint :identity_id, null: true
|
|
708
|
+
t.string :name, null: false
|
|
709
|
+
t.string :role, null: false, default: "member"
|
|
710
|
+
t.boolean :active, null: false, default: true
|
|
711
|
+
t.datetime :verified_at
|
|
712
|
+
t.timestamps
|
|
713
|
+
end
|
|
714
|
+
add_index :accounts_memberships, %i[account_id identity_id], unique: true,
|
|
715
|
+
name: "index_accounts_memberships_unique"
|
|
716
|
+
add_index :accounts_memberships, %i[account_id role]
|
|
717
|
+
|
|
718
|
+
create_table :teams do |t|
|
|
719
|
+
t.string :name, null: false
|
|
720
|
+
t.string :slug, null: false
|
|
721
|
+
t.timestamps
|
|
722
|
+
end
|
|
723
|
+
add_index :teams, :slug, unique: true
|
|
724
|
+
|
|
725
|
+
create_table :team_memberships do |t|
|
|
726
|
+
t.references :team, null: false, foreign_key: { to_table: :teams }, index: true
|
|
727
|
+
t.bigint :identity_id, null: false
|
|
728
|
+
t.string :role, null: false, default: "member"
|
|
729
|
+
t.timestamps
|
|
730
|
+
end
|
|
731
|
+
add_index :team_memberships, %i[team_id identity_id], unique: true
|
|
732
|
+
add_index :team_memberships, :identity_id
|
|
733
|
+
|
|
734
|
+
create_table :team_invitations do |t|
|
|
735
|
+
t.references :team, null: false, foreign_key: { to_table: :teams }, index: true
|
|
736
|
+
t.string :email, null: false
|
|
737
|
+
t.string :token, null: false
|
|
738
|
+
t.string :role, null: false, default: "member"
|
|
739
|
+
t.datetime :expires_at, null: false
|
|
740
|
+
t.datetime :accepted_at
|
|
741
|
+
t.timestamps
|
|
742
|
+
end
|
|
743
|
+
add_index :team_invitations, :token, unique: true
|
|
744
|
+
|
|
745
|
+
create_table :notifications do |t|
|
|
746
|
+
t.string :type, null: false
|
|
747
|
+
t.string :owner_type, null: false
|
|
748
|
+
t.string :owner_id, null: false
|
|
749
|
+
t.string :recipient
|
|
750
|
+
t.string :template, null: false
|
|
751
|
+
t.jsonb :schedule_data
|
|
752
|
+
t.datetime :next_delivery_at
|
|
753
|
+
t.datetime :read_at
|
|
754
|
+
t.timestamps
|
|
755
|
+
end
|
|
756
|
+
add_index :notifications, %i[owner_type owner_id]
|
|
757
|
+
add_index :notifications, :next_delivery_at
|
|
758
|
+
|
|
759
|
+
create_table :notification_preferences do |t|
|
|
760
|
+
t.bigint :identity_id, null: false
|
|
761
|
+
t.string :channel, null: false
|
|
762
|
+
t.string :notification_type
|
|
763
|
+
t.boolean :enabled, null: false, default: true
|
|
764
|
+
t.timestamps
|
|
765
|
+
end
|
|
766
|
+
add_index :notification_preferences, %i[identity_id channel notification_type], unique: true,
|
|
767
|
+
name: "index_notification_prefs_unique"
|
|
768
|
+
|
|
769
|
+
create_table :notification_deliveries do |t|
|
|
770
|
+
t.references :notification, null: false, foreign_key: true, index: true
|
|
771
|
+
t.datetime :sent_at, null: false
|
|
772
|
+
t.timestamps
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
create_table :billing_plans do |t|
|
|
776
|
+
t.string :gateway_ref, null: false
|
|
777
|
+
t.string :name, null: false
|
|
778
|
+
t.text :description
|
|
779
|
+
t.integer :amount_cents, null: false, default: 0
|
|
780
|
+
t.string :currency, null: false, default: "usd"
|
|
781
|
+
t.string :interval, null: false, default: "month"
|
|
782
|
+
t.integer :trial_period_days
|
|
783
|
+
t.boolean :active, null: false, default: true
|
|
784
|
+
t.jsonb :features, null: false, default: {}
|
|
785
|
+
t.integer :max_lifetime_units
|
|
786
|
+
t.timestamps
|
|
787
|
+
end
|
|
788
|
+
add_index :billing_plans, :gateway_ref, unique: true
|
|
789
|
+
|
|
790
|
+
create_table :billing_subscriptions do |t|
|
|
791
|
+
t.uuid :account_id, null: false
|
|
792
|
+
t.string :customer_ref, null: false
|
|
793
|
+
t.string :plan_ref, null: false
|
|
794
|
+
t.string :gateway_ref, null: false
|
|
795
|
+
t.string :status, null: false, default: "incomplete"
|
|
796
|
+
t.datetime :current_period_end
|
|
797
|
+
t.timestamps
|
|
798
|
+
end
|
|
799
|
+
add_index :billing_subscriptions, :gateway_ref, unique: true
|
|
800
|
+
|
|
801
|
+
create_table :billing_invoices do |t|
|
|
802
|
+
t.uuid :account_id, null: false
|
|
803
|
+
t.string :gateway_ref, null: false
|
|
804
|
+
t.string :customer_ref, null: false
|
|
805
|
+
t.string :subscription_ref
|
|
806
|
+
t.integer :amount_cents, null: false
|
|
807
|
+
t.string :currency, null: false, default: "USD"
|
|
808
|
+
t.string :status, null: false, default: "open"
|
|
809
|
+
t.datetime :paid_at
|
|
810
|
+
t.timestamps
|
|
811
|
+
end
|
|
812
|
+
add_index :billing_invoices, :gateway_ref, unique: true
|
|
813
|
+
|
|
814
|
+
create_table :billing_lifetime_passes do |t|
|
|
815
|
+
t.uuid :account_id, null: false
|
|
816
|
+
t.string :customer_ref, null: false
|
|
817
|
+
t.string :plan_ref, null: false
|
|
818
|
+
t.string :gateway_ref
|
|
819
|
+
t.bigint :granted_by_identity_id
|
|
820
|
+
t.datetime :granted_at, null: false
|
|
821
|
+
t.datetime :revoked_at
|
|
822
|
+
t.bigint :revoked_by_identity_id
|
|
823
|
+
t.text :notes
|
|
824
|
+
t.timestamps
|
|
825
|
+
end
|
|
826
|
+
add_index :billing_lifetime_passes, %i[account_id plan_ref], unique: true,
|
|
827
|
+
name: "index_billing_ltd_unique"
|
|
828
|
+
|
|
829
|
+
create_table :billing_webhook_events do |t|
|
|
830
|
+
t.string :gateway, null: false
|
|
831
|
+
t.string :gateway_event_id, null: false
|
|
832
|
+
t.string :event_type, null: false
|
|
833
|
+
t.boolean :livemode, null: false, default: false
|
|
834
|
+
t.timestamps
|
|
835
|
+
end
|
|
836
|
+
add_index :billing_webhook_events, %i[gateway gateway_event_id], unique: true
|
|
837
|
+
|
|
838
|
+
create_table :core_audit_logs do |t|
|
|
839
|
+
t.string :action, null: false
|
|
840
|
+
t.string :auditable_type
|
|
841
|
+
t.string :auditable_id
|
|
842
|
+
t.bigint :actor_id
|
|
843
|
+
t.jsonb :payload, null: false, default: {}
|
|
844
|
+
t.timestamps
|
|
845
|
+
end
|
|
846
|
+
add_index :core_audit_logs, %i[auditable_type auditable_id]
|
|
847
|
+
SCHEMA
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
# rubocop:enable Metrics/ClassLength
|
|
851
|
+
end
|
|
852
|
+
end
|