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,191 @@
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 Core engine on top of the generic engine
14
+ # scaffold. Ships the seven primitive concerns + AuditLog model
15
+ # other engines lean on:
16
+ #
17
+ # - Core::Auditable — records create/update/destroy in audit_logs
18
+ # - Core::SoftDeletable — deleted_at column + default_scope
19
+ # - Core::Sluggable — auto-generated URL slug from a configurable field
20
+ # - Core::TenantScoped — scopes records to Current.team
21
+ # - Core::HasCurrentAttributes — Current.user / Current.team
22
+ # - Core::EventPublisher — convenience wrapper around Seams::Events::Publisher
23
+ # - Core::EmailFormatValidator — custom validator
24
+ #
25
+ # Run with: bin/rails generate seams:core
26
+ class CoreGenerator < Rails::Generators::Base
27
+ include Seams::Generators::HostInjector
28
+ include Seams::Generators::EjectAware
29
+
30
+ source_root File.expand_path("templates", __dir__)
31
+
32
+ ENGINE_NAME = "core"
33
+
34
+ def create_base_engine
35
+ EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
36
+ end
37
+
38
+ def overwrite_engine_entry_point
39
+ # engine.rb / lib/core.rb stay framework-managed.
40
+ template "lib/engine.rb.tt", engine_path("lib/core/engine.rb"), force: true
41
+ template "lib/core.rb.tt", engine_path("lib/core.rb"), force: true
42
+ end
43
+
44
+ def create_models
45
+ template_unless_ejected "app/models/application_record.rb.tt",
46
+ engine_path("app/models/core/application_record.rb")
47
+ template_unless_ejected "app/models/audit_log.rb.tt",
48
+ engine_path("app/models/core/audit_log.rb")
49
+ end
50
+
51
+ def create_concerns
52
+ template_unless_ejected "app/models/concerns/auditable.rb.tt",
53
+ engine_path("app/models/concerns/core/auditable.rb")
54
+ template_unless_ejected "app/models/concerns/soft_deletable.rb.tt",
55
+ engine_path("app/models/concerns/core/soft_deletable.rb")
56
+ template_unless_ejected "app/models/concerns/sluggable.rb.tt",
57
+ engine_path("app/models/concerns/core/sluggable.rb")
58
+ template_unless_ejected "app/models/concerns/tenant_scoped.rb.tt",
59
+ engine_path("app/models/concerns/core/tenant_scoped.rb")
60
+ template_unless_ejected "app/controllers/concerns/has_current_attributes.rb.tt",
61
+ engine_path("app/controllers/concerns/core/has_current_attributes.rb")
62
+ end
63
+
64
+ def create_services_and_validators
65
+ template_unless_ejected "app/services/event_publisher.rb.tt",
66
+ engine_path("app/services/core/event_publisher.rb")
67
+ template_unless_ejected "app/validators/email_format_validator.rb.tt",
68
+ engine_path("app/validators/core/email_format_validator.rb")
69
+ end
70
+
71
+ def create_current
72
+ template_unless_ejected "app/models/current.rb.tt",
73
+ engine_path("app/models/core/current.rb")
74
+ end
75
+
76
+ def create_migration
77
+ template "db/migrate/create_core_audit_logs.rb.tt",
78
+ engine_path("db/migrate/#{timestamp}_create_core_audit_logs.rb")
79
+ end
80
+
81
+ def create_specs
82
+ template_unless_ejected "spec/models/audit_log_spec.rb.tt",
83
+ engine_path("spec/models/core/audit_log_spec.rb")
84
+ template_unless_ejected "spec/concerns/auditable_spec.rb.tt",
85
+ engine_path("spec/concerns/core/auditable_spec.rb")
86
+ template_unless_ejected "spec/concerns/sluggable_spec.rb.tt",
87
+ engine_path("spec/concerns/core/sluggable_spec.rb")
88
+ template_unless_ejected "spec/validators/email_format_validator_spec.rb.tt",
89
+ engine_path("spec/validators/core/email_format_validator_spec.rb")
90
+ end
91
+
92
+ def overwrite_readme
93
+ template "README.md.tt", engine_path("README.md"), force: true
94
+ end
95
+
96
+ def update_exposed_concerns
97
+ rubocop_path = engine_path(".rubocop.yml")
98
+ return unless File.exist?(rubocop_path)
99
+
100
+ contents = File.read(rubocop_path)
101
+ replacement = " ExposedConcerns:\n " \
102
+ "- Core::Auditable\n " \
103
+ "- Core::SoftDeletable\n " \
104
+ "- Core::Sluggable\n " \
105
+ "- Core::TenantScoped\n " \
106
+ "- Core::HasCurrentAttributes"
107
+ contents.sub!(/^ ExposedConcerns: \[\]$/, replacement)
108
+ File.write(rubocop_path, contents)
109
+ end
110
+
111
+ def create_dummy_app
112
+ # Post Wave 9: the dummy app does NOT ship a host User model.
113
+ # The auditable spec stubs its own `Article` (via stub_const)
114
+ # and the tenant_scoped/sluggable specs work against the
115
+ # `articles`/`teams` tables in dummy_schema. There is no
116
+ # remaining caller for a top-level User class.
117
+ Seams::Generators::DummyAppWriter.write!(
118
+ engine_path: File.join(destination_root, "engines", ENGINE_NAME),
119
+ engine_module: "Core",
120
+ schema: dummy_schema
121
+ )
122
+ template "spec/runtime/boot_spec.rb.tt",
123
+ engine_path("spec/runtime/core_boot_spec.rb")
124
+ end
125
+
126
+ def wire_into_host
127
+ host_inject_mount(engine_class: "Core::Engine", at: "/")
128
+ end
129
+
130
+ def report_summary
131
+ say ""
132
+ say " Core engine generated at engines/core/", :green
133
+ say ""
134
+ say " Next steps:", :yellow
135
+ say " 1. bin/rails db:migrate"
136
+ say " 2. Mix concerns into your models as needed:"
137
+ say " include Core::Auditable, Core::SoftDeletable, Core::Sluggable, etc."
138
+ say " 3. Run the engine specs: bin/rails seams:test[core]"
139
+ say ""
140
+ end
141
+
142
+ private
143
+
144
+ def engine_path(relative)
145
+ File.join(destination_root, "engines", ENGINE_NAME, relative)
146
+ end
147
+
148
+ def dummy_schema
149
+ <<~SCHEMA
150
+ create_table :core_audit_logs do |t|
151
+ t.string :action, null: false
152
+ t.string :auditable_type
153
+ t.string :auditable_id
154
+ t.bigint :actor_id
155
+ t.jsonb :payload, null: false, default: {}
156
+ t.timestamps
157
+ end
158
+ add_index :core_audit_logs, %i[auditable_type auditable_id]
159
+
160
+ create_table :articles do |t|
161
+ t.string :title
162
+ t.string :slug
163
+ t.datetime :deleted_at
164
+ t.bigint :team_id
165
+ t.timestamps
166
+ end
167
+ add_index :articles, :slug, unique: true
168
+
169
+ create_table :teams do |t|
170
+ t.string :name
171
+ t.string :slug
172
+ t.timestamps
173
+ end
174
+ add_index :teams, :slug, unique: true
175
+ SCHEMA
176
+ end
177
+
178
+ # Core's migration sits AHEAD of the canonical engine offsets
179
+ # (auth +0/+1, notifications +100, billing +200..+202, teams
180
+ # +300..+302) so other engines' tables can reference audit_logs
181
+ # if they ever need to.
182
+ def timestamp
183
+ base = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
184
+ # Use a NEGATIVE offset (subtract 1000) so core migrates first
185
+ # in the standard ordering. This is safe because timestamps are
186
+ # just sortable strings.
187
+ (base - 1000).to_s
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,45 @@
1
+ # Core
2
+
3
+ > Foundation primitives every Seams engine can build on. Concerns for
4
+ > auditing, soft delete, slugs, multi-tenancy, plus shared services
5
+ > and validators.
6
+
7
+ ## Events emitted
8
+
9
+ | Event name | Payload | Emitted when |
10
+ | --- | --- | --- |
11
+ | `record.audited.core` | `{ action:, type:, id:, actor_id: }` | `Core::Auditable` records an entry on create/update/destroy |
12
+
13
+ ## Events consumed
14
+
15
+ This engine does not subscribe to any other engine's events.
16
+
17
+ ## Exposed concerns
18
+
19
+ | Concern | Purpose |
20
+ | --- | --- |
21
+ | `Core::Auditable` | Records audit entries for the model on CRUD. |
22
+ | `Core::SoftDeletable` | `deleted_at` tombstone + default scope. |
23
+ | `Core::Sluggable` | Auto-generated URL slug with collision suffixing. |
24
+ | `Core::TenantScoped` | Auto-scopes records to `Core::Current.team`. |
25
+ | `Core::HasCurrentAttributes` | Populates `Core::Current` on every request. |
26
+
27
+ ## Services + validators
28
+
29
+ | Symbol | Purpose |
30
+ | --- | --- |
31
+ | `Core::EventPublisher.publish` | Wraps `Seams::Events::Publisher.publish` and adds actor/team/request id. |
32
+ | `Core::EmailFormatValidator` | Stricter email-shape validator (`validates :email, email_format: true`). |
33
+ | `Core::Current` (CurrentAttributes) | Per-request `user`, `team`, `request_id`. |
34
+
35
+ ## Mounting
36
+
37
+ The engine has no routes — it ships shared code, not endpoints. The
38
+ generator mounts at `/` for completeness; you can remove the mount
39
+ line if you don't need any routing entry point.
40
+
41
+ ## Running the specs
42
+
43
+ ```bash
44
+ bin/rails seams:test[core]
45
+ ```
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Core
6
+ # Mix into ApplicationController to populate Core::Current at the
7
+ # start of every request. Hosts override #resolve_current_user and
8
+ # #resolve_current_team to plug in their own auth/tenant lookup —
9
+ # the default implementations look for `Auth::Current.identity`
10
+ # (the Auth engine's per-request namespace post-Wave-9) and
11
+ # `params[:team_id]` (the Teams engine).
12
+ #
13
+ # `Core::Current.user` is best read as "the current actor" — the
14
+ # auditable concern writes `actor_id = Core::Current.user&.id`. By
15
+ # default that's the signed-in Identity. Hosts that maintain a
16
+ # domain-specific User on top of Auth::Identity can override
17
+ # #resolve_current_user to point at that User instead.
18
+ #
19
+ # class ApplicationController < ActionController::Base
20
+ # include Core::HasCurrentAttributes
21
+ # end
22
+ module HasCurrentAttributes
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ before_action :populate_current_attributes
27
+ end
28
+
29
+ private
30
+
31
+ def populate_current_attributes
32
+ Core::Current.user = resolve_current_user
33
+ Core::Current.team = resolve_current_team
34
+ Core::Current.request_id = request.request_id if respond_to?(:request)
35
+ end
36
+
37
+ # Default resolution order (post-Wave-9):
38
+ # 1. `Auth::Current.identity` — the canonical signed-in human.
39
+ # 2. `current_identity` — the helper Auth engine exposes.
40
+ # 3. `current_user` — the legacy Wave-8 helper, kept
41
+ # so hosts that maintain a User on top of Auth::Identity
42
+ # stay working without overriding the concern.
43
+ # Hosts override this method to plug in a different actor.
44
+ def resolve_current_user
45
+ if defined?(Auth::Current) && Auth::Current.respond_to?(:identity) && Auth::Current.identity
46
+ return Auth::Current.identity
47
+ end
48
+ return current_identity if respond_to?(:current_identity)
49
+ return current_user if respond_to?(:current_user)
50
+
51
+ nil
52
+ end
53
+
54
+ # Default: read `Teams::Current.team` (set by the host's teams
55
+ # before_action), which the boundary cop exempts as a per-request
56
+ # namespace. Hosts that want a different binding (e.g. resolving
57
+ # the team from `params[:team_id]` without delegating to teams)
58
+ # override this method in their ApplicationController.
59
+ #
60
+ # Why we no longer reach into `Teams::Team` here: doing so makes
61
+ # core depend on teams, which inverts the dependency direction
62
+ # (teams may include core's concerns; core must not know about
63
+ # teams' models). Reading `Teams::Current.team` keeps the
64
+ # cross-engine surface to the documented per-request namespace.
65
+ def resolve_current_team
66
+ return nil unless defined?(Teams::Current) && Teams::Current.respond_to?(:team)
67
+
68
+ Teams::Current.team
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ class ApplicationRecord < ::ApplicationRecord
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ # Records create/update/destroy events for any model that includes
5
+ # Core::Auditable. Schema is intentionally polymorphic so the audit
6
+ # table can serve every engine.
7
+ class AuditLog < ApplicationRecord
8
+ self.table_name = "core_audit_logs"
9
+
10
+ ACTIONS = %w[create update destroy].freeze
11
+
12
+ belongs_to :auditable, polymorphic: true, optional: true
13
+
14
+ validates :action, inclusion: { in: ACTIONS }
15
+
16
+ scope :for, ->(record) { where(auditable_type: record.class.name, auditable_id: record.id) }
17
+ scope :recent, -> { order(created_at: :desc) }
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Core
6
+ # Mix into any model to get an audit log entry on every
7
+ # create/update/destroy. Records the actor from Current.user and
8
+ # the changed attributes for updates.
9
+ #
10
+ # class Article < ApplicationRecord
11
+ # include Core::Auditable
12
+ # end
13
+ module Auditable
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ has_many :audit_logs, class_name: "Core::AuditLog",
18
+ as: :auditable, dependent: :nullify
19
+
20
+ # after_commit (not after_create / after_update / after_destroy)
21
+ # so the publish doesn't fire inside the host model's open
22
+ # transaction. Subscribers run synchronously; an in-transaction
23
+ # publish lets a slow subscriber hold the table lock.
24
+ after_commit :record_audit_create, on: :create
25
+ after_commit :record_audit_update, on: :update
26
+ after_commit :record_audit_destroy, on: :destroy
27
+ end
28
+
29
+ private
30
+
31
+ def record_audit_create
32
+ record_audit("create", attributes_snapshot)
33
+ end
34
+
35
+ def record_audit_update
36
+ changed = saved_changes.transform_values(&:last)
37
+ return if changed.empty?
38
+
39
+ record_audit("update", changed)
40
+ end
41
+
42
+ def record_audit_destroy
43
+ record_audit("destroy", attributes_snapshot)
44
+ end
45
+
46
+ def record_audit(action, payload)
47
+ Core::AuditLog.create!(
48
+ action: action,
49
+ auditable_type: self.class.name,
50
+ auditable_id: id,
51
+ actor_id: Core::Current.user&.id,
52
+ payload: payload
53
+ )
54
+ Seams::Events::Publisher.publish(
55
+ "record.audited.core",
56
+ action: action, type: self.class.name, id: id, actor_id: Core::Current.user&.id
57
+ )
58
+ end
59
+
60
+ def attributes_snapshot
61
+ attributes.except("created_at", "updated_at")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Core
6
+ # Auto-generates a URL slug from a configured source attribute on
7
+ # create. Appends a numeric suffix on collision (acme, acme-2,
8
+ # acme-3, ...) so users see "creating Acme" succeed even when the
9
+ # name is taken.
10
+ #
11
+ # class Team < ApplicationRecord
12
+ # include Core::Sluggable
13
+ # sluggable_from :name
14
+ # end
15
+ module Sluggable
16
+ extend ActiveSupport::Concern
17
+
18
+ class_methods do
19
+ def sluggable_from(attribute)
20
+ @slug_source = attribute
21
+ end
22
+
23
+ def slug_source
24
+ @slug_source || :name
25
+ end
26
+ end
27
+
28
+ included do
29
+ validates :slug, presence: true, uniqueness: true,
30
+ format: { with: /\A[a-z0-9-]+\z/ }
31
+ before_validation :assign_slug, on: :create
32
+ end
33
+
34
+ private
35
+
36
+ def assign_slug
37
+ return if slug.present?
38
+
39
+ base = slug_base
40
+ candidate = base
41
+ counter = 2
42
+ while self.class.where.not(id: id).exists?(slug: candidate)
43
+ candidate = "#{base}-#{counter}"
44
+ counter += 1
45
+ end
46
+ self.slug = candidate
47
+ end
48
+
49
+ def slug_base
50
+ send(self.class.slug_source).to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/(^-|-$)/, "")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Core
6
+ # Tombstone-style soft delete. Sets `deleted_at` instead of actually
7
+ # destroying the row, and adds a default scope that hides deleted
8
+ # records. Use `unscoped` to query them.
9
+ #
10
+ # class Article < ApplicationRecord
11
+ # include Core::SoftDeletable
12
+ # end
13
+ #
14
+ # Requires a `deleted_at` timestamp column on the model's table.
15
+ module SoftDeletable
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ default_scope { where(deleted_at: nil) }
20
+
21
+ scope :deleted, -> { unscoped.where.not(deleted_at: nil) }
22
+ scope :with_deleted, -> { unscoped }
23
+ end
24
+
25
+ def soft_delete!
26
+ update!(deleted_at: Time.current)
27
+ end
28
+
29
+ def restore!
30
+ update!(deleted_at: nil)
31
+ end
32
+
33
+ def deleted?
34
+ deleted_at.present?
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Core
6
+ # Multi-tenant row scoping. Auto-fills team_id from Core::Current.team
7
+ # on create and adds a default scope that filters to the current
8
+ # team's rows.
9
+ #
10
+ # class Article < ApplicationRecord
11
+ # include Core::TenantScoped
12
+ # end
13
+ #
14
+ # Requires a `team_id` column on the model's table. Pair with the
15
+ # Teams engine, or any model that exposes Core::Current.team.
16
+ module TenantScoped
17
+ extend ActiveSupport::Concern
18
+
19
+ included do
20
+ belongs_to :team, optional: true
21
+
22
+ default_scope do
23
+ if Core::Current.team
24
+ where(team_id: Core::Current.team.id)
25
+ else
26
+ all
27
+ end
28
+ end
29
+
30
+ before_validation :assign_team, on: :create
31
+ end
32
+
33
+ private
34
+
35
+ def assign_team
36
+ self.team_id ||= Core::Current.team&.id
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ # Per-request global state. Hosts set Current.user (typically from
5
+ # an Auth before-action) and Current.team (from a tenant-scope
6
+ # before-action) so deeper layers — concerns, jobs invoked from
7
+ # the request, etc. — can read them without threading args through.
8
+ #
9
+ # Backed by ActiveSupport::CurrentAttributes which clears between
10
+ # requests automatically.
11
+ class Current < ActiveSupport::CurrentAttributes
12
+ attribute :user, :team, :request_id
13
+
14
+ resets { Time.zone = nil }
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ # Thin convenience wrapper around Seams::Events::Publisher. Adds
5
+ # the request id, actor id, and team id from Core::Current to every
6
+ # payload so subscribers don't have to thread that context through
7
+ # by hand.
8
+ #
9
+ # Core::EventPublisher.publish("subscription.created.billing", id: 42)
10
+ module EventPublisher
11
+ module_function
12
+
13
+ def publish(event_name, payload = {})
14
+ enriched = payload.merge(
15
+ actor_id: Core::Current.user&.id,
16
+ team_id: Core::Current.team&.id,
17
+ request_id: Core::Current.request_id
18
+ ).compact
19
+
20
+ Seams::Events::Publisher.publish(event_name, enriched)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ # ActiveModel validator. Use as `validates :email, "core/email_format": true`
5
+ # or via the convenience: `validates :email, email_format: true` (Rails
6
+ # auto-discovers `EmailFormatValidator`).
7
+ #
8
+ # Stricter than URI::MailTo::EMAIL_REGEXP — also rejects consecutive
9
+ # dots, leading/trailing dots in the local part, and trailing dots
10
+ # in the domain.
11
+ class EmailFormatValidator < ActiveModel::EachValidator
12
+ PATTERN = /\A[^\s@]+@[^\s@]+\.[^\s@]+\z/
13
+
14
+ def validate_each(record, attribute, value)
15
+ return if value.blank?
16
+
17
+ record.errors.add(attribute, :invalid) unless PATTERN.match?(value.to_s)
18
+ record.errors.add(attribute, :invalid) if value.to_s.include?("..")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates the core_audit_logs table.
4
+ # Why: every engine that mixes Core::Auditable into a model needs
5
+ # somewhere to write audit entries. Single shared table keeps
6
+ # cross-engine queries trivial ("show me everything actor X did
7
+ # last week" doesn't have to UNION across N tables).
8
+ # Risk: append-only writes from after_create/update/destroy hooks.
9
+ # Indexed by auditable + actor for the common queries.
10
+ class CreateCoreAuditLogs < ActiveRecord::Migration[7.1]
11
+ def change
12
+ create_table :core_audit_logs do |t|
13
+ t.string :action, null: false
14
+ t.string :auditable_type
15
+ # String, not bigint — auditable can be a Wave-9 UUID-PK Account
16
+ # or Membership, OR a bigint-PK Identity / Notification / Plan.
17
+ # Polymorphic over both ID-types means the column has to be
18
+ # widest-of (string). Actors stay bigint (Identity is bigint-PK).
19
+ t.string :auditable_id
20
+ t.bigint :actor_id
21
+ t.jsonb :payload, null: false, default: {}
22
+ t.timestamps
23
+ end
24
+
25
+ add_index :core_audit_logs, %i[auditable_type auditable_id]
26
+ add_index :core_audit_logs, :actor_id
27
+ add_index :core_audit_logs, %i[action created_at]
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core/version"
4
+ require "core/engine"
5
+
6
+ module Core
7
+ class Error < StandardError; end
8
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Core
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ end
10
+
11
+ initializer "core.register_events" do
12
+ Seams::EventRegistry.register("record.audited.core", emitted_by: "Core")
13
+ # Follow-up generators that emit new core events (e.g. record.soft_deleted.core) register them here.
14
+ # seams:insertion-point core.engine.events
15
+ end
16
+
17
+ initializer "core.append_migrations" do |app|
18
+ unless app.root == root
19
+ config.paths["db/migrate"].expanded.each do |expanded_path|
20
+ app.config.paths["db/migrate"] << expanded_path
21
+ end
22
+ end
23
+ end
24
+
25
+ # Follow-up generators that need their own initializer block (audit subscribers, validators) declare them here.
26
+ # seams:insertion-point core.engine.initializers
27
+ end
28
+ end