feat(settings): persist storage provider settings to DB, add export endpoint, enrich setup wizard

- dropbox/google_drive/onedrive save-settings: switch to DB as primary,
  .env write as best-effort (no longer fails when .env is absent)
- onedrive/google_drive update-settings: persist changes to DB alongside
  in-memory update; call notify_settings_updated()
- onedrive test_onedrive_token: persist rotated refresh token to DB
- settings_service: add get_settings_for_export() (db / effective modes)
- settings API: add GET /api/settings/export-env (admin-only, downloads .env)
- wizard: enrich settings with current values (DB > ENV > default) and
  value_source badges; pass setup_skipped to template; call
  notify_settings_updated() on save; add /setup/undo-skip route
- setup_wizard.html: pre-populate inputs with current_value; show
  DB/ENV/DEFAULT source badges; skip/undo-skip messaging
- settings.html: replace single Audit Log button with Setup Wizard link,
  Export .env dropdown, and Audit Log button group

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-23 02:41:04 +00:00
parent 0d5c20f685
commit ecc8100e24
8 changed files with 651 additions and 180 deletions
+35
View File
@@ -1287,3 +1287,38 @@ def rollback_setting(
)
db.rollback()
return False
def get_settings_for_export(db: Session, source: str = "db") -> Dict[str, str]:
"""
Collect settings for export as environment variables.
Args:
db: Database session
source: ``"db"`` to export only database-persisted settings (default);
``"effective"`` to export the full current runtime configuration
(DB overrides ENV overrides application defaults) for every key
listed in SETTING_METADATA.
Returns:
Ordered dict mapping uppercase ENV variable names to their string values.
Sensitive values are included (the caller is responsible for access control).
"""
if source == "effective":
from app.config import settings as app_settings
db_settings = get_all_settings_from_db(db)
result = {}
for key in sorted(SETTING_METADATA.keys()):
# DB wins, then live settings object (ENV/default)
if key in db_settings and db_settings[key] is not None:
value = db_settings[key]
else:
value = getattr(app_settings, key, None)
if value is not None:
result[key.upper()] = str(value)
return result
else:
# DB only
db_settings = get_all_settings_from_db(db)
return {k.upper(): v for k, v in sorted(db_settings.items()) if v is not None}