Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions app/api/v1/api_keys.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
"""API endpoints for job status monitoring."""
"""API endpoints for API key management."""
from fastapi import Depends, APIRouter, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.session import get_db
from app.dependencies import require_read_access, require_write_access
from app.dependencies import ApiKeyServiceDep, require_read_access, require_write_access
from app.api.responses import APIException
from app.schemas.common import ApiResultResponse
from app.schemas.api_key import ApiKeyCreateDTO
from app.services.api_key_service import ApiKeyService

router = APIRouter(prefix="/api-keys", tags=["ApiKeys"])


@router.get("", dependencies=[Depends(require_read_access)])
async def list_all_keys(active_only: bool = False, db: AsyncSession = Depends(get_db)) -> ApiResultResponse:
async def list_all_keys(
service: ApiKeyServiceDep,
active_only: bool = False,
) -> ApiResultResponse:
"""List all API Keys, optionally filtered by active status."""
service = ApiKeyService(db)
keys = await service.list_keys(active_only)
key_response = ApiResultResponse(errorCode=0, errorMessage=None, payload=keys)
return key_response
return ApiResultResponse(errorCode=0, errorMessage=None, payload=keys)


@router.post("", dependencies=[Depends(require_write_access)])
async def create_api_key(data: ApiKeyCreateDTO, db: AsyncSession = Depends(get_db)) -> ApiResultResponse:
async def create_api_key(
data: ApiKeyCreateDTO,
service: ApiKeyServiceDep,
) -> ApiResultResponse:
"""Create a new API Key."""
service = ApiKeyService(db)
try:
new_key = await service.create_key(data)
return ApiResultResponse(errorCode=0, errorMessage=None, payload=new_key)
Expand All @@ -33,9 +33,11 @@ async def create_api_key(data: ApiKeyCreateDTO, db: AsyncSession = Depends(get_d


@router.delete("/{key_id}", dependencies=[Depends(require_write_access)])
async def deactivate_api_key(key_id: str, db: AsyncSession = Depends(get_db)) -> ApiResultResponse:
async def deactivate_api_key(
key_id: str,
service: ApiKeyServiceDep,
) -> ApiResultResponse:
"""Deactivate an API Key by ID."""
service = ApiKeyService(db)
try:
deactivated_key = await service.deactivate_key(key_id)
except LookupError as e:
Expand All @@ -46,8 +48,10 @@ async def deactivate_api_key(key_id: str, db: AsyncSession = Depends(get_db)) ->


@router.get("/count", dependencies=[Depends(require_read_access)])
async def get_api_key_count(active_only: bool = False, db: AsyncSession = Depends(get_db)) -> ApiResultResponse:
async def get_api_key_count(
service: ApiKeyServiceDep,
active_only: bool = False,
) -> ApiResultResponse:
"""Get the current number of API Keys."""
service = ApiKeyService(db)
count = await service.get_count(active_only)
return ApiResultResponse(errorCode=0, errorMessage=None, payload=count)
26 changes: 10 additions & 16 deletions app/api/v1/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from fastapi import Security, APIRouter, HTTPException

from app.dependencies import require_read_access, require_write_access
from app.dependencies import RedisServiceDep, require_read_access, require_write_access
from app.api.responses import success_response
from app.schemas.health import (
HealthSummaryResponse,
Expand All @@ -20,13 +20,12 @@
)
from app.services.watchdog import get_watchdog
from app.services.pv_monitor import get_pv_monitor
from app.services.redis_service import get_redis_service

router = APIRouter(prefix="/health", tags=["Health"])


@router.get("/heartbeat")
async def get_heartbeat() -> dict:
async def get_heartbeat(redis: RedisServiceDep) -> dict:
"""
Simple heartbeat check for frontend polling.

Expand All @@ -35,8 +34,6 @@ async def get_heartbeat() -> dict:
banner if alive=false.
"""
try:
redis = get_redis_service()

# Check if Redis is connected
if not redis.is_connected():
return success_response(
Expand Down Expand Up @@ -72,14 +69,13 @@ async def get_heartbeat() -> dict:


@router.get("/monitor", dependencies=[Security(require_read_access)])
async def get_monitor_health() -> MonitorHealthResponse:
async def get_monitor_health(redis: RedisServiceDep) -> MonitorHealthResponse:
"""
Get detailed monitor health information.

Includes connection counts, monitor status, and watchdog status.
"""
try:
redis = get_redis_service()
pv_monitor = get_pv_monitor()
watchdog = get_watchdog()

Expand Down Expand Up @@ -166,7 +162,7 @@ async def force_watchdog_check() -> WatchdogStatsResponse:


@router.get("/summary", dependencies=[Security(require_read_access)])
async def get_health_summary() -> HealthSummaryResponse:
async def get_health_summary(redis: RedisServiceDep) -> HealthSummaryResponse:
"""
Get a complete health summary for monitoring dashboards.

Expand All @@ -175,7 +171,6 @@ async def get_health_summary() -> HealthSummaryResponse:
issues = []

try:
redis = get_redis_service()
pv_monitor = get_pv_monitor()
watchdog = get_watchdog()

Expand Down Expand Up @@ -253,14 +248,13 @@ async def get_health_summary() -> HealthSummaryResponse:


@router.get("/disconnected", dependencies=[Security(require_read_access)])
async def get_disconnected_pvs() -> dict:
async def get_disconnected_pvs(redis: RedisServiceDep) -> dict:
"""
Get list of all disconnected PVs.

Useful for diagnostics and debugging connection issues.
"""
try:
redis = get_redis_service()
disconnected = await redis.get_disconnected_pvs()

return {
Expand All @@ -273,15 +267,17 @@ async def get_disconnected_pvs() -> dict:


@router.get("/stale", dependencies=[Security(require_read_access)])
async def get_stale_pvs(max_age_seconds: float = 300) -> dict:
async def get_stale_pvs(
redis: RedisServiceDep,
max_age_seconds: float = 300,
) -> dict:
"""
Get list of stale PVs (connected but not updated recently).

Args:
max_age_seconds: Consider stale if not updated in this many seconds
"""
try:
redis = get_redis_service()
stale = await redis.get_stale_pvs(max_age_seconds=max_age_seconds)

return {
Expand Down Expand Up @@ -386,7 +382,7 @@ async def force_open_circuit(circuit_name: str) -> dict:


@router.get("/monitor/status", dependencies=[Security(require_read_access)])
async def monitor_process_status() -> dict:
async def monitor_process_status(redis: RedisServiceDep) -> dict:
"""
Check if the separate PV Monitor process is alive via Redis heartbeat.

Expand All @@ -399,8 +395,6 @@ async def monitor_process_status() -> dict:
leader: Instance ID of current monitor leader (if available)
"""
try:
redis = get_redis_service()

if not redis.is_connected():
return {
"status": "unknown",
Expand Down
10 changes: 3 additions & 7 deletions app/api/v1/jobs.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
"""API endpoints for job status monitoring."""
from fastapi import Depends, Security, APIRouter
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import Security, APIRouter

from app.db.session import get_db
from app.dependencies import require_read_access
from app.dependencies import JobServiceDep, require_read_access
from app.api.responses import APIException, success_response
from app.services.job_service import JobService

router = APIRouter(prefix="/jobs", tags=["Jobs"])


@router.get("/{job_id}", dependencies=[Security(require_read_access)])
async def get_job_status(job_id: str, db: AsyncSession = Depends(get_db)) -> dict:
async def get_job_status(job_id: str, job_service: JobServiceDep) -> dict:
"""
Get the status of a background job.

Returns the current status, progress percentage, and result when complete.
Poll this endpoint to track the progress of async operations.
"""
job_service = JobService(db)
job = await job_service.get_job(job_id)
if not job:
raise APIException(404, f"Job {job_id} not found", 404)
Expand Down
Loading
Loading