87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from pydantic import BaseModel
|
|
|
|
from app.db.database import get_db
|
|
from app.db.models import ApiSettings
|
|
from app.services.kis_auth import kis_auth
|
|
import logging
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger("SettingsAPI")
|
|
|
|
class SettingsSchema(BaseModel):
|
|
# Partial schema for updates
|
|
appKey: str | None = None
|
|
appSecret: str | None = None
|
|
accountNumber: str | None = None
|
|
kisApiDelayMs: int | None = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
from app.core.crypto import encrypt_str, decrypt_str
|
|
|
|
@router.get("/", response_model=SettingsSchema)
|
|
async def get_settings(db: AsyncSession = Depends(get_db)):
|
|
stmt = select(ApiSettings).where(ApiSettings.id == 1)
|
|
result = await db.execute(stmt)
|
|
settings = result.scalar_one_or_none()
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not initialized")
|
|
|
|
# Clone logic to mask secrets for display
|
|
# We can't easily clone SQLA model to Pydantic and modify without validation error if strict.
|
|
# So we construct dict.
|
|
|
|
resp_data = SettingsSchema.model_validate(settings)
|
|
|
|
if settings.appKey: resp_data.appKey = "********" # Masked
|
|
if settings.appSecret: resp_data.appSecret = "********" # Masked
|
|
if settings.accountNumber:
|
|
# Decrypt first if we want to show last 4 digits?
|
|
# Or just show encrypted? Users prefer masking.
|
|
# Let's decrypt and mask everything except last 2.
|
|
pass # Keep simple for now: Mask all or partial?
|
|
# User just sees *****
|
|
# Actually proper UX is to leave field blank or show placeholder.
|
|
# Let's mask entirely for keys.
|
|
|
|
return resp_data
|
|
|
|
@router.put("/", response_model=SettingsSchema)
|
|
async def update_settings(payload: SettingsSchema, db: AsyncSession = Depends(get_db)):
|
|
logger.info("Updating API Settings...")
|
|
stmt = select(ApiSettings).where(ApiSettings.id == 1)
|
|
result = await db.execute(stmt)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
settings = ApiSettings(id=1)
|
|
db.add(settings)
|
|
|
|
# Update fields if provided - ENCRYPT SENSITIVE DATA
|
|
if payload.appKey is not None:
|
|
settings.appKey = encrypt_str(payload.appKey)
|
|
if payload.appSecret is not None:
|
|
settings.appSecret = encrypt_str(payload.appSecret)
|
|
if payload.accountNumber is not None:
|
|
settings.accountNumber = encrypt_str(payload.accountNumber)
|
|
|
|
if payload.kisApiDelayMs is not None: settings.kisApiDelayMs = payload.kisApiDelayMs
|
|
|
|
await db.commit()
|
|
await db.refresh(settings)
|
|
|
|
# Return masked object check
|
|
# We return what was saved, but masked?
|
|
# Usually convention is to return updated state.
|
|
resp = SettingsSchema.model_validate(settings)
|
|
resp.appKey = "********"
|
|
resp.appSecret = "********"
|
|
resp.accountNumber = "********"
|
|
|
|
return resp
|
|
|