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,4 @@
1
+ <%%# Default billing/subscription_started HTML, fires from %>
2
+ <%%# subscription.created.billing. Override at %>
3
+ <%%# app/views/notifications/templates/billing/subscription_started.html.erb. %>
4
+ <p>Your subscription is active. Welcome aboard.</p>
@@ -0,0 +1,5 @@
1
+ <%%# Default billing/subscription_started template, used by BillingSubscriber %>
2
+ <%%# when it consumes subscription.created.billing. Override by dropping a %>
3
+ <%%# file at app/views/notifications/templates/billing/subscription_started.erb %>
4
+ <%%# in your host. %>
5
+ Your subscription is active. Welcome aboard.
@@ -0,0 +1,3 @@
1
+ <%%# Default billing/subscription_updated HTML. Override at %>
2
+ <%%# app/views/notifications/templates/billing/subscription_updated.html.erb. %>
3
+ <p>Your subscription has been updated.</p>
@@ -0,0 +1,3 @@
1
+ <%%# Default billing/subscription_updated template. Override at %>
2
+ <%%# app/views/notifications/templates/billing/subscription_updated.erb. %>
3
+ Your subscription has been updated.
@@ -0,0 +1,10 @@
1
+ <%%# Default HTML template. notification is the local. Override at %>
2
+ <%%# app/views/notifications/templates/<name>.html.erb in the host. %>
3
+ <p>Hello,</p>
4
+
5
+ <p>You have a notification.</p>
6
+
7
+ <p>—<br>
8
+ This is the default Seams notification template. Override it by creating
9
+ <code>app/views/notifications/templates/&lt;your-template&gt;.html.erb</code>
10
+ in your host app.</p>
@@ -0,0 +1,11 @@
1
+ <%%# Default ERB template. The notification model exposes itself via %>
2
+ <%%# the local variable +notification+. Override by dropping a file %>
3
+ <%%# at app/views/notifications/templates/<name>.erb in your host. %>
4
+ Hello,
5
+
6
+ You have a notification.
7
+
8
+
9
+ This is the default Seams notification template. Override it by
10
+ creating app/views/notifications/templates/<your-template>.erb in
11
+ your host app.
@@ -0,0 +1,6 @@
1
+ <%%# Default welcome HTML template, used by AuthSubscriber when it %>
2
+ <%%# consumes identity.signed_up.auth. Override by dropping a file at %>
3
+ <%%# app/views/notifications/templates/welcome.html.erb in your host. %>
4
+ <p>Welcome<%%= ", #{notification.owner.respond_to?(:name) ? notification.owner.name : ''}".rstrip %>!</p>
5
+
6
+ <p>Thanks for signing up. Reply to this email if you need anything.</p>
@@ -0,0 +1,6 @@
1
+ <%%# Default welcome template, used by AuthSubscriber when it consumes %>
2
+ <%%# identity.signed_up.auth. Override by dropping a file at %>
3
+ <%%# app/views/notifications/templates/welcome.erb in your host. %>
4
+ Welcome<%%= ", #{notification.owner.respond_to?(:name) ? notification.owner.name : ''}".rstrip %>!
5
+
6
+ Thanks for signing up. Reply to this email if you need anything.
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Configuration for the Notifications engine.
4
+ #
5
+ # Adapters
6
+ # --------
7
+ # Email defaults to Notifications::Adapters::ActionMailer (delegates to
8
+ # the engine's NotificationMailer). SMS defaults to a no-op NullSms
9
+ # adapter that logs and drops. Swap either by assigning a class name
10
+ # (a string — resolved via constantize at delivery time so the host
11
+ # doesn't have to require the adapter at boot):
12
+ #
13
+ # Notifications.configure do |config|
14
+ # config.email_adapter = "MyApp::MailgunAdapter"
15
+ # config.sms_adapter = "MyApp::TwilioAdapter"
16
+ # end
17
+ #
18
+ # Optional: Notifiable concern
19
+ # ----------------------------
20
+ # Notifications work without the concern — every Notification has a
21
+ # polymorphic `owner`, and `Notifications::Notification.create!(owner: anything, ...)`
22
+ # is the always-supported path.
23
+ #
24
+ # The concern is OPTIONAL sugar: it adds a `notifications` has_many
25
+ # association and a `#notify(strategy:, template:)` helper to whatever
26
+ # class includes it. Pick one of the patterns below if you want that
27
+ # convenience.
28
+ #
29
+ # Pattern A — wire onto Auth::Identity (canonical post-Wave-9 host):
30
+ #
31
+ # Rails.application.config.to_prepare do
32
+ # Auth::Identity.include(Notifications::Notifiable)
33
+ # end
34
+ #
35
+ # Pattern B — wire onto a host User class (hosts that keep their own
36
+ # domain User alongside Auth::Identity):
37
+ #
38
+ # class User < ApplicationRecord
39
+ # include Notifications::Notifiable
40
+ # end
41
+ #
42
+ # Pattern C — don't include the concern at all. Use
43
+ # `Notifications::Notification.create!(owner: ..., template: ...)` or
44
+ # the per-strategy classes directly.
45
+ #
46
+ # Default subscriber
47
+ # ------------------
48
+ # Notifications::AuthSubscriber listens for `identity.signed_up.auth`
49
+ # and creates an InApp + Email welcome notification owned by the
50
+ # Auth::Identity. To redirect the welcome notification at a different
51
+ # owner (an Account, a Membership, a host User), copy
52
+ # `engines/notifications/app/subscribers/notifications/auth_subscriber.rb`
53
+ # into your host's app/subscribers/, change `OWNER_CLASS_NAME` and the
54
+ # resolution rule, then re-attach in this initializer:
55
+ #
56
+ # Rails.application.config.to_prepare do
57
+ # Notifications::AuthSubscriber.attach!
58
+ # end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ Notifications::Engine.routes.draw do
4
+ resources :notifications, only: %i[index show] do
5
+ member do
6
+ patch :mark_as_read
7
+ end
8
+ collection do
9
+ patch :mark_all_as_read
10
+ end
11
+ end
12
+
13
+ resource :preferences, only: %i[show update]
14
+
15
+ # Follow-up generators that add admin-side notification routes (digest scheduling, channel-specific opt-outs) splice their resources here.
16
+ # seams:insertion-point notifications.routes.after_preferences
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates notification_deliveries — one row per successful send.
4
+ # Why: audit trail for support and ops ("when did we last send X to
5
+ # user Y?") without round-tripping the upstream gateway.
6
+ # Risk: append-only writes from Notification#send!. Indexed by
7
+ # notification_id for join queries from the bell view.
8
+ class CreateNotificationDeliveries < ActiveRecord::Migration[7.1]
9
+ def change
10
+ create_table :notification_deliveries do |t|
11
+ t.references :notification, null: false, foreign_key: true, index: true
12
+ t.datetime :sent_at, null: false
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates notification_preferences for per-Identity opt-outs.
4
+ # Why: hosts need a place for "email me when X" toggles. Channel
5
+ # preferences live with the human (Auth::Identity), not with
6
+ # the polymorphic Notification owner — a host wiring
7
+ # notifications onto an Account or Membership still resolves
8
+ # toggles via the Identity behind the actor. Single row per
9
+ # (identity, channel, notification_type) — absence means
10
+ # use defaults.
11
+ # Risk: tiny table, indexed for fast lookup.
12
+ class CreateNotificationPreferences < ActiveRecord::Migration[7.1]
13
+ def change
14
+ create_table :notification_preferences do |t|
15
+ t.bigint :identity_id, null: false
16
+ t.string :channel, null: false # "in_app" | "email" | "sms"
17
+ t.string :notification_type
18
+ t.boolean :enabled, null: false, default: true
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :notification_preferences, %i[identity_id channel notification_type], unique: true,
23
+ name: "index_notification_prefs_unique"
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates the notifications table for the STI Notification model.
4
+ # Why: every channel (in-app, email, SMS) shares the same scheduling +
5
+ # audit semantics. STI keeps that DRY: one table, one indexed
6
+ # next_delivery_at, three subclasses with their own dispatch!.
7
+ # Risk: append + per-row updates (read_at flips, next_delivery_at
8
+ # advances). Indexed by (next_delivery_at) for the due sweeper
9
+ # and (owner) for the bell view.
10
+ class CreateNotifications < ActiveRecord::Migration[7.1]
11
+ def change
12
+ create_table :notifications do |t|
13
+ t.string :type, null: false # STI discriminator
14
+ # Polymorphic owner: can be Auth::Identity (bigint), an
15
+ # Accounts::Account (UUID post-Wave-9), or any other host model.
16
+ # `owner_id` is stored as a string so the column accommodates
17
+ # both PK types without needing two separate FKs. Rails handles
18
+ # the type coercion in the polymorphic association — the lookup
19
+ # uses `WHERE owner_type = ? AND owner_id = ?` and AR casts the
20
+ # parameter to text in the prepared statement.
21
+ t.string :owner_type, null: false
22
+ t.string :owner_id, null: false
23
+ t.string :recipient # destination address (override of owner default)
24
+ t.string :template, null: false
25
+ t.jsonb :schedule_data # IceCube::Schedule#to_hash
26
+ t.datetime :next_delivery_at
27
+ t.datetime :read_at # InApp only
28
+ t.timestamps
29
+ end
30
+
31
+ add_index :notifications, %i[owner_type owner_id]
32
+ add_index :notifications, :next_delivery_at
33
+ add_index :notifications, %i[type next_delivery_at]
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notifications
4
+ module Adapters
5
+ # Contract every notification adapter must implement. Subclass
6
+ # this in your host application to wire Mailgun, SendGrid, Twilio,
7
+ # etc., then point the relevant +Notifications.configuration+
8
+ # knob at the subclass name.
9
+ #
10
+ # Each adapter receives the full Notification object so it can
11
+ # read +recipient+, +template+, +rendered_content+, +owner+ —
12
+ # whatever the gateway needs. Returns a Hash that includes at
13
+ # least { ok: true|false, provider: <string> }.
14
+ class Abstract
15
+ def deliver(notification:)
16
+ raise NotImplementedError, "#{self.class} must implement #deliver"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "notifications/adapters/abstract"
4
+
5
+ module Notifications
6
+ module Adapters
7
+ # Default email adapter — delegates to the engine's
8
+ # NotificationMailer which renders the notification's template
9
+ # and dispatches via ActionMailer.
10
+ class ActionMailer < Abstract
11
+ def deliver(notification:)
12
+ Notifications::NotificationMailer.notify(notification).deliver_later
13
+ { ok: true, provider: "action_mailer" }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "notifications/adapters/abstract"
4
+
5
+ module Notifications
6
+ module Adapters
7
+ # No-op SMS adapter. Logs the delivery via Seams::Observability
8
+ # and returns success without dispatching. Useful in development
9
+ # and tests so SMS calls succeed without burning vendor quota.
10
+ class NullSms < Abstract
11
+ def deliver(notification:)
12
+ Seams::Observability.adapter.info(
13
+ "notifications.null_sms.deliver",
14
+ engine: "notifications",
15
+ to: notification.recipient,
16
+ template: notification.template,
17
+ length: notification.rendered_content.to_s.length
18
+ )
19
+ { ok: true, provider: "null_sms" }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Notifications
6
+ # OPTIONAL convenience concern. Adds a +notifications+ has_many
7
+ # association + a +#notify+ helper to whatever model includes it.
8
+ # Notifications themselves are polymorphic — every Notification has
9
+ # an +owner+, which can be ANY ActiveRecord model. You don't need
10
+ # this concern to attach notifications to a model:
11
+ #
12
+ # Notifications::Notification.create!(owner: anything, template: ...)
13
+ #
14
+ # The concern is just sugar for the receiving side.
15
+ #
16
+ # Three include patterns hosts can pick from:
17
+ #
18
+ # 1. Wire into Auth::Identity (most common for canonical seams hosts
19
+ # after Wave 9 — the "human" is Auth::Identity, not a host User):
20
+ #
21
+ # # config/initializers/notifications.rb
22
+ # Rails.application.config.to_prepare do
23
+ # Auth::Identity.include(Notifications::Notifiable)
24
+ # end
25
+ #
26
+ # 2. Wire into a host-defined User class (older hosts that still
27
+ # keep their own User model alongside Auth::Identity):
28
+ #
29
+ # class User < ApplicationRecord
30
+ # include Notifications::Notifiable
31
+ # end
32
+ #
33
+ # 3. Don't include the concern at all. Create notifications via
34
+ # +Notifications::Notification.create!(owner: ..., template: ...)+
35
+ # or the per-strategy classes directly. The polymorphic +owner+
36
+ # column doesn't care.
37
+ #
38
+ # Once included:
39
+ #
40
+ # target.notify(strategy: :email, template: "welcome")
41
+ # target.notify(strategy: :sms, template: "alert", schedule_config: { starts_at: 1.day.from_now, frequency: "once" })
42
+ # target.notify(strategy: :in_app, template: "system")
43
+ module Notifiable
44
+ extend ActiveSupport::Concern
45
+
46
+ STRATEGY_CLASSES = {
47
+ <%- if channels.include?("email") -%>
48
+ email: "Notifications::Strategies::Email",
49
+ <%- end -%>
50
+ <%- if channels.include?("sms") -%>
51
+ sms: "Notifications::Strategies::Sms",
52
+ <%- end -%>
53
+ <%- if channels.include?("in_app") -%>
54
+ in_app: "Notifications::Strategies::InApp",
55
+ <%- end -%>
56
+ # Follow-up generators that ship new delivery strategies (push, webhook, slack) splice their `key: "Notifications::Strategies::ClassName",` entry here. Trailing commas on every entry keep the hash valid both before and after a splice.
57
+ # seams:insertion-point notifications.notifiable.strategies
58
+ }.freeze
59
+
60
+ included do
61
+ has_many :notifications, class_name: "Notifications::Notification",
62
+ as: :owner, dependent: :destroy
63
+ end
64
+
65
+ def notify(strategy:, template:, schedule_config: nil, recipient: nil)
66
+ notif = notifications.build(
67
+ type: STRATEGY_CLASSES.fetch(strategy.to_sym),
68
+ recipient: recipient,
69
+ template: template
70
+ )
71
+ assign_schedule!(notif, schedule_config)
72
+ notif.save!
73
+ notif.send_async if notif.due?
74
+ notif
75
+ end
76
+
77
+ # Looks up a registered Notifications::TypeRegistry::Type by name
78
+ # and creates one Notification per channel the type supports
79
+ # (subject to the host's NotificationPreference rows). Returns the
80
+ # array of created notifications.
81
+ #
82
+ # Preference lookup keys off `identity_id` — if the including
83
+ # model is an Auth::Identity, that's just `id`. Hosts that include
84
+ # the concern on a non-Identity model (a host User keyed by a
85
+ # different identity_id, an Account, etc.) should override
86
+ # `notification_preference_identity_id` to return the right id.
87
+ def notify_typed(type:, schedule_config: nil, recipient: nil)
88
+ record = Notifications::TypeRegistry.fetch(type)
89
+ preference_id = notification_preference_identity_id
90
+ record.channels.filter_map do |channel|
91
+ next unless STRATEGY_CLASSES.key?(channel.to_sym)
92
+ next unless Notifications::NotificationPreference.enabled?(
93
+ identity_id: preference_id, channel: channel.to_s, notification_type: record.name
94
+ )
95
+
96
+ notify(strategy: channel, template: record.template,
97
+ schedule_config: schedule_config, recipient: recipient)
98
+ end
99
+ end
100
+
101
+ # Override on a non-Identity host class to point preference lookup
102
+ # at the right identity_id. Default: the receiver's own id (works
103
+ # when Notifiable is included on Auth::Identity directly).
104
+ def notification_preference_identity_id
105
+ id
106
+ end
107
+
108
+ # Override per-host if the receiver exposes its email address under
109
+ # a different name (e.g. a host User that keeps `primary_email_id`).
110
+ # Default falls back to `email_address` (the Rails 8 has_secure_password
111
+ # convention some hosts keep) and then `email` (Auth::Identity + most
112
+ # host User models).
113
+ def email_notification_recipient
114
+ try(:email_address) || try(:email)
115
+ end
116
+
117
+ def sms_notification_recipient
118
+ try(:phone)
119
+ end
120
+
121
+ def unread_in_app_notifications
122
+ notifications.where(type: "Notifications::Strategies::InApp").unread
123
+ end
124
+
125
+ private
126
+
127
+ def assign_schedule!(notif, schedule_config)
128
+ if schedule_config
129
+ notif.schedule_config = schedule_config
130
+ else
131
+ notif.schedule = IceCube::Schedule.new(Time.current)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notifications
4
+ # Engine-scoped configuration. Override in
5
+ # config/initializers/notifications.rb of the host application:
6
+ #
7
+ # Notifications.configure do |c|
8
+ # c.email_adapter = "Notifications::Adapters::Mailgun"
9
+ # c.sms_adapter = "Notifications::Adapters::Twilio"
10
+ # end
11
+ class Configuration
12
+ attr_accessor :email_adapter, :sms_adapter, :default_from
13
+ # Follow-up generators that add knobs (push adapter, webhook adapter, in-app retention) declare their attr_accessor here.
14
+ # seams:insertion-point notifications.configuration.attributes
15
+
16
+ def initialize
17
+ @email_adapter = "Notifications::Adapters::ActionMailer"
18
+ @sms_adapter = "Notifications::Adapters::NullSms"
19
+ @default_from = "no-reply@example.com"
20
+ # Follow-up generators that add defaults for new attributes (matching notifications.configuration.attributes) splice them here.
21
+ # seams:insertion-point notifications.configuration.defaults
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notifications
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Notifications
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ end
10
+
11
+ initializer "notifications.register_events" do
12
+ Seams::EventRegistry.register("notification.queued.notifications", emitted_by: "Notifications")
13
+ Seams::EventRegistry.register("notification.delivered.notifications", emitted_by: "Notifications")
14
+ Seams::EventRegistry.register("notification.failed.notifications", emitted_by: "Notifications")
15
+ # Follow-up generators that ship new delivery semantics (notification.bounced.notifications, etc.) register them here.
16
+ # seams:insertion-point notifications.engine.events
17
+ end
18
+
19
+ initializer "notifications.append_migrations" do |app|
20
+ unless app.root == root
21
+ config.paths["db/migrate"].expanded.each do |expanded_path|
22
+ app.config.paths["db/migrate"] << expanded_path
23
+ end
24
+ end
25
+ end
26
+
27
+ config.after_initialize do
28
+ require "notifications/concerns/notifiable"
29
+ Notifications::AuthSubscriber.attach!
30
+ # Follow-up generators that ship subscribers for new cross-engine events (Notifications::TeamsSubscriber, etc.) splice their attach! calls here.
31
+ # seams:insertion-point notifications.engine.subscribers
32
+ Notifications::BillingSubscriber.attach! if defined?(Billing::Engine)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "notifications/version"
4
+ require "notifications/configuration"
5
+ require "notifications/type_registry"
6
+ require "notifications/engine"
7
+ require "notifications/concerns/notifiable"
8
+ require "notifications/adapters/abstract"
9
+ require "notifications/adapters/action_mailer"
10
+ require "notifications/adapters/null_sms"
11
+
12
+ module Notifications
13
+ class Error < StandardError; end
14
+ class AdapterError < Error; end
15
+
16
+ class << self
17
+ def configuration
18
+ @configuration ||= Configuration.new
19
+ end
20
+
21
+ def configure
22
+ yield configuration
23
+ end
24
+
25
+ # Seed the TypeRegistry with the default cross-engine notification
26
+ # types other engines emit (auth.signed_up, billing.invoice_paid,
27
+ # etc). Hosts call this from their initializer; subsequent
28
+ # +TypeRegistry.register+ calls override the defaults or extend the
29
+ # set with host-specific types.
30
+ def seed_default_types!
31
+ TypeRegistry.register("welcome",
32
+ template: "welcome",
33
+ channels: %i[in_app email],
34
+ display: "Welcome")
35
+ TypeRegistry.register("billing.invoice_paid",
36
+ template: "billing/invoice_paid",
37
+ channels: %i[in_app email],
38
+ display: "Invoice paid")
39
+ TypeRegistry.register("billing.invoice_failed",
40
+ template: "billing/invoice_failed",
41
+ channels: %i[in_app email],
42
+ display: "Invoice payment failed")
43
+ TypeRegistry.register("billing.subscription_started",
44
+ template: "billing/subscription_started",
45
+ channels: %i[in_app email],
46
+ display: "Subscription started")
47
+ TypeRegistry.register("billing.subscription_canceled",
48
+ template: "billing/subscription_canceled",
49
+ channels: %i[in_app email],
50
+ display: "Subscription canceled")
51
+ TypeRegistry.register("billing.lifetime_purchased",
52
+ template: "billing/lifetime_purchased",
53
+ channels: %i[in_app email],
54
+ display: "Lifetime access purchased")
55
+ # Follow-up generators that ship new cross-engine notification types (teams.invitation_received, auth.password_reset) splice their TypeRegistry.register(...) call here.
56
+ # seams:insertion-point notifications.type_registry.defaults
57
+ end
58
+
59
+ def email_adapter
60
+ configuration.email_adapter.constantize.new
61
+ rescue NameError => e
62
+ raise Notifications::AdapterError,
63
+ "Notifications.configuration.email_adapter is set to " \
64
+ "#{configuration.email_adapter.inspect}, which could not be resolved: #{e.message}"
65
+ end
66
+
67
+ def sms_adapter
68
+ configuration.sms_adapter.constantize.new
69
+ rescue NameError => e
70
+ raise Notifications::AdapterError,
71
+ "Notifications.configuration.sms_adapter is set to " \
72
+ "#{configuration.sms_adapter.inspect}, which could not be resolved: #{e.message}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notifications
4
+ # Registry of typed notification semantics — distinct from the STI
5
+ # strategy classes (InApp / Email / Sms), which are *delivery
6
+ # channels*, not *what the notification is about*.
7
+ #
8
+ # A NotificationType pairs a stable name (e.g. "billing.invoice_paid")
9
+ # with a template path, the channels it can fan out to, and a human
10
+ # display name. Hosts register types up front in an initializer so
11
+ # the host has a closed-set list to power preference UIs and audit
12
+ # queries.
13
+ #
14
+ # # config/initializers/notifications.rb
15
+ # Notifications::TypeRegistry.register("billing.invoice_paid",
16
+ # template: "billing/invoice_paid",
17
+ # channels: %i[in_app email],
18
+ # display: "Invoice paid")
19
+ #
20
+ # user.notify_typed(type: "billing.invoice_paid")
21
+ #
22
+ # The engine seeds a small default set on boot (see
23
+ # +Notifications.seed_default_types!+); hosts override or extend
24
+ # in their initializer.
25
+ module TypeRegistry
26
+ Type = Struct.new(:name, :template, :channels, :display, keyword_init: true) do
27
+ def supports_channel?(channel)
28
+ channels.map(&:to_sym).include?(channel.to_sym)
29
+ end
30
+
31
+ def to_s
32
+ name
33
+ end
34
+ end
35
+
36
+ UnknownType = Class.new(StandardError)
37
+
38
+ @types = {}
39
+ @mutex = Mutex.new
40
+
41
+ module_function
42
+
43
+ def register(name, template:, channels:, display: nil)
44
+ @mutex.synchronize do
45
+ @types[name.to_s] = Type.new(
46
+ name: name.to_s,
47
+ template: template.to_s,
48
+ channels: Array(channels).map(&:to_sym),
49
+ display: display || name.to_s.tr("._", " ").capitalize
50
+ )
51
+ end
52
+ end
53
+
54
+ def find(name)
55
+ @types[name.to_s]
56
+ end
57
+
58
+ def fetch(name)
59
+ find(name) || raise(UnknownType, "no notification type registered as #{name.inspect}")
60
+ end
61
+
62
+ def all
63
+ @types.values
64
+ end
65
+
66
+ def names
67
+ @types.keys
68
+ end
69
+
70
+ def reset!
71
+ @mutex.synchronize { @types = {} }
72
+ end
73
+ end
74
+ end