mirror of
https://github.com/we-promise/sure.git
synced 2026-06-01 08:49:01 +00:00
feat(modules): v1 feature-module gating + Investments
Exploration spike following expert-panel synthesis. Codifies the 3-tier gating model (instance / per-user / per-family) without adding a framework. Investments is the first family-scoped module. - Family#disabled_modules: string[] column, opt-out semantics. No per-module DB column. Existing families default to [] = enabled. - Family::AVAILABLE_MODULES = %w[investments] (the registry). - ModuleGateable concern (auto-included in ApplicationController): require_module! class macro + module_enabled? helper. - Api::V1::BaseController#require_module!: 403 feature_disabled JSON, mirrors require_ai_enabled. - NavigationHelper extracts mobile/desktop nav into a single source with module: key support; mobile shrinks via justify-around, never auto-fills empty slots. - Settings → Preferences gains a family-scoped module toggle card. - 4 HTML controllers (Investments, Holdings, Trades, Securities) + 3 API controllers gated. Investment/Crypto account types hidden in the new-account modal when off. - docs/feature-gating.md codifies the rule for future modules. Background-job layer not wired (no Investments-specific scheduled job to gate; flagged as TODO in docs). Run db:migrate before bin/rails test. No PR yet — awaiting decisions in open-questions list.
This commit is contained in:
@@ -3,17 +3,18 @@ class Settings::PreferencesController < ApplicationController
|
||||
|
||||
def show
|
||||
@user = Current.user
|
||||
@family = Current.family
|
||||
end
|
||||
|
||||
# Writes per-user boolean preferences stored in the JSONB `users.preferences`
|
||||
# column. Mirrors Settings::AppearancesController#update so the toggle card on
|
||||
# the Preferences page can submit directly without going through the broader
|
||||
# UsersController#update flow (which expects a full user form payload).
|
||||
# column, plus per-family module toggles in `families.disabled_modules`. The
|
||||
# auto-submit pattern matches Settings::AppearancesController#update.
|
||||
def update
|
||||
@user = Current.user
|
||||
user_params = params.permit(user: [ :preview_features_enabled ]).fetch(:user, {})
|
||||
module_params = params.permit(family: { modules: {} }).dig(:family, :modules)
|
||||
|
||||
@user.transaction do
|
||||
ActiveRecord::Base.transaction do
|
||||
@user.lock!
|
||||
updated_prefs = (@user.preferences || {}).deep_dup
|
||||
if user_params.key?(:preview_features_enabled)
|
||||
@@ -21,6 +22,22 @@ class Settings::PreferencesController < ApplicationController
|
||||
ActiveModel::Type::Boolean.new.cast(user_params[:preview_features_enabled])
|
||||
end
|
||||
@user.update!(preferences: updated_prefs)
|
||||
|
||||
if module_params.present?
|
||||
family = Current.family
|
||||
family.lock!
|
||||
disabled = Array(family.disabled_modules).map(&:to_s)
|
||||
module_params.each do |name, enabled|
|
||||
name = name.to_s
|
||||
next unless Family::AVAILABLE_MODULES.include?(name)
|
||||
if ActiveModel::Type::Boolean.new.cast(enabled)
|
||||
disabled.delete(name)
|
||||
else
|
||||
disabled << name unless disabled.include?(name)
|
||||
end
|
||||
end
|
||||
family.update!(disabled_modules: disabled)
|
||||
end
|
||||
end
|
||||
redirect_to settings_preferences_path
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user