seams 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +212 -12
- data/README.md +235 -82
- data/lib/generators/seams/accounts/accounts_generator.rb +31 -3
- data/lib/generators/seams/accounts/templates/app/controllers/memberships_controller.rb.tt +100 -0
- data/lib/generators/seams/accounts/templates/app/views/memberships/index.html.erb.tt +52 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +10 -4
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +35 -2
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +13 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/authorization_spec.rb.tt +102 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/memberships_flow_spec.rb.tt +145 -0
- data/lib/generators/seams/admin/admin_generator.rb +51 -4
- data/lib/generators/seams/admin/templates/README.md.tt +33 -3
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +156 -8
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +9 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +9 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +7 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +12 -2
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +7 -1
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/belongs_to.rb.tt +12 -0
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/dashboard_option.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/has_many.rb.tt +12 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +4 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +51 -10
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +4 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +7 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_index_header.html.erb.tt +57 -0
- data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_navigation.html.erb.tt +14 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +24 -14
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +3 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +3 -1
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +4 -2
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +1 -2
- data/lib/generators/seams/auth/auth_generator.rb +1 -2
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +3 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +4 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +5 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +5 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +5 -1
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +12 -1
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/billing/billing_generator.rb +4 -3
- data/lib/generators/seams/billing/templates/README.md.tt +26 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +2 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +2 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +8 -7
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +9 -7
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +18 -3
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +7 -21
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +10 -10
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +40 -14
- data/lib/generators/seams/billing/templates/lib/stripe/payload.rb.tt +90 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +11 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +15 -4
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +7 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +18 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +26 -5
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +17 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +47 -1
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +39 -1
- data/lib/generators/seams/design/design_generator.rb +647 -0
- data/lib/generators/seams/design/templates/README.md.tt +129 -0
- data/lib/generators/seams/design/templates/app/assets/tailwind/_tokens.css +322 -0
- data/lib/generators/seams/design/templates/app/assets/tailwind/themes/_quire.css +42 -0
- data/lib/generators/seams/design/templates/app/controllers/design/dashboard_controller.rb.tt +30 -0
- data/lib/generators/seams/design/templates/app/controllers/design/guide_controller.rb.tt +28 -0
- data/lib/generators/seams/design/templates/app/form_builders/design/form_builder.rb.tt +90 -0
- data/lib/generators/seams/design/templates/app/helpers/design/ui_helper.rb.tt +37 -0
- data/lib/generators/seams/design/templates/app/views/design/dashboard/index.html.erb.tt +38 -0
- data/lib/generators/seams/design/templates/app/views/design/guide/index.html.erb.tt +19 -0
- data/lib/generators/seams/design/templates/app/views/layouts/application.html.erb.tt +61 -0
- data/lib/generators/seams/design/templates/app/views/layouts/design/guide.html.erb.tt +21 -0
- data/lib/generators/seams/design/templates/app/views/ui/_banner.html.erb.tt +10 -0
- data/lib/generators/seams/design/templates/app/views/ui/_breadcrumb.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/_build_row.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_button.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/_card.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_chapter_row.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/app/views/ui/_checkbox.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_counter.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_data_table.html.erb.tt +25 -0
- data/lib/generators/seams/design/templates/app/views/ui/_dialog.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_diff.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_drawer.html.erb.tt +7 -0
- data/lib/generators/seams/design/templates/app/views/ui/_empty.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_field.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_icon.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_icon_sprite.html.erb.tt +22 -0
- data/lib/generators/seams/design/templates/app/views/ui/_input_group.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/app/views/ui/_kbd.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/_menu.html.erb.tt +18 -0
- data/lib/generators/seams/design/templates/app/views/ui/_meter.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/_note.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_outline.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/_pagination.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_panel.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_popover.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_radio.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_savestate.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_segmented.html.erb.tt +7 -0
- data/lib/generators/seams/design/templates/app/views/ui/_stepper.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_switch.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/_tag.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_toast.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_toolbar.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_banner.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_breadcrumb.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_build_row.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_button.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_card.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_chapter_row.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_checkbox.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_counter.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_data_table.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_dialog.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_diff.html.erb.tt +10 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_drawer.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_empty.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_field.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_input_group.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_kbd.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_menu.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_meter.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_note.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_outline.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_pagination.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_panel.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_popover.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_radio.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_savestate.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_segmented.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_stepper.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_switch.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_tag.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_toast.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_toolbar.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/lib/design/components.rb.tt +22 -0
- data/lib/generators/seams/design/templates/lib/design.rb.tt +18 -0
- data/lib/generators/seams/design/templates/lib/engine.rb.tt +38 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/component_generator.rb.tt +52 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/templates/component.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/templates/preview.html.erb.tt +3 -0
- data/lib/generators/seams/design/templates/spec/runtime/design_boot_spec.rb.tt +79 -0
- data/lib/generators/seams/design/templates/spec/runtime/form_builder_spec.rb.tt +97 -0
- data/lib/generators/seams/design/templates/spec/runtime/guide_spec.rb.tt +40 -0
- data/lib/generators/seams/design/templates/spec/runtime/ui_components_spec.rb.tt +70 -0
- data/lib/generators/seams/engine/engine_generator.rb +6 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +1 -1
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +9 -0
- data/lib/generators/seams/install/install_generator.rb +72 -6
- data/lib/generators/seams/install/templates/Dockerfile.tt +2 -2
- data/lib/generators/seams/install/templates/bin_seams.tt +7 -4
- data/lib/generators/seams/install/templates/ci.yml.tt +18 -8
- data/lib/generators/seams/install/templates/deploy.yml.tt +4 -2
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +1 -1
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +4 -2
- data/lib/generators/seams/install/templates/herb.yml.tt +10 -0
- data/lib/generators/seams/install/templates/lefthook.yml.tt +25 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -1
- data/lib/generators/seams/install/templates/seams.rake.tt +13 -1
- data/lib/generators/seams/install/templates/strong_migrations.rb.tt +26 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +3 -2
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +5 -13
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +1 -0
- data/lib/generators/seams/notifications/templates/lib/preferences.rb.tt +36 -0
- data/lib/generators/seams/permissions/permissions_generator.rb +97 -0
- data/lib/generators/seams/permissions/templates/config/initializers/seams_permissions.rb.tt +35 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +27 -11
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +5 -13
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +2 -4
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +13 -0
- data/lib/seams/cli/list.rb +15 -0
- data/lib/seams/cli/quality.rb +46 -5
- data/lib/seams/cli/resolve.rb +9 -5
- data/lib/seams/cli/test_changed.rb +5 -1
- data/lib/seams/cli.rb +20 -0
- data/lib/seams/configuration.rb +24 -1
- data/lib/seams/cops/no_cross_engine_dependency.rb +39 -7
- data/lib/seams/cops/no_cross_engine_model_access.rb +79 -1
- data/lib/seams/event_registry.rb +19 -0
- data/lib/seams/events/adapter.rb +9 -0
- data/lib/seams/events/adapters/active_support.rb +4 -0
- data/lib/seams/events/publisher.rb +29 -0
- data/lib/seams/events.rb +1 -0
- data/lib/seams/generators/dummy_app_writer.rb +21 -0
- data/lib/seams/generators/follow_up_generator.rb +1 -2
- data/lib/seams/generators/host_injector.rb +9 -2
- data/lib/seams/generators/splicer.rb +2 -2
- data/lib/seams/observability/adapter.rb +22 -0
- data/lib/seams/observability.rb +6 -0
- data/lib/seams/permission_registry.rb +66 -0
- data/lib/seams/permissions.rb +120 -0
- data/lib/seams/version.rb +1 -1
- data/lib/seams.rb +7 -0
- metadata +107 -3
|
@@ -23,6 +23,10 @@ module Seams
|
|
|
23
23
|
|
|
24
24
|
NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
|
|
25
25
|
|
|
26
|
+
class_option :skip_host_wiring, type: :boolean, default: false,
|
|
27
|
+
desc: "Do not mount the engine or add a host initializer " \
|
|
28
|
+
"(for canonical generators that wire the host themselves)"
|
|
29
|
+
|
|
26
30
|
def validate_name
|
|
27
31
|
unless NAME_PATTERN.match?(name)
|
|
28
32
|
raise Seams::GeneratorError,
|
|
@@ -107,6 +111,8 @@ module Seams
|
|
|
107
111
|
end
|
|
108
112
|
|
|
109
113
|
def wire_into_host
|
|
114
|
+
return if options[:skip_host_wiring]
|
|
115
|
+
|
|
110
116
|
# Mount the engine into the host's routes (idempotent — skips
|
|
111
117
|
# if the line already exists, so canonical generators that
|
|
112
118
|
# call this and ALSO mount themselves are safe).
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module <%= module_name %>
|
|
4
|
+
# Enforces authentication on every action by default. Mount points
|
|
5
|
+
# that should be reachable while signed out (e.g. password reset,
|
|
6
|
+
# invitation accept-via-token) must opt out explicitly:
|
|
7
|
+
#
|
|
8
|
+
# skip_before_action :authenticate_identity!, only: %i[accept_form accept]
|
|
4
9
|
class ApplicationController < ::ApplicationController
|
|
10
|
+
before_action :authenticate_identity!
|
|
5
11
|
end
|
|
6
12
|
end
|
|
@@ -32,16 +32,25 @@ Metrics/MethodLength:
|
|
|
32
32
|
# host application here so this engine cannot reach into them
|
|
33
33
|
# accidentally. Add concerns the other engines expose (e.g.
|
|
34
34
|
# `Billing::Billable`) to ExposedConcerns to allow `include` calls.
|
|
35
|
+
#
|
|
36
|
+
# Both boundary cops exclude spec/**/*: boundaries are a
|
|
37
|
+
# production-code contract, and integration specs legitimately need to
|
|
38
|
+
# create real sibling records (e.g. `Auth::Identity.create!` in test
|
|
39
|
+
# setup) to exercise cross-engine behaviour.
|
|
35
40
|
Seams/NoCrossEngineModelAccess:
|
|
36
41
|
Enabled: true
|
|
37
42
|
OwnEngine: <%= module_name %>
|
|
38
43
|
OtherEngines: []
|
|
39
44
|
ExposedConcerns: []
|
|
45
|
+
Exclude:
|
|
46
|
+
- "spec/**/*"
|
|
40
47
|
|
|
41
48
|
Seams/NoCrossEngineDependency:
|
|
42
49
|
Enabled: true
|
|
43
50
|
OwnEngine: <%= name %>
|
|
44
51
|
OtherEngines: []
|
|
52
|
+
Exclude:
|
|
53
|
+
- "spec/**/*"
|
|
45
54
|
|
|
46
55
|
Seams/KnownQueueNames:
|
|
47
56
|
Enabled: true
|
|
@@ -18,6 +18,23 @@ module Seams
|
|
|
18
18
|
|
|
19
19
|
source_root File.expand_path("templates", __dir__)
|
|
20
20
|
|
|
21
|
+
# The quality toolchain is installed by DEFAULT — Seams is opinionated and
|
|
22
|
+
# sets a host up for success from the first CLI run. Opt out per tool with
|
|
23
|
+
# --no-strong-migrations / --no-lefthook.
|
|
24
|
+
#
|
|
25
|
+
# herb is the exception and is OPT-IN: it ships an arm64-darwin-only native
|
|
26
|
+
# gem with no Linux build, so on a Rails 8 multi-platform lock (which lists
|
|
27
|
+
# the Linux deploy platforms) `bundle` fails and the host won't boot. A
|
|
28
|
+
# fresh install booting matters more than a linter being on, so enable it
|
|
29
|
+
# explicitly with --herb once your lock can resolve it.
|
|
30
|
+
class_option :herb, type: :boolean, default: false,
|
|
31
|
+
desc: "Install herb (HTML+ERB linter) + .herb.yml + CI/hook lint step. " \
|
|
32
|
+
"Off by default: herb is arm64-darwin-only and breaks multi-platform locks."
|
|
33
|
+
class_option :strong_migrations, type: :boolean, default: true,
|
|
34
|
+
desc: "Install strong_migrations + initializer + CI migration-safety step"
|
|
35
|
+
class_option :lefthook, type: :boolean, default: true,
|
|
36
|
+
desc: "Install lefthook + lefthook.yml (pre-commit lint, pre-push specs)"
|
|
37
|
+
|
|
21
38
|
def create_initializer
|
|
22
39
|
template "seams.rb.tt", "config/initializers/seams.rb"
|
|
23
40
|
end
|
|
@@ -129,6 +146,41 @@ module Seams
|
|
|
129
146
|
File.chmod(0o755, full_path) if File.exist?(full_path)
|
|
130
147
|
end
|
|
131
148
|
|
|
149
|
+
# --- Quality toolchain (default on; opt out per tool) -----------------
|
|
150
|
+
|
|
151
|
+
def create_herb_config
|
|
152
|
+
return unless options[:herb]
|
|
153
|
+
|
|
154
|
+
template_if_missing "herb.yml.tt", ".herb.yml"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def create_strong_migrations_initializer
|
|
158
|
+
return unless options[:strong_migrations]
|
|
159
|
+
|
|
160
|
+
template_if_missing "strong_migrations.rb.tt", "config/initializers/strong_migrations.rb"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def create_lefthook_config
|
|
164
|
+
return unless options[:lefthook]
|
|
165
|
+
|
|
166
|
+
template_if_missing "lefthook.yml.tt", "lefthook.yml"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def install_quality_gems
|
|
170
|
+
# The CI workflow (and lefthook) shell out to these, so make sure the
|
|
171
|
+
# gems are in the host Gemfile. All idempotent — skipped if present.
|
|
172
|
+
host_inject_gem("rubocop", group: :development)
|
|
173
|
+
host_inject_gem("brakeman", group: :development)
|
|
174
|
+
host_inject_gem("bundler-audit", group: :development)
|
|
175
|
+
host_inject_gem("herb", group: :development) if options[:herb]
|
|
176
|
+
host_inject_gem("lefthook", group: :development) if options[:lefthook]
|
|
177
|
+
# strong_migrations is a dev/CI guard — it vets migrations where they're
|
|
178
|
+
# written and in CI, and is kept OUT of the production path. The
|
|
179
|
+
# initializer is guarded with `if defined?(StrongMigrations)` so a
|
|
180
|
+
# production boot (where the gem isn't loaded) is unaffected.
|
|
181
|
+
host_inject_gem("strong_migrations", group: %i[development test]) if options[:strong_migrations]
|
|
182
|
+
end
|
|
183
|
+
|
|
132
184
|
# Phase 1.5 — per-host helper scripts and architecture doc.
|
|
133
185
|
def create_helper_scripts
|
|
134
186
|
template_if_missing "script/collate_coverage.rb.tt", "script/collate_coverage.rb"
|
|
@@ -149,8 +201,9 @@ module Seams
|
|
|
149
201
|
host_inject_gem("seams", "~> #{Seams::VERSION}")
|
|
150
202
|
# Every Seams host needs rspec-rails so the per-engine
|
|
151
203
|
# spec/dummy specs can actually run. Idempotent — skipped if
|
|
152
|
-
# the host already has these gems.
|
|
153
|
-
|
|
204
|
+
# the host already has these gems. The range lets bundler pick
|
|
205
|
+
# rspec-rails 8.x on Rails 7.2+ and fall back to 7.1.x on Rails 7.1.
|
|
206
|
+
host_inject_gem("rspec-rails", ">= 7.1", "< 9", group: :test)
|
|
154
207
|
end
|
|
155
208
|
|
|
156
209
|
private
|
|
@@ -166,12 +219,23 @@ module Seams
|
|
|
166
219
|
|
|
167
220
|
public
|
|
168
221
|
|
|
169
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
222
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
|
|
170
223
|
def post_install_message
|
|
171
224
|
say ""
|
|
172
225
|
say " Seams is installed. Generate your first engine with:", :green
|
|
173
226
|
say " bin/seams core (or bin/rails generate seams:core)"
|
|
174
227
|
say ""
|
|
228
|
+
say " Quality toolchain (installed by default — opt out with --no-<tool>):", :yellow
|
|
229
|
+
say " rubocop + brakeman + bundle-audit (in CI)" \
|
|
230
|
+
"#{", herb (HTML+ERB lint)" if options[:herb]}" \
|
|
231
|
+
"#{", strong_migrations" if options[:strong_migrations]}"
|
|
232
|
+
say " HTML+ERB linting (herb) is opt-in — add --herb (arm64-darwin only).", :yellow unless options[:herb]
|
|
233
|
+
say " bundle install then bin/rails db:prepare"
|
|
234
|
+
if options[:lefthook]
|
|
235
|
+
say " Activate the git hooks (pre-commit lint, pre-push specs):", :yellow
|
|
236
|
+
say " bundle exec lefthook install"
|
|
237
|
+
end
|
|
238
|
+
say ""
|
|
175
239
|
say " Canonical generators (run in this order):", :yellow
|
|
176
240
|
say " bin/seams core - Core engine (concerns, audit log)"
|
|
177
241
|
say " bin/seams auth - Auth engine (Identity, sessions, OAuth)"
|
|
@@ -184,6 +248,9 @@ module Seams
|
|
|
184
248
|
say " bin/seams admin - Admin engine (Administrate-backed dashboards"
|
|
185
249
|
say " for the canonical models, Pundit-gated,"
|
|
186
250
|
say " audit-log auto-write). Requires auth + accounts."
|
|
251
|
+
say " bin/seams design - Design engine (ui_* component partials,"
|
|
252
|
+
say " Tailwind v4 tokens, Design::FormBuilder)."
|
|
253
|
+
say " Non-isolated: ui_* helpers resolve host-wide."
|
|
187
254
|
say ""
|
|
188
255
|
say " Follow-up generators (extend an already-installed engine):", :yellow
|
|
189
256
|
say " bin/rails generate seams:auth:add_oauth_provider <name>"
|
|
@@ -201,11 +268,10 @@ module Seams
|
|
|
201
268
|
say ""
|
|
202
269
|
say " Recommended order: core -> auth -> accounts -> notifications -> billing -> teams.", :yellow
|
|
203
270
|
say " Optional: append `admin` last for an Administrate-backed admin surface.", :yellow
|
|
204
|
-
say " See doc/CURRENT_ATTRIBUTES.md (after install) for the per-request namespace cascade.", :yellow
|
|
205
|
-
say " See doc/WRITING_FOLLOW_UP_GENERATORS.md to write your own follow-up generator.", :yellow
|
|
271
|
+
say " See doc/reference/CURRENT_ATTRIBUTES.md (after install) for the per-request namespace cascade.", :yellow
|
|
272
|
+
say " See doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md to write your own follow-up generator.", :yellow
|
|
206
273
|
say ""
|
|
207
274
|
end
|
|
208
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
209
275
|
end
|
|
210
276
|
end
|
|
211
277
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Multi-stage Dockerfile tuned for a Seams-powered Rails app.
|
|
2
2
|
#
|
|
3
3
|
# RUBY_VERSION is read from the host's `.ruby-version` at build time
|
|
4
|
-
# (default 4.0.
|
|
4
|
+
# (default 4.0.7 — bump when you bump .ruby-version). Override with
|
|
5
5
|
# `--build-arg RUBY_VERSION=...` if you need to.
|
|
6
6
|
#
|
|
7
7
|
# This file is compatible with Rails 8's `bin/docker-build`.
|
|
8
8
|
|
|
9
|
-
ARG RUBY_VERSION=4.0.
|
|
9
|
+
ARG RUBY_VERSION=4.0.7
|
|
10
10
|
FROM ruby:${RUBY_VERSION}-slim AS base
|
|
11
11
|
|
|
12
12
|
ENV BUNDLE_PATH="/usr/local/bundle" \
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
#
|
|
6
6
|
# bin/seams install # bin/rails generate seams:install
|
|
7
7
|
# bin/seams engine billing # bin/rails generate seams:engine billing
|
|
8
|
-
# bin/seams core | auth | accounts | notifications | billing | teams | admin
|
|
8
|
+
# bin/seams core | auth | accounts | notifications | billing | teams | admin | design
|
|
9
|
+
# bin/seams permissions # bin/rails generate seams:permissions
|
|
9
10
|
# bin/seams remove billing # bin/rails generate seams:remove billing
|
|
10
11
|
# bin/seams list # bin/rails seams:list
|
|
11
12
|
# bin/seams test billing # bin/rails seams:test[billing]
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
|
|
17
18
|
require "shellwords"
|
|
18
19
|
|
|
19
|
-
CANONICAL = %w[install core auth accounts notifications billing teams admin].freeze
|
|
20
|
+
CANONICAL = %w[install core auth accounts notifications billing teams admin design permissions].freeze
|
|
20
21
|
GENERIC = %w[engine remove].freeze
|
|
21
22
|
TASKS = %w[list].freeze
|
|
22
23
|
ENGINE_TASKS = %w[test quality].freeze
|
|
@@ -69,15 +70,17 @@ when nil, "help", "-h", "--help"
|
|
|
69
70
|
Canonical generators:
|
|
70
71
|
install Install the framework
|
|
71
72
|
engine <name> Generate a generic engine
|
|
72
|
-
core | auth | accounts | notifications | billing | teams | admin
|
|
73
|
+
core | auth | accounts | notifications | billing | teams | admin | design
|
|
73
74
|
Generate a canonical engine
|
|
75
|
+
permissions Generate the host-editable role -> ability
|
|
76
|
+
grant map (config/initializers/seams_permissions.rb)
|
|
74
77
|
remove <name> Remove an engine
|
|
75
78
|
|
|
76
79
|
Follow-up generators (extend an already-installed engine):
|
|
77
80
|
bin/rails generate seams:auth:add_oauth_provider <name>
|
|
78
81
|
Add a new OAuth provider adapter
|
|
79
82
|
(e.g. linkedin, apple, microsoft)
|
|
80
|
-
See doc/WRITING_FOLLOW_UP_GENERATORS.md for how to write your own.
|
|
83
|
+
See doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md for how to write your own.
|
|
81
84
|
|
|
82
85
|
Diagnostics + escape hatch:
|
|
83
86
|
list List engines + their events
|
|
@@ -16,21 +16,25 @@ env:
|
|
|
16
16
|
|
|
17
17
|
jobs:
|
|
18
18
|
lint:
|
|
19
|
-
name:
|
|
19
|
+
name: Lint
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@v7
|
|
23
23
|
- uses: ruby/setup-ruby@v1
|
|
24
24
|
with:
|
|
25
25
|
ruby-version: ".ruby-version"
|
|
26
26
|
bundler-cache: true
|
|
27
27
|
- run: bundle exec rubocop --parallel
|
|
28
|
+
<% if options[:herb] -%>
|
|
29
|
+
- name: Lint HTML+ERB (herb)
|
|
30
|
+
run: bundle exec herb lint "**/*.html.erb"
|
|
31
|
+
<% end -%>
|
|
28
32
|
|
|
29
33
|
security:
|
|
30
34
|
name: Security (brakeman + bundle-audit)
|
|
31
35
|
runs-on: ubuntu-latest
|
|
32
36
|
steps:
|
|
33
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@v7
|
|
34
38
|
- uses: ruby/setup-ruby@v1
|
|
35
39
|
with:
|
|
36
40
|
ruby-version: ".ruby-version"
|
|
@@ -48,7 +52,7 @@ jobs:
|
|
|
48
52
|
outputs:
|
|
49
53
|
engines: ${{ steps.set.outputs.engines }}
|
|
50
54
|
steps:
|
|
51
|
-
- uses: actions/checkout@
|
|
55
|
+
- uses: actions/checkout@v7
|
|
52
56
|
- id: set
|
|
53
57
|
run: |
|
|
54
58
|
if [ -d engines ]; then
|
|
@@ -71,7 +75,7 @@ jobs:
|
|
|
71
75
|
engine: ${{ fromJson(needs.discover.outputs.engines) }}
|
|
72
76
|
services:
|
|
73
77
|
postgres:
|
|
74
|
-
image: postgres:
|
|
78
|
+
image: postgres:18
|
|
75
79
|
env:
|
|
76
80
|
POSTGRES_PASSWORD: postgres
|
|
77
81
|
ports: ["5432:5432"]
|
|
@@ -87,7 +91,7 @@ jobs:
|
|
|
87
91
|
PGUSER: "postgres"
|
|
88
92
|
PGPASSWORD: "postgres"
|
|
89
93
|
steps:
|
|
90
|
-
- uses: actions/checkout@
|
|
94
|
+
- uses: actions/checkout@v7
|
|
91
95
|
- uses: ruby/setup-ruby@v1
|
|
92
96
|
with:
|
|
93
97
|
ruby-version: ".ruby-version"
|
|
@@ -99,7 +103,7 @@ jobs:
|
|
|
99
103
|
runs-on: ubuntu-latest
|
|
100
104
|
services:
|
|
101
105
|
postgres:
|
|
102
|
-
image: postgres:
|
|
106
|
+
image: postgres:18
|
|
103
107
|
env:
|
|
104
108
|
POSTGRES_PASSWORD: postgres
|
|
105
109
|
ports: ["5432:5432"]
|
|
@@ -115,9 +119,15 @@ jobs:
|
|
|
115
119
|
PGUSER: "postgres"
|
|
116
120
|
PGPASSWORD: "postgres"
|
|
117
121
|
steps:
|
|
118
|
-
- uses: actions/checkout@
|
|
122
|
+
- uses: actions/checkout@v7
|
|
119
123
|
- uses: ruby/setup-ruby@v1
|
|
120
124
|
with:
|
|
121
125
|
ruby-version: ".ruby-version"
|
|
122
126
|
bundler-cache: true
|
|
127
|
+
<% if options[:strong_migrations] -%>
|
|
128
|
+
- name: Migrations are safe (strong_migrations runs every migration from an empty database)
|
|
129
|
+
env:
|
|
130
|
+
RAILS_ENV: test
|
|
131
|
+
run: bundle exec rails db:create db:migrate
|
|
132
|
+
<% end -%>
|
|
123
133
|
- run: bundle exec rspec spec
|
|
@@ -45,7 +45,7 @@ env:
|
|
|
45
45
|
# Otherwise delete this block and provide DATABASE_URL via env.secret.
|
|
46
46
|
# accessories:
|
|
47
47
|
# postgres:
|
|
48
|
-
# image: postgres:
|
|
48
|
+
# image: postgres:18
|
|
49
49
|
# host: TODO.web.example.com
|
|
50
50
|
# port: "127.0.0.1:5432:5432"
|
|
51
51
|
# env:
|
|
@@ -55,7 +55,9 @@ env:
|
|
|
55
55
|
# secret:
|
|
56
56
|
# - POSTGRES_PASSWORD
|
|
57
57
|
# volumes:
|
|
58
|
-
#
|
|
58
|
+
# # postgres:18+ keeps data under /var/lib/postgresql/<major>/docker,
|
|
59
|
+
# # so mount the parent directory (not .../data as in 17 and older).
|
|
60
|
+
# - /var/lib/postgresql:/var/lib/postgresql
|
|
59
61
|
|
|
60
62
|
asset_path: /rails/public/assets
|
|
61
63
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Architecture (host)
|
|
2
2
|
|
|
3
3
|
> Generated by `seams:install`. Edit freely — this is YOUR doc, not
|
|
4
|
-
> the gem's. Cross-reference the gem's own `doc/ARCHITECTURE.md` (in
|
|
4
|
+
> the gem's. Cross-reference the gem's own `doc/explanation/ARCHITECTURE.md` (in
|
|
5
5
|
> the seams gem source) for framework-level details.
|
|
6
6
|
|
|
7
7
|
## The model
|
|
@@ -18,8 +18,10 @@ set -e
|
|
|
18
18
|
# Web command is `bundle exec rails server` (or `./bin/rails server`).
|
|
19
19
|
# Workers use `bundle exec rails solid_queue:start` — never run
|
|
20
20
|
# db:prepare from a worker, two containers racing CREATE TABLE breaks.
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
# Match `server` as a whole word anywhere in the command: in
|
|
22
|
+
# `bundle exec rails server` it is the 4th argument.
|
|
23
|
+
case " $* " in
|
|
24
|
+
*" server "*|*" s "*)
|
|
23
25
|
bundle exec rails db:prepare
|
|
24
26
|
;;
|
|
25
27
|
esac
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Herb config for this Seams host. Herb lints HTML+ERB templates (host views
|
|
2
|
+
# AND generated engine views) for invalid nesting, accessibility, and unsafe
|
|
3
|
+
# raw output. Run with `bundle exec herb lint "**/*.html.erb"`; CI runs it too.
|
|
4
|
+
#
|
|
5
|
+
# Pin the version so a herb upgrade can't enable new rules mid-CI without a
|
|
6
|
+
# deliberate bump. Regenerate the full default with `bundle exec herb-lint --init`.
|
|
7
|
+
#
|
|
8
|
+
# Docs: https://herb-tools.dev/configuration
|
|
9
|
+
|
|
10
|
+
framework: actionview
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Git hooks for this Seams host. Install once with:
|
|
2
|
+
#
|
|
3
|
+
# bundle exec lefthook install
|
|
4
|
+
#
|
|
5
|
+
# These run the same quality gates as CI, but locally — before a push, so
|
|
6
|
+
# issues surface immediately instead of in CI. Bypass with `git ... --no-verify`.
|
|
7
|
+
|
|
8
|
+
pre-commit:
|
|
9
|
+
parallel: true
|
|
10
|
+
commands:
|
|
11
|
+
rubocop:
|
|
12
|
+
glob: "*.rb"
|
|
13
|
+
# --force-exclusion honours the engines/ exclude in .rubocop.yml even
|
|
14
|
+
# when files are passed explicitly (engines lint themselves).
|
|
15
|
+
run: bundle exec rubocop --force-exclusion {staged_files}
|
|
16
|
+
<% if options[:herb] -%>
|
|
17
|
+
herb:
|
|
18
|
+
glob: "*.html.erb"
|
|
19
|
+
run: bundle exec herb lint {staged_files}
|
|
20
|
+
<% end -%>
|
|
21
|
+
|
|
22
|
+
pre-push:
|
|
23
|
+
commands:
|
|
24
|
+
specs:
|
|
25
|
+
run: bundle exec rspec
|
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.7
|
|
@@ -21,7 +21,12 @@ namespace :seams do
|
|
|
21
21
|
task :quality, [:engine] => :environment do |_t, args|
|
|
22
22
|
name = args[:engine] or abort("Usage: rake seams:quality[billing]")
|
|
23
23
|
abort("Invalid engine name: #{name.inspect}") unless ENGINE_NAME_PATTERN.match?(name)
|
|
24
|
-
|
|
24
|
+
# Run from inside the engine directory with --ignore-parent-exclusion:
|
|
25
|
+
# the host .rubocop.yml excludes engines/**/* (engines lint
|
|
26
|
+
# themselves), and RuboCop merges that parent exclusion into any run
|
|
27
|
+
# unless told not to — `rubocop engines/#{name}` from the host root
|
|
28
|
+
# inspects zero files and reports a false pass.
|
|
29
|
+
sh "bundle", "exec", "rubocop", "--ignore-parent-exclusion", chdir: "engines/#{name}"
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
namespace :test do
|
|
@@ -45,6 +50,13 @@ namespace :seams do
|
|
|
45
50
|
task audit: :environment do
|
|
46
51
|
Rake::Task["environment"].invoke
|
|
47
52
|
sh "bundle", "exec", "rubocop", "--parallel"
|
|
53
|
+
# Per-engine RuboCop: the host run above excludes engines/**/*, so
|
|
54
|
+
# each engine with its own .rubocop.yml is linted from inside its
|
|
55
|
+
# directory (--ignore-parent-exclusion stops the host's exclusion
|
|
56
|
+
# from turning these runs into 0-file no-ops).
|
|
57
|
+
Dir["engines/*"].select { |d| File.directory?(d) && File.exist?("#{d}/.rubocop.yml") }.sort.each do |dir|
|
|
58
|
+
sh "bundle", "exec", "rubocop", "--ignore-parent-exclusion", chdir: dir
|
|
59
|
+
end
|
|
48
60
|
sh "bundle", "exec", "rspec", "spec"
|
|
49
61
|
Dir["engines/*"].select { |d| File.directory?(d) }.sort.each do |dir|
|
|
50
62
|
next if Dir.glob("#{dir}/spec/**/*_spec.rb").empty?
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# strong_migrations rejects unsafe database migrations (lock-heavy ALTERs,
|
|
4
|
+
# non-concurrent indexes, add_column with a volatile default on an existing
|
|
5
|
+
# table, …) before they reach production. It is a development/CI guard — the
|
|
6
|
+
# gem lives in the :development, :test groups and is NOT loaded in production,
|
|
7
|
+
# so this initializer is guarded accordingly. CI runs every migration from an
|
|
8
|
+
# empty database, which is where the checks actually fire.
|
|
9
|
+
return unless defined?(StrongMigrations)
|
|
10
|
+
|
|
11
|
+
# A fresh host has nothing in production yet, so audit ALL migrations rather
|
|
12
|
+
# than grandfathering them. Once you go live, set this to the latest applied
|
|
13
|
+
# version so a from-scratch migrate doesn't re-check historical migrations.
|
|
14
|
+
StrongMigrations.start_after = 0
|
|
15
|
+
|
|
16
|
+
# Match your production Postgres major version so the right checks run in
|
|
17
|
+
# development and CI. Uncomment and set:
|
|
18
|
+
# StrongMigrations.target_version = 16
|
|
19
|
+
|
|
20
|
+
# Safety timeouts. If you use PgBouncer in transaction mode, remove these and
|
|
21
|
+
# set timeouts on the database user instead.
|
|
22
|
+
StrongMigrations.lock_timeout = 10.seconds
|
|
23
|
+
StrongMigrations.statement_timeout = 1.hour
|
|
24
|
+
|
|
25
|
+
# Refresh planner statistics after an index is added.
|
|
26
|
+
StrongMigrations.auto_analyze = true
|
|
@@ -21,7 +21,7 @@ module Seams
|
|
|
21
21
|
# reads from.
|
|
22
22
|
#
|
|
23
23
|
# Run with: bin/rails generate seams:notifications
|
|
24
|
-
# rubocop:disable Metrics/ClassLength
|
|
24
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
25
25
|
class NotificationsGenerator < Rails::Generators::Base
|
|
26
26
|
include Seams::Generators::HostInjector
|
|
27
27
|
include Seams::Generators::EjectAware
|
|
@@ -67,6 +67,8 @@ module Seams
|
|
|
67
67
|
engine_path("lib/notifications/configuration.rb")
|
|
68
68
|
template_unless_ejected "lib/type_registry.rb.tt",
|
|
69
69
|
engine_path("lib/notifications/type_registry.rb")
|
|
70
|
+
template_unless_ejected "lib/preferences.rb.tt",
|
|
71
|
+
engine_path("lib/notifications/preferences.rb")
|
|
70
72
|
# lib/notifications.rb stays framework-managed (root require file).
|
|
71
73
|
template "lib/notifications.rb.tt", engine_path("lib/notifications.rb"), force: true
|
|
72
74
|
end
|
|
@@ -390,6 +392,5 @@ module Seams
|
|
|
390
392
|
RB
|
|
391
393
|
end
|
|
392
394
|
end
|
|
393
|
-
# rubocop:enable Metrics/ClassLength
|
|
394
395
|
end
|
|
395
396
|
end
|
data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt
CHANGED
|
@@ -7,15 +7,8 @@ module Notifications
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def update
|
|
10
|
-
# Whitelist the keys we accept. The form posts
|
|
11
|
-
# `preferences[<channel>:<type>] = "1" | "0"`. Allowed channels
|
|
12
|
-
# are the canonical CHANNELS list; type is either "any" (→ nil)
|
|
13
|
-
# or a NotificationPreference::TYPE-style identifier ([a-z0-9_]).
|
|
14
10
|
preference_params.each do |key, enabled|
|
|
15
11
|
channel, type = key.split(":", 2)
|
|
16
|
-
next unless Notifications::NotificationPreference::CHANNELS.include?(channel)
|
|
17
|
-
next unless type.nil? || type == "any" || type.match?(/\A[a-z0-9_]+\z/)
|
|
18
|
-
|
|
19
12
|
type = nil if type == "any"
|
|
20
13
|
pref = Notifications::NotificationPreference.find_or_initialize_by(
|
|
21
14
|
identity_id: current_identity_id,
|
|
@@ -29,13 +22,12 @@ module Notifications
|
|
|
29
22
|
|
|
30
23
|
private
|
|
31
24
|
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
25
|
+
# Permit only the keys the channel/type registry allows. The closed
|
|
26
|
+
# list is derived at request time from NotificationPreference::CHANNELS
|
|
27
|
+
# and Notifications::TypeRegistry, so brakeman sees no mass-assignment
|
|
28
|
+
# risk and unknown keys are rejected by Rails rather than silently dropped.
|
|
36
29
|
def preference_params
|
|
37
|
-
|
|
38
|
-
raw.respond_to?(:permit!) ? raw.permit!.to_h : raw.to_h
|
|
30
|
+
params.require(:preferences).permit(*Notifications::Preferences.allowed_keys)
|
|
39
31
|
end
|
|
40
32
|
|
|
41
33
|
# Resolves the signed-in identity's id from `Auth::Current.identity`
|
|
@@ -16,6 +16,18 @@ module Notifications
|
|
|
16
16
|
# seams:insertion-point notifications.engine.events
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
initializer "notifications.register_abilities" do
|
|
20
|
+
# Ability codes this engine owns, registered exactly like events
|
|
21
|
+
# (resource.action.engine). `read` is the member-level view;
|
|
22
|
+
# `manage` is the admin-level mutate. Host grant maps reference
|
|
23
|
+
# these codes; see Seams::Permissions::DEFAULT_GRANTS.
|
|
24
|
+
Seams::PermissionRegistry.register("notification.read.notifications", owned_by: "Notifications")
|
|
25
|
+
Seams::PermissionRegistry.register("notification.manage.notifications", owned_by: "Notifications")
|
|
26
|
+
Seams::PermissionRegistry.register("preference.manage.notifications", owned_by: "Notifications")
|
|
27
|
+
# Follow-up generators that ship new notifications abilities register them here.
|
|
28
|
+
# seams:insertion-point notifications.engine.abilities
|
|
29
|
+
end
|
|
30
|
+
|
|
19
31
|
initializer "notifications.append_migrations" do |app|
|
|
20
32
|
unless app.root == root
|
|
21
33
|
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "notifications/version"
|
|
4
4
|
require "notifications/configuration"
|
|
5
5
|
require "notifications/type_registry"
|
|
6
|
+
require "notifications/preferences"
|
|
6
7
|
require "notifications/engine"
|
|
7
8
|
require "notifications/concerns/notifiable"
|
|
8
9
|
require "notifications/adapters/abstract"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notifications
|
|
4
|
+
# Channel/type key registry for the preferences surface.
|
|
5
|
+
#
|
|
6
|
+
# The preferences form posts flat keys of the form
|
|
7
|
+
# +<channel>:<type>+ (e.g. +in_app:billing.invoice_paid+) or
|
|
8
|
+
# +<channel>:any+ for the channel-wide catch-all row. This module
|
|
9
|
+
# derives the closed set of acceptable keys from:
|
|
10
|
+
#
|
|
11
|
+
# * +NotificationPreference::CHANNELS+ — the canonical delivery
|
|
12
|
+
# channels compiled into the engine.
|
|
13
|
+
# * +Notifications::TypeRegistry.names+ — the notification types the
|
|
14
|
+
# host has registered (seeded by +Notifications.seed_default_types!+
|
|
15
|
+
# and extended via the initializer).
|
|
16
|
+
#
|
|
17
|
+
# PreferencesController passes the result to +params.permit(...)+ so
|
|
18
|
+
# Rails rejects unknown keys without relying on +permit!+.
|
|
19
|
+
module Preferences
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# Returns every key the preferences form is allowed to post.
|
|
23
|
+
# Format: +["in_app:any", "in_app:welcome", "email:any", ...]+
|
|
24
|
+
def allowed_keys
|
|
25
|
+
channels = Notifications::NotificationPreference::CHANNELS
|
|
26
|
+
types = Notifications::TypeRegistry.names
|
|
27
|
+
|
|
28
|
+
channels.flat_map do |channel|
|
|
29
|
+
[
|
|
30
|
+
"#{channel}:any",
|
|
31
|
+
*types.map { |type| "#{channel}:#{type}" }
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|