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.
Files changed (414) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +335 -0
  3. data/LICENSE +21 -0
  4. data/README.md +104 -0
  5. data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
  6. data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
  7. data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
  8. data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
  9. data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
  10. data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
  11. data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
  12. data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
  13. data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
  14. data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
  15. data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
  16. data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
  17. data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
  18. data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
  19. data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
  20. data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
  21. data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
  22. data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
  23. data/lib/generators/seams/admin/admin_generator.rb +852 -0
  24. data/lib/generators/seams/admin/templates/README.md.tt +266 -0
  25. data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
  26. data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
  27. data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
  28. data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
  29. data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
  30. data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
  31. data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
  32. data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
  33. data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
  34. data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
  35. data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
  36. data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
  37. data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
  38. data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
  39. data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
  40. data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
  41. data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
  42. data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
  43. data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
  44. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
  45. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
  46. data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
  47. data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
  48. data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
  49. data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
  50. data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
  51. data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
  52. data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
  53. data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
  54. data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
  55. data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
  56. data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
  57. data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
  58. data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
  59. data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
  60. data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
  61. data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
  62. data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
  63. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
  64. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
  65. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
  66. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
  67. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
  68. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
  69. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
  70. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
  71. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
  72. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
  73. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
  74. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
  75. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
  76. data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
  77. data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
  78. data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
  79. data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
  80. data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
  81. data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
  82. data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
  83. data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
  84. data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
  85. data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
  86. data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
  87. data/lib/generators/seams/auth/auth_generator.rb +311 -0
  88. data/lib/generators/seams/auth/templates/README.md.tt +289 -0
  89. data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
  90. data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
  91. data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
  92. data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
  93. data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
  94. data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
  95. data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
  96. data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
  97. data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
  98. data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
  99. data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
  100. data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
  101. data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
  102. data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
  103. data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
  104. data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
  105. data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
  106. data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
  107. data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
  108. data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
  109. data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
  110. data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
  111. data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
  112. data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
  113. data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
  114. data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
  115. data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
  116. data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
  117. data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
  118. data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
  119. data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
  120. data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
  121. data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
  122. data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
  123. data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
  124. data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
  125. data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
  126. data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
  127. data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
  128. data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
  129. data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
  130. data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
  131. data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
  132. data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
  133. data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
  134. data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
  135. data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
  136. data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
  137. data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
  138. data/lib/generators/seams/billing/billing_generator.rb +476 -0
  139. data/lib/generators/seams/billing/templates/README.md.tt +355 -0
  140. data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
  141. data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
  142. data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
  143. data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
  144. data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
  145. data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
  146. data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
  147. data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
  148. data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
  149. data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
  150. data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
  151. data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
  152. data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
  153. data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
  154. data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
  155. data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
  156. data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
  157. data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
  158. data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
  159. data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
  160. data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
  161. data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
  162. data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
  163. data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
  164. data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
  165. data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
  166. data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
  167. data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
  168. data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
  169. data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
  170. data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
  171. data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
  172. data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
  173. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
  174. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
  175. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
  176. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
  177. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
  178. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
  179. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
  180. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
  181. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
  182. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
  183. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
  184. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
  185. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
  186. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
  187. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
  188. data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
  189. data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
  190. data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
  191. data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
  192. data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
  193. data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
  194. data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
  195. data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
  196. data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
  197. data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
  198. data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
  199. data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
  200. data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
  201. data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
  202. data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
  203. data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
  204. data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
  205. data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
  206. data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
  207. data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
  208. data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
  209. data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
  210. data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
  211. data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
  212. data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
  213. data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
  214. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
  215. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
  216. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
  217. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
  218. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
  219. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
  220. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
  221. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
  222. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
  223. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
  224. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
  225. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
  226. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
  227. data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
  228. data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
  229. data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
  230. data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
  231. data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
  232. data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
  233. data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
  234. data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
  235. data/lib/generators/seams/core/core_generator.rb +191 -0
  236. data/lib/generators/seams/core/templates/README.md.tt +45 -0
  237. data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
  238. data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
  239. data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
  240. data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
  241. data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
  242. data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
  243. data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
  244. data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
  245. data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
  246. data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
  247. data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
  248. data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
  249. data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
  250. data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
  251. data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
  252. data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
  253. data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
  254. data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
  255. data/lib/generators/seams/engine/engine_generator.rb +165 -0
  256. data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
  257. data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
  258. data/lib/generators/seams/engine/templates/README.md.tt +40 -0
  259. data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
  260. data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
  261. data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
  262. data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
  263. data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
  264. data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
  265. data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
  266. data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
  267. data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
  268. data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
  269. data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
  270. data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
  271. data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
  272. data/lib/generators/seams/install/install_generator.rb +211 -0
  273. data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
  274. data/lib/generators/seams/install/templates/Procfile.tt +14 -0
  275. data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
  276. data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
  277. data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
  278. data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
  279. data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
  280. data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
  281. data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
  282. data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
  283. data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
  284. data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
  285. data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
  286. data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
  287. data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
  288. data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
  289. data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
  290. data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
  291. data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
  292. data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
  293. data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
  294. data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
  295. data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
  296. data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
  297. data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
  298. data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
  299. data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
  300. data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
  301. data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
  302. data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
  303. data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
  304. data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
  305. data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
  306. data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
  307. data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
  308. data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
  309. data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
  310. data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
  311. data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
  312. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
  313. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
  314. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
  315. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
  316. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
  317. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
  318. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
  319. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
  320. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
  321. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
  322. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
  323. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
  324. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
  325. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
  326. data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
  327. data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
  328. data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
  329. data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
  330. data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
  331. data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
  332. data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
  333. data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
  334. data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
  335. data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
  336. data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
  337. data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
  338. data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
  339. data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
  340. data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
  341. data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
  342. data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
  343. data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
  344. data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
  345. data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
  346. data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
  347. data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
  348. data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
  349. data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
  350. data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
  351. data/lib/generators/seams/remove/remove_generator.rb +259 -0
  352. data/lib/generators/seams/teams/teams_generator.rb +298 -0
  353. data/lib/generators/seams/teams/templates/README.md.tt +88 -0
  354. data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
  355. data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
  356. data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
  357. data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
  358. data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
  359. data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
  360. data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
  361. data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
  362. data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
  363. data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
  364. data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
  365. data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
  366. data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
  367. data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
  368. data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
  369. data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
  370. data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
  371. data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
  372. data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
  373. data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
  374. data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
  375. data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
  376. data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
  377. data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
  378. data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
  379. data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
  380. data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
  381. data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
  382. data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
  383. data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
  384. data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
  385. data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
  386. data/lib/seams/cli/list.rb +111 -0
  387. data/lib/seams/cli/quality.rb +99 -0
  388. data/lib/seams/cli/resolve.rb +276 -0
  389. data/lib/seams/cli/test_changed.rb +116 -0
  390. data/lib/seams/cli.rb +48 -0
  391. data/lib/seams/configuration.rb +19 -0
  392. data/lib/seams/cops/known_queue_names.rb +42 -0
  393. data/lib/seams/cops/migration_comments.rb +68 -0
  394. data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
  395. data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
  396. data/lib/seams/cops.rb +18 -0
  397. data/lib/seams/event_registry.rb +49 -0
  398. data/lib/seams/events/adapter.rb +24 -0
  399. data/lib/seams/events/adapters/active_support.rb +31 -0
  400. data/lib/seams/events/publisher.rb +178 -0
  401. data/lib/seams/events.rb +39 -0
  402. data/lib/seams/generators/dummy_app_writer.rb +424 -0
  403. data/lib/seams/generators/eject_aware.rb +102 -0
  404. data/lib/seams/generators/follow_up_generator.rb +148 -0
  405. data/lib/seams/generators/host_injector.rb +124 -0
  406. data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
  407. data/lib/seams/generators/splicer.rb +217 -0
  408. data/lib/seams/observability/adapter.rb +33 -0
  409. data/lib/seams/observability/adapters/rails_logger.rb +59 -0
  410. data/lib/seams/observability.rb +34 -0
  411. data/lib/seams/runtime.rb +23 -0
  412. data/lib/seams/version.rb +5 -0
  413. data/lib/seams.rb +23 -0
  414. 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