178 lines
6.7 KiB
Python
178 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.api.deps import get_api_current_user
|
|
from app.api.errors import ApiException
|
|
from app.api.responses import success_payload
|
|
from app.api.schemas import SettingsEnvelope, SettingsUpdateRequest
|
|
from app.db.models import User
|
|
from app.db.session import get_db_session
|
|
from app.logging_utils import structured_log
|
|
from app.services.domains.ingestion import safety as run_safety_service
|
|
from app.services.domains.settings import application as user_settings_service
|
|
from app.settings import settings as settings_module
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/settings", tags=["api-settings"])
|
|
|
|
|
|
def _serialize_settings(user_settings) -> dict[str, object]:
|
|
min_run_interval_minutes, min_request_delay_seconds = _minimum_policy()
|
|
return {
|
|
"auto_run_enabled": bool(user_settings.auto_run_enabled) and settings_module.ingestion_automation_allowed,
|
|
"run_interval_minutes": int(user_settings.run_interval_minutes),
|
|
"request_delay_seconds": int(user_settings.request_delay_seconds),
|
|
"nav_visible_pages": list(user_settings.nav_visible_pages or []),
|
|
"policy": {
|
|
"min_run_interval_minutes": min_run_interval_minutes,
|
|
"min_request_delay_seconds": min_request_delay_seconds,
|
|
"automation_allowed": bool(settings_module.ingestion_automation_allowed),
|
|
"manual_run_allowed": bool(settings_module.ingestion_manual_run_allowed),
|
|
"blocked_failure_threshold": max(1, int(settings_module.ingestion_alert_blocked_failure_threshold)),
|
|
"network_failure_threshold": max(1, int(settings_module.ingestion_alert_network_failure_threshold)),
|
|
"cooldown_blocked_seconds": max(60, int(settings_module.ingestion_safety_cooldown_blocked_seconds)),
|
|
"cooldown_network_seconds": max(60, int(settings_module.ingestion_safety_cooldown_network_seconds)),
|
|
},
|
|
"safety_state": run_safety_service.get_safety_state_payload(user_settings),
|
|
"openalex_api_key": user_settings.openalex_api_key,
|
|
"crossref_api_token": user_settings.crossref_api_token,
|
|
"crossref_api_mailto": user_settings.crossref_api_mailto,
|
|
}
|
|
|
|
|
|
def _minimum_policy() -> tuple[int, int]:
|
|
min_run_interval_minutes = user_settings_service.resolve_run_interval_minimum(
|
|
settings_module.ingestion_min_run_interval_minutes
|
|
)
|
|
min_request_delay_seconds = user_settings_service.resolve_request_delay_minimum(
|
|
settings_module.ingestion_min_request_delay_seconds
|
|
)
|
|
return min_run_interval_minutes, min_request_delay_seconds
|
|
|
|
|
|
def _parse_settings_payload(payload: SettingsUpdateRequest, user_settings) -> tuple[int, int, list[str]]:
|
|
min_run_interval_minutes, min_request_delay_seconds = _minimum_policy()
|
|
parsed_interval = user_settings_service.parse_run_interval_minutes(
|
|
str(payload.run_interval_minutes),
|
|
minimum=min_run_interval_minutes,
|
|
)
|
|
parsed_delay = user_settings_service.parse_request_delay_seconds(
|
|
str(payload.request_delay_seconds),
|
|
minimum=min_request_delay_seconds,
|
|
)
|
|
parsed_nav_visible_pages = user_settings_service.parse_nav_visible_pages(
|
|
payload.nav_visible_pages
|
|
if payload.nav_visible_pages is not None
|
|
else list(user_settings.nav_visible_pages or user_settings_service.DEFAULT_NAV_VISIBLE_PAGES)
|
|
)
|
|
return parsed_interval, parsed_delay, parsed_nav_visible_pages
|
|
|
|
|
|
async def _clear_expired_cooldown_with_log(
|
|
db_session: AsyncSession,
|
|
*,
|
|
user_id: int,
|
|
user_settings,
|
|
) -> None:
|
|
previous_safety_state = run_safety_service.get_safety_event_context(user_settings)
|
|
if not run_safety_service.clear_expired_cooldown(user_settings):
|
|
return
|
|
await db_session.commit()
|
|
await db_session.refresh(user_settings)
|
|
structured_log(
|
|
logger, "info", "api.settings.safety_cooldown_cleared",
|
|
user_id=user_id,
|
|
reason=previous_safety_state.get("cooldown_reason"),
|
|
cooldown_until=previous_safety_state.get("cooldown_until"),
|
|
)
|
|
|
|
|
|
def _effective_auto_run_enabled(payload: SettingsUpdateRequest) -> bool:
|
|
return bool(payload.auto_run_enabled) and settings_module.ingestion_automation_allowed
|
|
|
|
|
|
@router.get(
|
|
"",
|
|
response_model=SettingsEnvelope,
|
|
)
|
|
async def get_settings(
|
|
request: Request,
|
|
db_session: AsyncSession = Depends(get_db_session),
|
|
current_user: User = Depends(get_api_current_user),
|
|
):
|
|
user_settings = await user_settings_service.get_or_create_settings(
|
|
db_session,
|
|
user_id=current_user.id,
|
|
)
|
|
await _clear_expired_cooldown_with_log(
|
|
db_session,
|
|
user_id=current_user.id,
|
|
user_settings=user_settings,
|
|
)
|
|
return success_payload(
|
|
request,
|
|
data=_serialize_settings(user_settings),
|
|
)
|
|
|
|
|
|
@router.put(
|
|
"",
|
|
response_model=SettingsEnvelope,
|
|
)
|
|
async def update_settings(
|
|
payload: SettingsUpdateRequest,
|
|
request: Request,
|
|
db_session: AsyncSession = Depends(get_db_session),
|
|
current_user: User = Depends(get_api_current_user),
|
|
):
|
|
user_settings = await user_settings_service.get_or_create_settings(
|
|
db_session,
|
|
user_id=current_user.id,
|
|
)
|
|
|
|
try:
|
|
parsed_interval, parsed_delay, parsed_nav_visible_pages = _parse_settings_payload(
|
|
payload,
|
|
user_settings,
|
|
)
|
|
except user_settings_service.UserSettingsServiceError as exc:
|
|
raise ApiException(
|
|
status_code=400,
|
|
code="invalid_settings",
|
|
message=str(exc),
|
|
) from exc
|
|
|
|
updated = await user_settings_service.update_settings(
|
|
db_session,
|
|
settings=user_settings,
|
|
auto_run_enabled=_effective_auto_run_enabled(payload),
|
|
run_interval_minutes=parsed_interval,
|
|
request_delay_seconds=parsed_delay,
|
|
nav_visible_pages=parsed_nav_visible_pages,
|
|
openalex_api_key=payload.openalex_api_key,
|
|
crossref_api_token=payload.crossref_api_token,
|
|
crossref_api_mailto=payload.crossref_api_mailto,
|
|
)
|
|
await _clear_expired_cooldown_with_log(
|
|
db_session,
|
|
user_id=current_user.id,
|
|
user_settings=updated,
|
|
)
|
|
structured_log(
|
|
logger, "info", "api.settings.updated",
|
|
user_id=current_user.id,
|
|
auto_run_enabled=updated.auto_run_enabled,
|
|
run_interval_minutes=updated.run_interval_minutes,
|
|
request_delay_seconds=updated.request_delay_seconds,
|
|
nav_visible_pages=updated.nav_visible_pages,
|
|
openalex_api_key="SET" if updated.openalex_api_key else "UNSET",
|
|
)
|
|
return success_payload(
|
|
request,
|
|
data=_serialize_settings(updated),
|
|
)
|