- Add Fitbit authentication flow (save credentials, OAuth callback handling) - Implement Garmin MFA support with successful session/cookie handling - Optimize segment discovery with new sampling and activity query services - Refactor database session management in discovery API for better testability - Enhance activity data parsing for charts and analysis - Update tests to use testcontainers and proper dependency injection - Clean up repository by ignoring and removing tracked transient files (.pyc, .db)
118 lines
4.7 KiB
Python
118 lines
4.7 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
import logging
|
|
import requests
|
|
import base64
|
|
import json
|
|
|
|
from ..services.postgresql_manager import PostgreSQLManager
|
|
from ..utils.config import config
|
|
from .status import get_db
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@router.post("/setup/load-consul-config")
|
|
def load_consul_config(db: Session = Depends(get_db)):
|
|
logger = logging.getLogger(__name__)
|
|
logger.info("Attempting to load configuration from Consul...")
|
|
try:
|
|
# User defined Consul URL
|
|
consul_host = "consul.service.dc1.consul"
|
|
consul_port = "8500"
|
|
app_prefix = "fitbit-garmin-sync/"
|
|
consul_url = f"http://{consul_host}:{consul_port}/v1/kv/{app_prefix}?recurse=true"
|
|
|
|
logger.debug(f"Connecting to Consul at: {consul_url}")
|
|
|
|
response = requests.get(consul_url, timeout=5)
|
|
if response.status_code == 404:
|
|
logger.warning(f"No configuration found in Consul under '{app_prefix}'")
|
|
raise HTTPException(status_code=404, detail="No configuration found in Consul")
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
|
|
config_map = {}
|
|
|
|
# Helper to decode Consul values
|
|
def decode_consul_value(val):
|
|
if not val: return None
|
|
try:
|
|
return base64.b64decode(val).decode('utf-8')
|
|
except Exception as e:
|
|
logger.warning(f"Failed to decode value: {e}")
|
|
return None
|
|
|
|
# Pass 1: Load all raw keys
|
|
for item in data:
|
|
key = item['Key'].replace(app_prefix, '')
|
|
value = decode_consul_value(item.get('Value'))
|
|
if value:
|
|
config_map[key] = value
|
|
|
|
# Pass 2: Check for special 'config' key (JSON blob)
|
|
if 'config' in config_map:
|
|
try:
|
|
json_config = json.loads(config_map['config'])
|
|
logger.debug("Found 'config' key with JSON content, merging...")
|
|
config_map.update(json_config)
|
|
except json.JSONDecodeError:
|
|
logger.warning("'config' key found but is not valid JSON, ignoring as blob.")
|
|
|
|
logger.debug(f"Resolved configuration keys: {list(config_map.keys())}")
|
|
|
|
# Look for standard keys
|
|
username = config_map.get('garmin_username') or config_map.get('USERNAME')
|
|
password = config_map.get('garmin_password') or config_map.get('PASSWORD')
|
|
is_china = str(config_map.get('is_china', 'false')).lower() == 'true'
|
|
|
|
if not username and isinstance(config_map.get('garmin'), dict):
|
|
logger.debug("Found nested 'garmin' config object.")
|
|
garmin_conf = config_map['garmin']
|
|
username = garmin_conf.get('username')
|
|
password = garmin_conf.get('password')
|
|
if 'is_china' in garmin_conf:
|
|
is_china = str(garmin_conf.get('is_china')).lower() == 'true'
|
|
|
|
if not username or not password:
|
|
logger.error("Consul config resolved but missing 'garmin_username' or 'garmin_password'")
|
|
raise HTTPException(status_code=400, detail="Consul config missing credentials")
|
|
|
|
# Extract Fitbit credentials
|
|
fitbit_client_id = config_map.get('fitbit_client_id')
|
|
fitbit_client_secret = config_map.get('fitbit_client_secret')
|
|
fitbit_redirect_uri = config_map.get('fitbit_redirect_uri')
|
|
|
|
if isinstance(config_map.get('fitbit'), dict):
|
|
logger.debug("Found nested 'fitbit' config object.")
|
|
fitbit_conf = config_map['fitbit']
|
|
fitbit_client_id = fitbit_conf.get('client_id')
|
|
fitbit_client_secret = fitbit_conf.get('client_secret')
|
|
|
|
logger.info("Consul config loaded successfully. Returning to frontend.")
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": "Configuration loaded from Consul",
|
|
"garmin": {
|
|
"username": username,
|
|
"password": password,
|
|
"is_china": is_china
|
|
},
|
|
"fitbit": {
|
|
"client_id": fitbit_client_id,
|
|
"client_secret": fitbit_client_secret,
|
|
"redirect_uri": fitbit_redirect_uri
|
|
}
|
|
}
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"Failed to connect to Consul: {e}")
|
|
raise HTTPException(status_code=502, detail=f"Failed to connect to Consul: {str(e)}")
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error loading from Consul: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=f"Internal error loading config: {str(e)}")
|