| 1 |
|
defmodule DaProductApp.Settings do |
| 2 |
|
@moduledoc """ |
| 3 |
|
Settings context for platform configuration management. |
| 4 |
|
|
| 5 |
|
This context handles platform-wide configuration settings across |
| 6 |
|
different categories including general, UPI, international payments, |
| 7 |
|
security, notifications, integrations, and system settings. |
| 8 |
|
|
| 9 |
|
Supports gradual migration from hardcoded to database-backed settings. |
| 10 |
|
""" |
| 11 |
|
|
| 12 |
|
import Ecto.Query, warn: false |
| 13 |
|
alias DaProductApp.Repo |
| 14 |
|
alias DaProductApp.Settings.PlatformSetting |
| 15 |
|
alias DaProductApp.FeatureFlags |
| 16 |
|
|
| 17 |
|
@doc """ |
| 18 |
|
Gets all platform settings organized by category. |
| 19 |
|
Uses feature flags to determine data source. |
| 20 |
|
""" |
| 21 |
|
def get_all_settings do |
| 22 |
:-( |
if FeatureFlags.enabled?(:use_database_settings) do |
| 23 |
:-( |
get_database_settings() |
| 24 |
|
else |
| 25 |
:-( |
get_hardcoded_settings() |
| 26 |
|
end |
| 27 |
|
end |
| 28 |
|
|
| 29 |
|
@doc """ |
| 30 |
|
Gets settings for a specific category. |
| 31 |
|
""" |
| 32 |
|
def get_settings_by_category(category) do |
| 33 |
:-( |
if FeatureFlags.enabled?(:use_database_settings) do |
| 34 |
:-( |
get_database_settings_by_category(category) |
| 35 |
|
else |
| 36 |
:-( |
all_settings = get_hardcoded_settings() |
| 37 |
:-( |
Map.get(all_settings, category, %{}) |
| 38 |
|
end |
| 39 |
|
end |
| 40 |
|
|
| 41 |
|
@doc """ |
| 42 |
|
Updates settings for a specific category. |
| 43 |
|
""" |
| 44 |
|
def update_settings(category, updates) when is_map(updates) do |
| 45 |
:-( |
if FeatureFlags.enabled?(:use_database_settings) do |
| 46 |
:-( |
update_database_settings(category, updates) |
| 47 |
|
else |
| 48 |
|
# In hardcoded mode, simulate success but don't persist |
| 49 |
|
{:ok, updates} |
| 50 |
|
end |
| 51 |
|
end |
| 52 |
|
|
| 53 |
|
@doc """ |
| 54 |
|
Updates a single setting. |
| 55 |
|
""" |
| 56 |
:-( |
def update_setting(category, key, value, opts \\ []) do |
| 57 |
:-( |
value_type = Keyword.get(opts, :type, "string") |
| 58 |
:-( |
description = Keyword.get(opts, :description) |
| 59 |
|
|
| 60 |
:-( |
if FeatureFlags.enabled?(:use_database_settings) do |
| 61 |
:-( |
update_database_setting(category, key, value, value_type, description) |
| 62 |
|
else |
| 63 |
|
# In hardcoded mode, simulate success |
| 64 |
|
{:ok, %{category: category, key: key, value: value}} |
| 65 |
|
end |
| 66 |
|
end |
| 67 |
|
|
| 68 |
|
@doc """ |
| 69 |
|
Gets a single setting value with optional default. |
| 70 |
|
""" |
| 71 |
:-( |
def get_setting(category, key, default \\ nil) do |
| 72 |
:-( |
if FeatureFlags.enabled?(:use_database_settings) do |
| 73 |
:-( |
get_database_setting(category, key, default) |
| 74 |
|
else |
| 75 |
:-( |
settings = get_settings_by_category(category) |
| 76 |
:-( |
Map.get(settings, key, default) |
| 77 |
|
end |
| 78 |
|
end |
| 79 |
|
|
| 80 |
|
@doc """ |
| 81 |
|
Validates a setting value. |
| 82 |
|
""" |
| 83 |
|
def validate_setting(category, key, value) do |
| 84 |
:-( |
case {category, key} do |
| 85 |
:-( |
{"general", "max_transaction_amount"} when is_number(value) and value > 0 -> {:ok, value} |
| 86 |
:-( |
{"general", "min_transaction_amount"} when is_number(value) and value > 0 -> {:ok, value} |
| 87 |
:-( |
{"upi", "npci_timeout"} when is_integer(value) and value > 0 -> {:ok, value} |
| 88 |
:-( |
{"upi", "retry_attempts"} when is_integer(value) and value >= 0 -> {:ok, value} |
| 89 |
:-( |
{"security", "session_timeout"} when is_integer(value) and value > 0 -> {:ok, value} |
| 90 |
:-( |
{"security", "max_login_attempts"} when is_integer(value) and value > 0 -> {:ok, value} |
| 91 |
:-( |
_ -> {:ok, value} # Default acceptance for demo |
| 92 |
|
end |
| 93 |
|
end |
| 94 |
|
|
| 95 |
|
# Private functions for different setting categories |
| 96 |
|
|
| 97 |
|
defp get_general_settings do |
| 98 |
:-( |
%{ |
| 99 |
|
"platform_name" => "Mercury UPI PSP", |
| 100 |
|
"platform_description" => "Advanced UPI Payment Service Provider", |
| 101 |
|
"default_timezone" => "Asia/Kolkata", |
| 102 |
|
"default_currency" => "INR", |
| 103 |
|
"maintenance_mode" => false, |
| 104 |
|
"debug_mode" => false, |
| 105 |
|
"max_transaction_amount" => 200000.00, |
| 106 |
|
"min_transaction_amount" => 1.00 |
| 107 |
|
} |
| 108 |
|
end |
| 109 |
|
|
| 110 |
|
defp get_upi_settings do |
| 111 |
:-( |
%{ |
| 112 |
|
"npci_endpoint" => "https://api.npci.org.in/v1", |
| 113 |
|
"npci_timeout" => 30, |
| 114 |
|
"retry_attempts" => 3, |
| 115 |
|
"retry_delay" => 5, |
| 116 |
|
"qr_expiry_minutes" => 15, |
| 117 |
|
"max_qr_per_merchant" => 100, |
| 118 |
|
"validate_vpa_real_time" => true, |
| 119 |
|
"allow_zero_amount_qr" => true, |
| 120 |
|
"mandate_beneficiary_validation" => true |
| 121 |
|
} |
| 122 |
|
end |
| 123 |
|
|
| 124 |
|
defp get_international_settings do |
| 125 |
:-( |
%{ |
| 126 |
|
"enable_international_payments" => true, |
| 127 |
|
"default_fx_provider" => "reuters", |
| 128 |
|
"fx_rate_refresh_interval" => 300, |
| 129 |
|
"fx_rate_tolerance" => 0.05, |
| 130 |
|
"supported_corridors" => ["SGD-INR", "USD-INR", "AED-INR"], |
| 131 |
|
"max_international_amount" => 500000.00, |
| 132 |
|
"compliance_check_required" => true, |
| 133 |
|
"auto_settlement_enabled" => false |
| 134 |
|
} |
| 135 |
|
end |
| 136 |
|
|
| 137 |
|
defp get_security_settings do |
| 138 |
:-( |
%{ |
| 139 |
|
"encryption_enabled" => true, |
| 140 |
|
"encryption_algorithm" => "AES-256-GCM", |
| 141 |
|
"session_timeout" => 30, |
| 142 |
|
"max_login_attempts" => 5, |
| 143 |
|
"lockout_duration" => 15, |
| 144 |
|
"require_2fa" => false, |
| 145 |
|
"ip_whitelist_enabled" => false, |
| 146 |
|
"audit_logging_enabled" => true, |
| 147 |
|
"sensitive_data_masking" => true |
| 148 |
|
} |
| 149 |
|
end |
| 150 |
|
|
| 151 |
|
defp get_notification_settings do |
| 152 |
:-( |
%{ |
| 153 |
|
"email_enabled" => true, |
| 154 |
|
"sms_enabled" => true, |
| 155 |
|
"webhook_enabled" => true, |
| 156 |
|
"push_notifications_enabled" => false, |
| 157 |
|
"transaction_alerts" => true, |
| 158 |
|
"failure_alerts" => true, |
| 159 |
|
"system_alerts" => true, |
| 160 |
|
"daily_reports" => true, |
| 161 |
|
"alert_threshold_amount" => 50000.00 |
| 162 |
|
} |
| 163 |
|
end |
| 164 |
|
|
| 165 |
|
defp get_integration_settings do |
| 166 |
:-( |
%{ |
| 167 |
|
"razorpay_enabled" => false, |
| 168 |
|
"razorpay_api_key" => "", |
| 169 |
|
"razorpay_webhook_secret" => "", |
| 170 |
|
"paytm_enabled" => true, |
| 171 |
|
"paytm_merchant_id" => "PAYTM_MERCHANT_001", |
| 172 |
|
"paytm_api_key" => "", |
| 173 |
|
"phonepe_enabled" => true, |
| 174 |
|
"phonepe_merchant_id" => "PHONEPE_MERCHANT_001", |
| 175 |
|
"phonepe_api_key" => "" |
| 176 |
|
} |
| 177 |
|
end |
| 178 |
|
|
| 179 |
|
defp get_system_settings do |
| 180 |
:-( |
%{ |
| 181 |
|
"max_concurrent_transactions" => 1000, |
| 182 |
|
"queue_size_limit" => 10000, |
| 183 |
|
"worker_pool_size" => 50, |
| 184 |
|
"database_pool_size" => 20, |
| 185 |
|
"cache_ttl_seconds" => 3600, |
| 186 |
|
"log_level" => "info", |
| 187 |
|
"metrics_enabled" => true, |
| 188 |
|
"health_check_interval" => 60, |
| 189 |
|
"live_reload_enabled" => true, |
| 190 |
|
"performance_monitoring" => false, |
| 191 |
|
"error_tracking_enabled" => true, |
| 192 |
|
"backup_enabled" => true, |
| 193 |
|
"backup_frequency" => "daily", |
| 194 |
|
"log_retention_days" => 90, |
| 195 |
|
"metrics_retention_days" => 30 |
| 196 |
|
} |
| 197 |
|
end |
| 198 |
|
|
| 199 |
|
# ================================ |
| 200 |
|
# DATABASE-BACKED SETTINGS |
| 201 |
|
# ================================ |
| 202 |
|
|
| 203 |
|
defp get_database_settings do |
| 204 |
:-( |
settings = |
| 205 |
|
from(s in PlatformSetting, where: s.is_active == true) |
| 206 |
|
|> Repo.all() |
| 207 |
:-( |
|> Enum.group_by(& &1.category) |
| 208 |
:-( |
|> Enum.into(%{}, fn {category, settings} -> |
| 209 |
:-( |
settings_map = |
| 210 |
|
settings |
| 211 |
:-( |
|> Enum.into(%{}, fn setting -> |
| 212 |
:-( |
{setting.key, PlatformSetting.parse_value(setting)} |
| 213 |
|
end) |
| 214 |
|
{category, settings_map} |
| 215 |
|
end) |
| 216 |
|
|
| 217 |
|
# Merge with defaults for any missing categories |
| 218 |
:-( |
Map.merge(get_hardcoded_settings(), settings) |
| 219 |
|
end |
| 220 |
|
|
| 221 |
|
defp get_database_settings_by_category(category) do |
| 222 |
|
from(s in PlatformSetting, |
| 223 |
|
where: s.category == ^category and s.is_active == true) |
| 224 |
|
|> Repo.all() |
| 225 |
:-( |
|> Enum.into(%{}, fn setting -> |
| 226 |
:-( |
{setting.key, PlatformSetting.parse_value(setting)} |
| 227 |
|
end) |
| 228 |
|
end |
| 229 |
|
|
| 230 |
|
defp get_database_setting(category, key, default) do |
| 231 |
:-( |
case Repo.get_by(PlatformSetting, category: category, key: key, is_active: true) do |
| 232 |
:-( |
nil -> default |
| 233 |
:-( |
setting -> PlatformSetting.parse_value(setting) |
| 234 |
|
end |
| 235 |
|
end |
| 236 |
|
|
| 237 |
|
defp update_database_settings(category, updates) do |
| 238 |
:-( |
results = |
| 239 |
:-( |
for {key, value} <- updates do |
| 240 |
|
update_database_setting(category, key, value, "string", nil) |
| 241 |
|
end |
| 242 |
|
|
| 243 |
:-( |
case Enum.find(results, &match?({:error, _}, &1)) do |
| 244 |
:-( |
nil -> {:ok, updates} |
| 245 |
:-( |
error -> error |
| 246 |
|
end |
| 247 |
|
end |
| 248 |
|
|
| 249 |
|
defp update_database_setting(category, key, value, value_type, description) do |
| 250 |
:-( |
case Repo.get_by(PlatformSetting, category: category, key: key) do |
| 251 |
|
nil -> |
| 252 |
|
# Create new setting |
| 253 |
|
%PlatformSetting{} |
| 254 |
|
|> PlatformSetting.changeset(%{ |
| 255 |
|
category: category, |
| 256 |
|
key: key, |
| 257 |
|
value: PlatformSetting.encode_value(value, value_type), |
| 258 |
|
value_type: value_type, |
| 259 |
|
description: description, |
| 260 |
|
is_active: true |
| 261 |
|
}) |
| 262 |
:-( |
|> Repo.insert() |
| 263 |
|
|
| 264 |
|
existing -> |
| 265 |
|
# Update existing setting |
| 266 |
|
existing |
| 267 |
|
|> PlatformSetting.changeset(%{ |
| 268 |
|
value: PlatformSetting.encode_value(value, value_type), |
| 269 |
|
value_type: value_type, |
| 270 |
:-( |
description: description || existing.description, |
| 271 |
|
is_active: true |
| 272 |
|
}) |
| 273 |
:-( |
|> Repo.update() |
| 274 |
|
end |
| 275 |
|
end |
| 276 |
|
|
| 277 |
|
defp get_hardcoded_settings do |
| 278 |
:-( |
%{ |
| 279 |
|
"general" => get_general_settings(), |
| 280 |
|
"upi" => get_upi_settings(), |
| 281 |
|
"international" => get_international_settings(), |
| 282 |
|
"security" => get_security_settings(), |
| 283 |
|
"notifications" => get_notification_settings(), |
| 284 |
|
"integrations" => get_integration_settings(), |
| 285 |
|
"system" => get_system_settings() |
| 286 |
|
} |
| 287 |
|
end |
| 288 |
|
end |