| """
|
| Controller for user settings operations.
|
| """
|
|
|
| from typing import Optional
|
| from sqlalchemy import select
|
| from sqlalchemy.ext.asyncio import AsyncSession
|
| from src.model import UserSettings
|
| from src.schema.user_settings import UserSettingsUpdate
|
| from fastapi import HTTPException, status
|
| from cryptography.fernet import Fernet, InvalidToken
|
| import os
|
|
|
|
|
| ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY")
|
| if not ENCRYPTION_KEY:
|
| raise ValueError("ENCRYPTION_KEY environment variable is not set")
|
|
|
| fernet = Fernet(ENCRYPTION_KEY.encode())
|
|
|
|
|
| class UserSettingsController:
|
| """Controller for user settings operations."""
|
|
|
| @staticmethod
|
| def is_api_key_encrypted(api_key: str) -> bool:
|
| """
|
| Check if an API key is already encrypted (Fernet token) or plaintext.
|
|
|
| Args:
|
| api_key: The API key string to check
|
|
|
| Returns:
|
| True if encrypted, False if plaintext
|
| """
|
| if not api_key:
|
| return False
|
| if len(api_key) < 80:
|
| return False
|
| try:
|
| decrypted = fernet.decrypt(api_key.encode()).decode()
|
| return True
|
| except (InvalidToken, Exception) as e:
|
| return False
|
|
|
| @staticmethod
|
| async def get_user_settings(
|
| session: AsyncSession, user_id: int
|
| ) -> Optional[UserSettings]:
|
| """
|
| Get user settings by user ID.
|
|
|
| Args:
|
| session: Database session
|
| user_id: User ID
|
|
|
| Returns:
|
| UserSettings object or None if not found
|
| """
|
| result = await session.execute(
|
| select(UserSettings).where(UserSettings.user_id == user_id)
|
| )
|
| return result.scalar_one_or_none()
|
|
|
| @staticmethod
|
| async def create_user_settings(
|
| session: AsyncSession,
|
| user_id: int,
|
| location: Optional[str] = None,
|
| custom_summary_instructions: Optional[str] = None,
|
| usability_analysis_instructions: Optional[str] = None,
|
| summary_model: Optional[str] = None,
|
| usability_model: Optional[str] = None,
|
| api_keys: Optional[list[dict]] = None,
|
| ) -> UserSettings:
|
| """
|
| Create user settings.
|
|
|
| Args:
|
| session: Database session
|
| user_id: User ID
|
| location: User location
|
| custom_summary_instructions: Custom summary instructions
|
| usability_analysis_instructions: Usability analysis instructions
|
| api_keys: List of API key dictionaries
|
|
|
| Returns:
|
| Created UserSettings object
|
| """
|
| settings = UserSettings(
|
| user_id=user_id,
|
| location=location,
|
| custom_summary_instructions=custom_summary_instructions,
|
| usability_analysis_instructions=usability_analysis_instructions,
|
| summary_model=summary_model,
|
| usability_model=usability_model,
|
| )
|
|
|
| if api_keys:
|
|
|
| processed_keys = []
|
| for key_item in api_keys:
|
| processed_item = key_item.copy()
|
| api_key_value = processed_item.get("api_key", "")
|
| slug = processed_item.get("slug", "unknown")
|
|
|
|
|
| if api_key_value and not UserSettingsController.is_api_key_encrypted(
|
| api_key_value
|
| ):
|
| encrypted_value = fernet.encrypt(api_key_value.encode()).decode()
|
| processed_item["api_key"] = encrypted_value
|
| processed_keys.append(processed_item)
|
|
|
| settings.api_keys_encrypted = processed_keys
|
|
|
| session.add(settings)
|
| await session.commit()
|
| await session.refresh(settings)
|
| return settings
|
|
|
| @staticmethod
|
| async def update_user_settings(
|
| session: AsyncSession, user_id: int, settings_data: UserSettingsUpdate
|
| ) -> UserSettings:
|
| """
|
| Update user settings.
|
|
|
| Args:
|
| session: Database session
|
| user_id: User ID
|
| settings_data: Settings data to update
|
|
|
| Returns:
|
| Updated UserSettings object
|
|
|
| Raises:
|
| HTTPException: If settings not found
|
| """
|
|
|
| settings = await UserSettingsController.get_user_settings(session, user_id)
|
|
|
| if not settings:
|
|
|
| api_keys = None
|
| if settings_data.api_keys_encrypted:
|
| api_keys = [
|
| item.model_dump() for item in settings_data.api_keys_encrypted
|
| ]
|
|
|
| settings = await UserSettingsController.create_user_settings(
|
| session=session,
|
| user_id=user_id,
|
| location=settings_data.location,
|
| custom_summary_instructions=settings_data.custom_summary_instructions,
|
| usability_analysis_instructions=settings_data.usability_analysis_instructions,
|
| summary_model=settings_data.summary_model,
|
| usability_model=settings_data.usability_model,
|
| api_keys=api_keys,
|
| )
|
| return settings
|
|
|
|
|
| update_data = settings_data.model_dump(exclude_unset=True)
|
|
|
|
|
| if "api_keys_encrypted" in update_data:
|
| api_keys = update_data.pop("api_keys_encrypted")
|
| if api_keys is not None:
|
|
|
| if len(api_keys) == 0:
|
| settings.api_keys_encrypted = []
|
| else:
|
|
|
| processed_keys = []
|
| for item in api_keys:
|
| key_dict = (
|
| item.model_dump() if hasattr(item, "model_dump") else item
|
| )
|
| processed_item = key_dict.copy()
|
| api_key_value = processed_item.get("api_key", "")
|
| slug = processed_item.get("slug", "unknown")
|
|
|
| if (
|
| api_key_value
|
| and not UserSettingsController.is_api_key_encrypted(
|
| api_key_value
|
| )
|
| ):
|
| encrypted_value = fernet.encrypt(
|
| api_key_value.encode()
|
| ).decode()
|
| processed_item["api_key"] = encrypted_value
|
|
|
| processed_keys.append(processed_item)
|
|
|
| settings.api_keys_encrypted = processed_keys
|
|
|
|
|
| for field, value in update_data.items():
|
| setattr(settings, field, value)
|
|
|
| await session.commit()
|
| await session.refresh(settings)
|
| return settings
|
|
|
| @staticmethod
|
| async def get_or_create_user_settings(
|
| session: AsyncSession, user_id: int
|
| ) -> UserSettings:
|
| """
|
| Get user settings, creating them if they don't exist.
|
|
|
| Args:
|
| session: Database session
|
| user_id: User ID
|
|
|
| Returns:
|
| UserSettings object
|
| """
|
| settings = await UserSettingsController.get_user_settings(session, user_id)
|
|
|
| if not settings:
|
| settings = await UserSettingsController.create_user_settings(
|
| session, user_id
|
| )
|
|
|
| return settings
|
|
|