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,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "seams"
5
+ require "seams/generators/follow_up_generator"
6
+
7
+ module Seams
8
+ module Generators
9
+ module Auth
10
+ # Adds a new OAuth provider adapter to an already-generated Auth
11
+ # engine. The first showcase follow-up generator built on top of
12
+ # the Wave 10 Phase 1 insertion-point machinery.
13
+ #
14
+ # Run with:
15
+ #
16
+ # bin/rails generate seams:auth:add_oauth_provider linkedin
17
+ # bin/rails generate seams:auth:add_oauth_provider apple
18
+ # bin/rails generate seams:auth:add_oauth_provider microsoft
19
+ #
20
+ # What it does:
21
+ #
22
+ # 1. Creates engines/auth/lib/auth/oauth/<name>.rb (subclasses
23
+ # Auth::OAuth::Abstract, with TODOs the host fills in).
24
+ # 2. Splices a configuration entry into
25
+ # engines/auth/lib/auth/configuration.rb at the
26
+ # `auth.configuration.oauth_providers` marker.
27
+ # 3. Creates engines/auth/spec/lib/auth/oauth/<name>_spec.rb
28
+ # covering the abstract contract.
29
+ #
30
+ # The existing `scope "/oauth/:provider"` route in
31
+ # engines/auth/config/routes.rb already handles every configured
32
+ # provider via the `:provider` param — the generator does NOT
33
+ # splice into auth.routes.after_oauth or any other route marker.
34
+ # Likewise, the `_oauth_buttons.html.erb` partial iterates
35
+ # `Auth.configuration.oauth_providers.each_key`, so the new entry
36
+ # auto-renders without a partial edit.
37
+ #
38
+ # Idempotent on rerun: the underlying Splicer detects the splice
39
+ # has already happened and skips. The adapter file itself is
40
+ # written via `template`, which Thor's --skip flag (default)
41
+ # leaves alone if it already exists.
42
+ class AddOauthProviderGenerator < Seams::Generators::FollowUpGenerator
43
+ engine_name "auth"
44
+
45
+ source_root File.expand_path("templates", __dir__)
46
+
47
+ argument :name, type: :string, banner: "<provider>",
48
+ desc: "The provider name (e.g. linkedin, apple, microsoft)"
49
+
50
+ # Provider names share the engine-name regex: lowercase letters,
51
+ # digits, underscores, starting with a letter. Hyphens, mixed
52
+ # case, and dots are normalised to underscores by `snake_name`.
53
+ NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
54
+
55
+ CONFIGURATION_FILE = "lib/auth/configuration.rb"
56
+ CONFIGURATION_MARKER = "auth.configuration.oauth_providers"
57
+
58
+ def normalise_name
59
+ @snake_name = name.to_s.strip.downcase
60
+ .gsub(/[^a-z0-9]+/, "_").squeeze("_")
61
+ .gsub(/^_|_$/, "")
62
+
63
+ if @snake_name.empty?
64
+ raise Seams::GeneratorError,
65
+ "Provider name #{name.inspect} normalises to an empty string. " \
66
+ "Pass a name like `linkedin`, `apple`, or `microsoft`."
67
+ end
68
+
69
+ return if NAME_PATTERN.match?(@snake_name)
70
+
71
+ raise Seams::GeneratorError,
72
+ "Provider name #{name.inspect} must be lowercase letters, digits, " \
73
+ "and underscores, starting with a letter."
74
+ end
75
+
76
+ def assert_engine_present
77
+ assert_marker_exists!(file: CONFIGURATION_FILE, marker: CONFIGURATION_MARKER)
78
+ end
79
+
80
+ def create_adapter
81
+ template "adapter.rb.tt", engine_path("lib/auth/oauth/#{snake_name}.rb")
82
+ end
83
+
84
+ def splice_configuration_entry
85
+ splice(
86
+ file: CONFIGURATION_FILE,
87
+ marker: CONFIGURATION_MARKER,
88
+ content: configuration_entry
89
+ )
90
+ end
91
+
92
+ def create_adapter_spec
93
+ template "adapter_spec.rb.tt",
94
+ engine_path("spec/lib/auth/oauth/#{snake_name}_spec.rb")
95
+ end
96
+
97
+ # rubocop:disable Metrics/AbcSize
98
+ def report_summary
99
+ say ""
100
+ say " Auth OAuth provider `#{snake_name}` added.", :green
101
+ say ""
102
+ say " Files:", :yellow
103
+ say " engines/auth/lib/auth/oauth/#{snake_name}.rb"
104
+ say " engines/auth/spec/lib/auth/oauth/#{snake_name}_spec.rb"
105
+ say " Spliced:", :yellow
106
+ say " engines/auth/lib/auth/configuration.rb @ #{CONFIGURATION_MARKER}"
107
+ say ""
108
+ say " Routes: the existing `scope \"/oauth/:provider\"` block matches", :blue
109
+ say " every configured provider — no route splice needed."
110
+ say " View: the engine's _oauth_buttons.html.erb partial iterates", :blue
111
+ say " Auth.configuration.oauth_providers — no partial edit needed."
112
+ say ""
113
+ say " Next steps:", :yellow
114
+ say " 1. Set #{upper_name}_OAUTH_CLIENT_ID and #{upper_name}_OAUTH_CLIENT_SECRET in your environment."
115
+ say " 2. Replace the TODO placeholders in lib/auth/oauth/#{snake_name}.rb with"
116
+ say " the provider's real OAuth endpoints + userinfo mapping."
117
+ say " 3. Run the spec: bin/rails seams:test[auth] (or rspec the new file)."
118
+ say " 4. Test the OAuth flow against the provider's sandbox."
119
+ say ""
120
+ say " Want to customise the adapter beyond the TODOs (override behaviour, change the", :blue
121
+ say " spec shape, etc.)? Eject it so subsequent regenerations of auth leave it alone:"
122
+ say " bin/seams resolve --eject auth/lib/auth/oauth/#{snake_name}.rb"
123
+ say ""
124
+ end
125
+ # rubocop:enable Metrics/AbcSize
126
+
127
+ private
128
+
129
+ # Exposed to the templates via ERB binding.
130
+ attr_reader :snake_name
131
+
132
+ def camel_name
133
+ snake_name.split("_").map(&:capitalize).join
134
+ end
135
+
136
+ def upper_name
137
+ snake_name.upcase
138
+ end
139
+
140
+ # The hash entry to splice after the marker. Two-space indent
141
+ # inside the entry so the splicer's auto-detected outer indent
142
+ # (8 spaces, matching the marker line) lands the inner keys at
143
+ # 10 spaces — same shape Phase 2A's marker placement implies.
144
+ def configuration_entry
145
+ <<~RUBY
146
+ #{snake_name}: {
147
+ adapter: "Auth::OAuth::#{camel_name}",
148
+ client_id: ENV.fetch("#{upper_name}_OAUTH_CLIENT_ID", nil),
149
+ client_secret: ENV.fetch("#{upper_name}_OAUTH_CLIENT_SECRET", nil),
150
+ scopes: %w[profile email]
151
+ },
152
+ RUBY
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "auth/oauth/abstract"
4
+
5
+ module Auth
6
+ module OAuth
7
+ # <%= camel_name %> OAuth 2.0 adapter — generated by
8
+ # `bin/rails generate seams:auth:add_oauth_provider <%= snake_name %>`.
9
+ #
10
+ # This is a starting point: the URLs, scopes, and userinfo mapping
11
+ # below are placeholders. Fill them in by following the provider's
12
+ # OAuth documentation, then delete each TODO once verified.
13
+ #
14
+ # Provider docs to consult (replace with the canonical URLs):
15
+ #
16
+ # https://example.com/<%= snake_name %>/oauth/docs
17
+ # https://example.com/<%= snake_name %>/oauth/userinfo
18
+ #
19
+ # The host wires this adapter via Auth.configuration.oauth_providers
20
+ # (the generator already added a stub entry there). Set the
21
+ # <%= upper_name %>_OAUTH_CLIENT_ID and <%= upper_name %>_OAUTH_CLIENT_SECRET environment
22
+ # variables before exercising the OAuth flow.
23
+ class <%= camel_name %> < Abstract
24
+ # TODO(<%= snake_name %>): replace with the provider's authorization endpoint.
25
+ AUTHORIZE_URL = "https://example.com/<%= snake_name %>/oauth/authorize"
26
+ # TODO(<%= snake_name %>): replace with the provider's token endpoint.
27
+ TOKEN_URL = "https://example.com/<%= snake_name %>/oauth/token"
28
+ # TODO(<%= snake_name %>): replace with the provider's userinfo endpoint.
29
+ USERINFO_URL = "https://example.com/<%= snake_name %>/oauth/userinfo"
30
+
31
+ # TODO(<%= snake_name %>): pick the minimum scopes that yield email + a stable
32
+ # provider_uid. Most providers gate `email` behind a named scope.
33
+ DEFAULT_SCOPES = %w[profile email].freeze
34
+
35
+ def initialize(client_id:, client_secret:, scopes: DEFAULT_SCOPES)
36
+ super
37
+ end
38
+
39
+ # TODO(<%= snake_name %>): tailor params to the provider. Many providers want
40
+ # `response_type=code` + `state=<csrf>` + `scope=<space-joined>`;
41
+ # some require `response_mode`, `nonce`, or `audience`.
42
+ def authorize_url(state:, redirect_uri:)
43
+ params = {
44
+ client_id: client_id,
45
+ redirect_uri: redirect_uri,
46
+ response_type: "code",
47
+ scope: scopes.join(" "),
48
+ state: state
49
+ }
50
+ "#{AUTHORIZE_URL}?#{URI.encode_www_form(params)}"
51
+ end
52
+
53
+ # TODO(<%= snake_name %>): some providers want JSON, some want form-urlencoded;
54
+ # some require `Authorization: Basic` instead of body credentials.
55
+ # Verify against the provider's token-exchange docs.
56
+ def exchange_code(code:, redirect_uri:)
57
+ response = conn(TOKEN_URL).post("", {
58
+ client_id: client_id,
59
+ client_secret: client_secret,
60
+ code: code,
61
+ redirect_uri: redirect_uri,
62
+ grant_type: "authorization_code"
63
+ })
64
+ assert_success!(response, action: "token exchange")
65
+ body = parse_json(response, action: "token exchange")
66
+ {
67
+ access_token: body["access_token"],
68
+ refresh_token: body["refresh_token"],
69
+ expires_in: body["expires_in"],
70
+ token_type: body["token_type"] || "Bearer",
71
+ scope: body["scope"]
72
+ }
73
+ end
74
+
75
+ # TODO(<%= snake_name %>): map the provider's userinfo response onto Profile.
76
+ # `provider_uid` MUST be stable across logins (an email isn't —
77
+ # users can change them); prefer the provider's numeric/uuid id.
78
+ def fetch_user_info(access_token:)
79
+ response = conn(USERINFO_URL).get("") do |req|
80
+ req.headers["Authorization"] = "Bearer #{access_token}"
81
+ end
82
+ assert_success!(response, action: "userinfo")
83
+ body = parse_json(response, action: "userinfo")
84
+ Profile.new(
85
+ provider_uid: body["id"] || body["sub"],
86
+ email: body["email"],
87
+ email_verified: body["email_verified"],
88
+ name: body["name"],
89
+ avatar_url: body["avatar_url"] || body["picture"],
90
+ raw: body
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+ require "auth/oauth/<%= snake_name %>"
5
+
6
+ RSpec.describe Auth::OAuth::<%= camel_name %> do
7
+ let(:client_id) { "test-client-id" }
8
+ let(:client_secret) { "test-client-secret" }
9
+
10
+ subject(:adapter) do
11
+ described_class.new(
12
+ client_id: client_id,
13
+ client_secret: client_secret
14
+ )
15
+ end
16
+
17
+ describe "the abstract contract" do
18
+ it "is a subclass of Auth::OAuth::Abstract" do
19
+ expect(described_class.ancestors).to include(Auth::OAuth::Abstract)
20
+ end
21
+
22
+ it "exposes default scopes appropriate for the provider" do
23
+ expect(described_class::DEFAULT_SCOPES).to be_a(Array)
24
+ expect(described_class::DEFAULT_SCOPES).not_to be_empty
25
+ end
26
+ end
27
+
28
+ describe "#authorize_url" do
29
+ # Until the TODOs in lib/auth/oauth/<%= snake_name %>.rb are filled in,
30
+ # the URL still points at the example.com placeholder. This test
31
+ # documents that — and fails the moment a host overrides the
32
+ # AUTHORIZE_URL with a real endpoint, which is when this spec
33
+ # should be replaced with provider-specific assertions.
34
+ it "builds an authorize URL containing the state and redirect_uri" do
35
+ url = adapter.authorize_url(state: "csrf-token", redirect_uri: "https://host.example/callback")
36
+
37
+ expect(url).to include("state=csrf-token")
38
+ expect(url).to include("redirect_uri=")
39
+ expect(url).to include("client_id=#{client_id}")
40
+ end
41
+ end
42
+
43
+ describe "#exchange_code" do
44
+ # TODO(<%= snake_name %>): replace this with a Faraday stub once the
45
+ # provider's token endpoint is wired up.
46
+ it "is implemented (raises only if the abstract methods are kept)" do
47
+ expect(adapter.method(:exchange_code).owner).to eq(described_class)
48
+ end
49
+ end
50
+
51
+ describe "#fetch_user_info" do
52
+ # TODO(<%= snake_name %>): replace this with a Faraday stub once the
53
+ # provider's userinfo endpoint is wired up.
54
+ it "is implemented (raises only if the abstract methods are kept)" do
55
+ expect(adapter.method(:fetch_user_info).owner).to eq(described_class)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,311 @@
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 Auth engine on top of the generic engine
14
+ # scaffold. Adds:
15
+ #
16
+ # - Identity and Session ActiveRecord models (has_secure_password).
17
+ # - SessionsController + RegistrationsController with sign in /
18
+ # sign up / sign out.
19
+ # - Authenticatable concern that the host application's user-like
20
+ # model can `include Auth::Authenticatable` (added to the
21
+ # engine's ExposedConcerns automatically) — OPTIONAL post-Wave-9.
22
+ # - Migrations for auth_identities and auth_sessions.
23
+ # - lib/auth/engine.rb registers the canonical events:
24
+ # identity.signed_up.auth, identity.signed_in.auth,
25
+ # identity.signed_out.auth, session.expired.auth.
26
+ #
27
+ # Run with: bin/rails generate seams:auth
28
+ # rubocop:disable Metrics/ClassLength
29
+ class AuthGenerator < Rails::Generators::Base
30
+ include Seams::Generators::HostInjector
31
+ include Seams::Generators::EjectAware
32
+
33
+ source_root File.expand_path("templates", __dir__)
34
+
35
+ ENGINE_NAME = "auth"
36
+
37
+ def create_base_engine
38
+ EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
39
+ end
40
+
41
+ def overwrite_engine_entry_point
42
+ # engine.rb / lib/auth.rb stay framework-managed (NOT
43
+ # eject-eligible — see Seams::Generators::EjectAware).
44
+ # configuration.rb is eject-eligible so a host that has
45
+ # ejected it keeps its overrides on regenerate.
46
+ template "lib/engine.rb.tt", engine_path("lib/auth/engine.rb"), force: true
47
+ template "lib/auth.rb.tt", engine_path("lib/auth.rb"), force: true
48
+ template_unless_ejected "lib/configuration.rb.tt",
49
+ engine_path("lib/auth/configuration.rb")
50
+ end
51
+
52
+ def overwrite_routes
53
+ template_unless_ejected "config/routes.rb.tt", engine_path("config/routes.rb"), force: true
54
+ end
55
+
56
+ def create_models
57
+ template_unless_ejected "app/models/application_record.rb.tt",
58
+ engine_path("app/models/auth/application_record.rb")
59
+ template_unless_ejected "app/models/identity.rb.tt",
60
+ engine_path("app/models/auth/identity.rb")
61
+ template_unless_ejected "app/models/session.rb.tt",
62
+ engine_path("app/models/auth/session.rb")
63
+ template_unless_ejected "app/models/current.rb.tt",
64
+ engine_path("app/models/auth/current.rb")
65
+ # OAuth identity link table — issue #2 section 2A. Lives under
66
+ # the Auth::OAuth namespace alongside the lib/ adapter classes
67
+ # (Abstract, Google, Github) so the Zeitwerk inflector only
68
+ # needs the single "oauth" => "OAuth" mapping at engine boot.
69
+ template_unless_ejected "app/models/oauth/provider.rb.tt",
70
+ engine_path("app/models/auth/oauth/provider.rb")
71
+ # Bearer-token API access — issue #2 section 2A.
72
+ template_unless_ejected "app/models/api_token.rb.tt",
73
+ engine_path("app/models/auth/api_token.rb")
74
+ end
75
+
76
+ def create_controllers
77
+ template_unless_ejected "app/controllers/sessions_controller.rb.tt",
78
+ engine_path("app/controllers/auth/sessions_controller.rb")
79
+ template_unless_ejected "app/controllers/registrations_controller.rb.tt",
80
+ engine_path("app/controllers/auth/registrations_controller.rb")
81
+ template_unless_ejected "app/controllers/password_resets_controller.rb.tt",
82
+ engine_path("app/controllers/auth/password_resets_controller.rb")
83
+ template_unless_ejected "app/controllers/oauth/callbacks_controller.rb.tt",
84
+ engine_path("app/controllers/auth/oauth/callbacks_controller.rb")
85
+ end
86
+
87
+ def create_services
88
+ template_unless_ejected "app/services/register_identity.rb.tt",
89
+ engine_path("app/services/auth/register_identity.rb")
90
+ template_unless_ejected "app/services/authenticate_identity.rb.tt",
91
+ engine_path("app/services/auth/authenticate_identity.rb")
92
+ template_unless_ejected "app/services/reset_password.rb.tt",
93
+ engine_path("app/services/auth/reset_password.rb")
94
+ template_unless_ejected "app/services/oauth/authenticator.rb.tt",
95
+ engine_path("app/services/auth/oauth/authenticator.rb")
96
+ template_unless_ejected "app/services/generate_api_token.rb.tt",
97
+ engine_path("app/services/auth/generate_api_token.rb")
98
+ # Phase Wave 5 (review fix): the revoke path was documented in
99
+ # the README + registered in engine.rb but had no implementer.
100
+ template_unless_ejected "app/services/revoke_api_token.rb.tt",
101
+ engine_path("app/services/auth/revoke_api_token.rb")
102
+ end
103
+
104
+ def create_jobs
105
+ template_unless_ejected "app/jobs/application_job.rb.tt",
106
+ engine_path("app/jobs/auth/application_job.rb")
107
+ template_unless_ejected "app/jobs/cleanup_expired_sessions_job.rb.tt",
108
+ engine_path("app/jobs/auth/cleanup_expired_sessions_job.rb")
109
+ end
110
+
111
+ def create_rake_tasks
112
+ # Wave 11: PII rotation task for hosts upgrading from Wave ≤10.
113
+ template_unless_ejected "lib/tasks/auth_pii.rake.tt",
114
+ engine_path("lib/tasks/auth_pii.rake")
115
+ end
116
+
117
+ def create_oauth_adapters
118
+ template_unless_ejected "lib/oauth/abstract.rb.tt",
119
+ engine_path("lib/auth/oauth/abstract.rb")
120
+ template_unless_ejected "lib/oauth/google.rb.tt",
121
+ engine_path("lib/auth/oauth/google.rb")
122
+ template_unless_ejected "lib/oauth/github.rb.tt",
123
+ engine_path("lib/auth/oauth/github.rb")
124
+ end
125
+
126
+ def create_mailer
127
+ template_unless_ejected "app/mailers/passwords_mailer.rb.tt",
128
+ engine_path("app/mailers/auth/passwords_mailer.rb")
129
+ template_unless_ejected "app/views/passwords_mailer/reset_email.html.erb.tt",
130
+ engine_path("app/views/auth/passwords_mailer/reset_email.html.erb")
131
+ end
132
+
133
+ def create_views
134
+ template_unless_ejected "app/views/sessions/new.html.erb.tt",
135
+ engine_path("app/views/auth/sessions/new.html.erb")
136
+ template_unless_ejected "app/views/sessions/_oauth_buttons.html.erb.tt",
137
+ engine_path("app/views/auth/sessions/_oauth_buttons.html.erb")
138
+ template_unless_ejected "app/views/registrations/new.html.erb.tt",
139
+ engine_path("app/views/auth/registrations/new.html.erb")
140
+ template_unless_ejected "app/views/password_resets/new.html.erb.tt",
141
+ engine_path("app/views/auth/password_resets/new.html.erb")
142
+ template_unless_ejected "app/views/password_resets/edit.html.erb.tt",
143
+ engine_path("app/views/auth/password_resets/edit.html.erb")
144
+ end
145
+
146
+ def create_concerns
147
+ template_unless_ejected "lib/concerns/authenticatable.rb.tt",
148
+ engine_path("lib/auth/concerns/authenticatable.rb")
149
+ template_unless_ejected "lib/concerns/authentication.rb.tt",
150
+ engine_path("lib/auth/concerns/authentication.rb")
151
+ # Bearer-token controller auth for API endpoints.
152
+ template_unless_ejected "lib/concerns/api_authenticatable.rb.tt",
153
+ engine_path("lib/auth/concerns/api_authenticatable.rb")
154
+ end
155
+
156
+ def create_migrations
157
+ template "db/migrate/create_auth_identities.rb.tt",
158
+ engine_path("db/migrate/#{timestamp(0)}_create_auth_identities.rb")
159
+ template "db/migrate/create_auth_sessions.rb.tt",
160
+ engine_path("db/migrate/#{timestamp(1)}_create_auth_sessions.rb")
161
+ template "db/migrate/create_auth_oauth_providers.rb.tt",
162
+ engine_path("db/migrate/#{timestamp(2)}_create_auth_oauth_providers.rb")
163
+ template "db/migrate/create_auth_api_tokens.rb.tt",
164
+ engine_path("db/migrate/#{timestamp(3)}_create_auth_api_tokens.rb")
165
+ end
166
+
167
+ def create_specs
168
+ template_unless_ejected "spec/models/identity_spec.rb.tt",
169
+ engine_path("spec/models/auth/identity_spec.rb")
170
+ template_unless_ejected "spec/models/session_spec.rb.tt",
171
+ engine_path("spec/models/auth/session_spec.rb")
172
+ # Phase 2A finish — coverage for the new Wave-9/10 models.
173
+ template_unless_ejected "spec/models/api_token_spec.rb.tt",
174
+ engine_path("spec/models/auth/api_token_spec.rb")
175
+ template_unless_ejected "spec/models/oauth/provider_spec.rb.tt",
176
+ engine_path("spec/models/auth/oauth/provider_spec.rb")
177
+ template_unless_ejected "spec/mailers/passwords_mailer_spec.rb.tt",
178
+ engine_path("spec/mailers/auth/passwords_mailer_spec.rb")
179
+ template_unless_ejected "spec/factories/auth.rb.tt",
180
+ engine_path("spec/factories/auth.rb")
181
+ end
182
+
183
+ def overwrite_readme
184
+ template "README.md.tt", engine_path("README.md"), force: true
185
+ end
186
+
187
+ def update_exposed_concerns
188
+ rubocop_path = engine_path(".rubocop.yml")
189
+ return unless File.exist?(rubocop_path)
190
+
191
+ contents = File.read(rubocop_path)
192
+ replacement = " ExposedConcerns:\n - Auth::Authenticatable\n - Auth::Authentication"
193
+ contents.sub!(/^ ExposedConcerns: \[\]$/, replacement)
194
+ File.write(rubocop_path, contents)
195
+ end
196
+
197
+ def create_dummy_app
198
+ # Post Wave 9: the dummy app no longer ships a host User
199
+ # model. Auth::Identity is the human now, and the auth specs
200
+ # don't need a host User to exercise the engine. Hosts that
201
+ # keep a User model can include Auth::Authenticatable in their
202
+ # own test fixture.
203
+ Seams::Generators::DummyAppWriter.write!(
204
+ engine_path: File.join(destination_root, "engines", ENGINE_NAME),
205
+ engine_module: "Auth",
206
+ mount_at: "/auth",
207
+ schema: dummy_schema
208
+ )
209
+ template "spec/runtime/boot_spec.rb.tt",
210
+ engine_path("spec/runtime/auth_boot_spec.rb")
211
+ template "spec/runtime/event_payload_spec.rb.tt",
212
+ engine_path("spec/runtime/auth_event_payload_spec.rb")
213
+ # Phase 2A finish — login flow round-trip via the request stack.
214
+ template "spec/runtime/login_flow_spec.rb.tt",
215
+ engine_path("spec/runtime/auth_login_flow_spec.rb")
216
+ end
217
+
218
+ def wire_into_host
219
+ host_inject_gem("bcrypt", "~> 3.1")
220
+ # OAuth adapters speak HTTP via Faraday (no Net::HTTP — see
221
+ # memory feedback_external_apis.md). The host needs Faraday on
222
+ # the load path even if it never configures an OAuth provider.
223
+ host_inject_gem("faraday", "~> 2.0")
224
+ # factory_bot_rails powers the engine's spec/factories/*. Lives
225
+ # in the host's test group only.
226
+ host_inject_gem("factory_bot_rails", "~> 6.4", group: :test)
227
+ host_inject_mount(engine_class: "Auth::Engine", at: "/auth")
228
+ # NB: post-Wave-9 the canonical host has no `app/models/user.rb`,
229
+ # so this call is a best-effort no-op (the helper logs `skip`
230
+ # and moves on). Hosts that DO maintain a domain User on top
231
+ # of `Auth::Identity` get the include wired automatically.
232
+ host_inject_include_in_user("Auth::Authenticatable")
233
+ host_inject_include_in_application_controller("Auth::Authentication")
234
+ end
235
+
236
+ def report_summary
237
+ say ""
238
+ say " Auth engine generated at engines/auth/", :green
239
+ say ""
240
+ say " Next steps:", :yellow
241
+ say " 1. bundle install (picks up bcrypt + Auth::Engine)"
242
+ say " 2. bin/rails db:migrate"
243
+ say " 3. Run the engine specs: bin/rails seams:test[auth]"
244
+ say ""
245
+ end
246
+
247
+ private
248
+
249
+ def engine_path(relative)
250
+ File.join(destination_root, "engines", ENGINE_NAME, relative)
251
+ end
252
+
253
+ def timestamp(offset)
254
+ # Microsecond-resolution timestamp so migrations generated
255
+ # back-to-back (and across sibling generators in the same
256
+ # second) don't collide. The 14-digit format is what Rails
257
+ # uses for its own generators.
258
+ base = Time.now.utc
259
+ seconds = base.strftime("%Y%m%d%H%M%S").to_i
260
+ (seconds + offset).to_s
261
+ end
262
+
263
+ def dummy_schema
264
+ <<~SCHEMA
265
+ create_table :auth_identities do |t|
266
+ t.text :email, null: false
267
+ t.string :password_digest, null: false
268
+ t.boolean :staff, null: false, default: false
269
+ t.timestamps
270
+ end
271
+ add_index :auth_identities, :email, unique: true
272
+ add_index :auth_identities, :staff, where: "staff = true"
273
+
274
+ create_table :auth_sessions do |t|
275
+ t.references :identity, null: false, foreign_key: { to_table: :auth_identities }
276
+ t.string :token, null: false
277
+ t.datetime :expires_at, null: false
278
+ t.timestamps
279
+ end
280
+ add_index :auth_sessions, :token, unique: true
281
+
282
+ create_table :auth_oauth_providers do |t|
283
+ t.references :identity, null: false, foreign_key: { to_table: :auth_identities }
284
+ t.string :provider, null: false
285
+ t.text :provider_uid, null: false
286
+ t.text :access_token
287
+ t.text :refresh_token
288
+ t.datetime :expires_at
289
+ t.string :token_type, default: "Bearer"
290
+ t.jsonb :profile_data, null: false, default: {}
291
+ t.timestamps
292
+ end
293
+ add_index :auth_oauth_providers, %i[provider provider_uid], unique: true
294
+ add_index :auth_oauth_providers, %i[identity_id provider], unique: true
295
+
296
+ create_table :auth_api_tokens do |t|
297
+ t.references :identity, null: false, foreign_key: { to_table: :auth_identities }
298
+ t.string :name, null: false
299
+ t.string :token_digest, null: false
300
+ t.string :token_prefix, null: false
301
+ t.datetime :expires_at
302
+ t.datetime :last_used_at
303
+ t.timestamps
304
+ end
305
+ add_index :auth_api_tokens, :token_digest, unique: true
306
+ SCHEMA
307
+ end
308
+ end
309
+ # rubocop:enable Metrics/ClassLength
310
+ end
311
+ end