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:
+125
-24
@@ -7,11 +7,15 @@ import os
|
||||
from datetime import datetime
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from fastapi import APIRouter, Form, HTTPException, Request, status
|
||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.auth import require_login
|
||||
from app.config import settings
|
||||
from app.database import get_db
|
||||
from app.utils.oauth_helper import exchange_oauth_token
|
||||
from app.utils.settings_service import save_setting_to_db
|
||||
from app.utils.settings_sync import notify_settings_updated
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -45,7 +49,9 @@ async def exchange_google_drive_token(
|
||||
}
|
||||
|
||||
# Use shared OAuth helper (handles secure logging and error handling)
|
||||
token_data = exchange_oauth_token(provider_name="Google Drive", token_url=token_url, payload=payload)
|
||||
token_data = exchange_oauth_token(
|
||||
provider_name="Google Drive", token_url=token_url, payload=payload
|
||||
)
|
||||
|
||||
# Return just what's needed by the frontend
|
||||
return {
|
||||
@@ -64,38 +70,73 @@ async def update_google_drive_settings(
|
||||
client_secret: Annotated[Optional[str], Form()] = None,
|
||||
folder_id: Annotated[Optional[str], Form()] = None,
|
||||
use_oauth: Annotated[str, Form()] = "true",
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Update Google Drive settings in memory
|
||||
Update Google Drive settings in memory and persist to database
|
||||
"""
|
||||
try:
|
||||
logger.info("Updating Google Drive settings in memory")
|
||||
logger.info("Updating Google Drive settings in memory and database")
|
||||
|
||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||
changed_by = (
|
||||
user.get("preferred_username")
|
||||
or user.get("username")
|
||||
or user.get("email")
|
||||
or user.get("id")
|
||||
or "wizard"
|
||||
)
|
||||
|
||||
# Convert use_oauth string to boolean
|
||||
use_oauth_bool = use_oauth.lower() in ("true", "1", "yes", "y", "t")
|
||||
|
||||
# Update settings in memory
|
||||
# Update settings in memory and persist to database
|
||||
if refresh_token:
|
||||
settings.google_drive_refresh_token = refresh_token
|
||||
logger.info("Updated GOOGLE_DRIVE_REFRESH_TOKEN in memory")
|
||||
save_setting_to_db(
|
||||
db, "google_drive_refresh_token", refresh_token, changed_by=changed_by
|
||||
)
|
||||
logger.info("Updated GOOGLE_DRIVE_REFRESH_TOKEN in memory and database")
|
||||
|
||||
if client_id:
|
||||
settings.google_drive_client_id = client_id
|
||||
logger.info("Updated GOOGLE_DRIVE_CLIENT_ID in memory")
|
||||
save_setting_to_db(
|
||||
db, "google_drive_client_id", client_id, changed_by=changed_by
|
||||
)
|
||||
logger.info("Updated GOOGLE_DRIVE_CLIENT_ID in memory and database")
|
||||
|
||||
if client_secret:
|
||||
settings.google_drive_client_secret = client_secret
|
||||
logger.info("Updated GOOGLE_DRIVE_CLIENT_SECRET in memory")
|
||||
save_setting_to_db(
|
||||
db, "google_drive_client_secret", client_secret, changed_by=changed_by
|
||||
)
|
||||
logger.info("Updated GOOGLE_DRIVE_CLIENT_SECRET in memory and database")
|
||||
|
||||
if folder_id:
|
||||
settings.google_drive_folder_id = folder_id
|
||||
logger.info("Updated GOOGLE_DRIVE_FOLDER_ID in memory")
|
||||
save_setting_to_db(
|
||||
db, "google_drive_folder_id", folder_id, changed_by=changed_by
|
||||
)
|
||||
logger.info("Updated GOOGLE_DRIVE_FOLDER_ID in memory and database")
|
||||
|
||||
# Set the OAuth flag
|
||||
settings.google_drive_use_oauth = use_oauth_bool
|
||||
logger.info(f"Updated GOOGLE_DRIVE_USE_OAUTH in memory to {use_oauth_bool}")
|
||||
save_setting_to_db(
|
||||
db,
|
||||
"google_drive_use_oauth",
|
||||
str(use_oauth_bool).lower(),
|
||||
changed_by=changed_by,
|
||||
)
|
||||
logger.info(
|
||||
f"Updated GOOGLE_DRIVE_USE_OAUTH in memory and database to {use_oauth_bool}"
|
||||
)
|
||||
|
||||
return {"status": "success", "message": "Google Drive settings have been updated in memory"}
|
||||
notify_settings_updated()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Google Drive settings have been updated in memory and database",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Unexpected error updating Google Drive settings: {str(e)}")
|
||||
@@ -113,7 +154,8 @@ async def test_google_drive_token(request: Request):
|
||||
Tests both OAuth and service account approaches based on configuration.
|
||||
"""
|
||||
try:
|
||||
from app.tasks.upload_to_google_drive import get_drive_service_oauth, get_google_drive_service
|
||||
from app.tasks.upload_to_google_drive import (get_drive_service_oauth,
|
||||
get_google_drive_service)
|
||||
|
||||
logger.info("Testing Google Drive token validity")
|
||||
|
||||
@@ -125,7 +167,10 @@ async def test_google_drive_token(request: Request):
|
||||
and settings.google_drive_refresh_token
|
||||
):
|
||||
logger.warning("Google Drive OAuth credentials not fully configured")
|
||||
return {"status": "error", "message": "Google Drive OAuth credentials are not fully configured"}
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Google Drive OAuth credentials are not fully configured",
|
||||
}
|
||||
|
||||
try:
|
||||
# Test OAuth connection
|
||||
@@ -187,8 +232,13 @@ async def test_google_drive_token(request: Request):
|
||||
else:
|
||||
# Test service account connection
|
||||
if not settings.google_drive_credentials_json:
|
||||
logger.warning("Google Drive service account credentials not configured")
|
||||
return {"status": "error", "message": "Google Drive service account credentials are not configured"}
|
||||
logger.warning(
|
||||
"Google Drive service account credentials not configured"
|
||||
)
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Google Drive service account credentials are not configured",
|
||||
}
|
||||
|
||||
try:
|
||||
service = get_google_drive_service()
|
||||
@@ -203,7 +253,9 @@ async def test_google_drive_token(request: Request):
|
||||
else:
|
||||
user_display = user_email
|
||||
|
||||
logger.info(f"Successfully connected to Google Drive using service account as {user_display}")
|
||||
logger.info(
|
||||
f"Successfully connected to Google Drive using service account as {user_display}"
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
@@ -214,7 +266,10 @@ async def test_google_drive_token(request: Request):
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
logger.error(f"Google Drive service account test failed: {error_msg}")
|
||||
return {"status": "error", "message": f"Service account validation failed: {error_msg}"}
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"Service account validation failed: {error_msg}",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error testing Google Drive token")
|
||||
@@ -246,7 +301,10 @@ async def get_google_drive_token_info(request: Request):
|
||||
and settings.google_drive_refresh_token
|
||||
):
|
||||
logger.warning("Google Drive OAuth credentials not fully configured")
|
||||
return {"status": "error", "message": "Google Drive OAuth credentials are not fully configured"}
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Google Drive OAuth credentials are not fully configured",
|
||||
}
|
||||
|
||||
try:
|
||||
# Get credentials and access token
|
||||
@@ -333,17 +391,29 @@ async def save_dropbox_settings(
|
||||
client_secret: Annotated[Optional[str], Form()] = None,
|
||||
folder_id: Annotated[Optional[str], Form()] = None,
|
||||
use_oauth: Annotated[str, Form()] = "true",
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Save Google Drive settings to the .env file
|
||||
Save Google Drive settings to the .env file (best-effort) and persist to database.
|
||||
"""
|
||||
try:
|
||||
# Get the path to the .env file
|
||||
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env")
|
||||
env_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env"
|
||||
)
|
||||
|
||||
# Convert use_oauth string to boolean
|
||||
use_oauth_bool = use_oauth.lower() in ("true", "1", "yes", "y", "t")
|
||||
|
||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||
changed_by = (
|
||||
user.get("preferred_username")
|
||||
or user.get("username")
|
||||
or user.get("email")
|
||||
or user.get("id")
|
||||
or "wizard"
|
||||
)
|
||||
|
||||
# Define settings to update
|
||||
drive_settings = {"GOOGLE_DRIVE_USE_OAUTH": str(use_oauth_bool).lower()}
|
||||
|
||||
@@ -376,7 +446,9 @@ async def save_dropbox_settings(
|
||||
stripped_line = line.rstrip()
|
||||
is_updated = False
|
||||
for key, value in drive_settings.items():
|
||||
if stripped_line.startswith(f"{key}=") or stripped_line.startswith(f"# {key}="):
|
||||
if stripped_line.startswith(
|
||||
f"{key}="
|
||||
) or stripped_line.startswith(f"# {key}="):
|
||||
# Uncomment if commented out - check the original stripped line
|
||||
new_env_lines.append(f"{key}={value}")
|
||||
updated.add(key)
|
||||
@@ -396,7 +468,9 @@ async def save_dropbox_settings(
|
||||
|
||||
logger.info("Successfully updated Google Drive settings in .env file")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to update .env file: {str(e)}, but will continue with in-memory update")
|
||||
logger.warning(
|
||||
f"Failed to update .env file: {str(e)}, but will continue with in-memory update"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f".env file not found at {env_path}, skipping file update but continuing with in-memory update"
|
||||
@@ -415,7 +489,33 @@ async def save_dropbox_settings(
|
||||
# Set OAuth flag
|
||||
settings.google_drive_use_oauth = use_oauth_bool
|
||||
|
||||
logger.info("Successfully updated Google Drive settings in memory")
|
||||
# Persist to database
|
||||
save_setting_to_db(
|
||||
db,
|
||||
"google_drive_use_oauth",
|
||||
str(use_oauth_bool).lower(),
|
||||
changed_by=changed_by,
|
||||
)
|
||||
if refresh_token:
|
||||
save_setting_to_db(
|
||||
db, "google_drive_refresh_token", refresh_token, changed_by=changed_by
|
||||
)
|
||||
if client_id:
|
||||
save_setting_to_db(
|
||||
db, "google_drive_client_id", client_id, changed_by=changed_by
|
||||
)
|
||||
if client_secret:
|
||||
save_setting_to_db(
|
||||
db, "google_drive_client_secret", client_secret, changed_by=changed_by
|
||||
)
|
||||
if folder_id:
|
||||
save_setting_to_db(
|
||||
db, "google_drive_folder_id", folder_id, changed_by=changed_by
|
||||
)
|
||||
|
||||
notify_settings_updated()
|
||||
|
||||
logger.info("Successfully updated Google Drive settings in memory and database")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
@@ -426,5 +526,6 @@ async def save_dropbox_settings(
|
||||
except Exception as e:
|
||||
logger.exception(f"Unexpected error saving Google Drive settings: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to save Google Drive settings: {str(e)}"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to save Google Drive settings: {str(e)}",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user