init
This commit is contained in:
2
app/api/__init__.py
Normal file
2
app/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# api package
|
||||
|
||||
41
app/api/routes_config.py
Normal file
41
app/api/routes_config.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
app/api/routes_config.py
|
||||
HTTP endpoints for reading and updating runtime configuration.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.models import ConfigResponse, UpdateConfigRequest
|
||||
from app.services import config_service, event_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/config", tags=["config"])
|
||||
|
||||
|
||||
@router.get("", response_model=ConfigResponse)
|
||||
def get_config():
|
||||
return config_service.get_config()
|
||||
|
||||
|
||||
@router.patch("", response_model=ConfigResponse)
|
||||
def update_config(body: UpdateConfigRequest):
|
||||
cfg = config_service.update_config(
|
||||
default_wait_seconds=body.default_wait_seconds,
|
||||
default_empty_response=body.default_empty_response,
|
||||
agent_stale_after_seconds=body.agent_stale_after_seconds,
|
||||
)
|
||||
event_service.broadcast(
|
||||
"config.updated",
|
||||
{
|
||||
"default_wait_seconds": cfg.default_wait_seconds,
|
||||
"default_empty_response": cfg.default_empty_response,
|
||||
"agent_stale_after_seconds": cfg.agent_stale_after_seconds,
|
||||
},
|
||||
)
|
||||
return cfg
|
||||
|
||||
72
app/api/routes_instructions.py
Normal file
72
app/api/routes_instructions.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
app/api/routes_instructions.py
|
||||
HTTP endpoints for instruction CRUD.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import Response
|
||||
|
||||
from app.models import (
|
||||
CreateInstructionRequest,
|
||||
InstructionCreateResponse,
|
||||
InstructionListResponse,
|
||||
UpdateInstructionRequest,
|
||||
)
|
||||
from app.services import event_service, instruction_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/instructions", tags=["instructions"])
|
||||
|
||||
|
||||
@router.get("", response_model=InstructionListResponse)
|
||||
def list_instructions(
|
||||
status: str = Query(default="all", pattern="^(pending|consumed|all)$")
|
||||
):
|
||||
items = instruction_service.list_instructions(status_filter=status)
|
||||
return InstructionListResponse(items=items)
|
||||
|
||||
|
||||
@router.post("", response_model=InstructionCreateResponse, status_code=201)
|
||||
def create_instruction(body: CreateInstructionRequest):
|
||||
item = instruction_service.create_instruction(body.content)
|
||||
event_service.broadcast(
|
||||
"instruction.created",
|
||||
{"item": item.model_dump(mode="json")},
|
||||
)
|
||||
return InstructionCreateResponse(item=item)
|
||||
|
||||
|
||||
@router.patch("/{instruction_id}", response_model=InstructionCreateResponse)
|
||||
def update_instruction(instruction_id: str, body: UpdateInstructionRequest):
|
||||
try:
|
||||
item = instruction_service.update_instruction(instruction_id, body.content)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="Instruction not found")
|
||||
except PermissionError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc))
|
||||
|
||||
event_service.broadcast(
|
||||
"instruction.updated",
|
||||
{"item": item.model_dump(mode="json")},
|
||||
)
|
||||
return InstructionCreateResponse(item=item)
|
||||
|
||||
|
||||
@router.delete("/{instruction_id}", status_code=204)
|
||||
def delete_instruction(instruction_id: str):
|
||||
try:
|
||||
instruction_service.delete_instruction(instruction_id)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="Instruction not found")
|
||||
except PermissionError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc))
|
||||
|
||||
event_service.broadcast("instruction.deleted", {"id": instruction_id})
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
77
app/api/routes_status.py
Normal file
77
app/api/routes_status.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
app/api/routes_status.py
|
||||
HTTP endpoints for server/agent status and SSE events.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app.models import (
|
||||
AgentInfo,
|
||||
HealthResponse,
|
||||
QueueCounts,
|
||||
ServerInfo,
|
||||
StatusResponse,
|
||||
)
|
||||
from app.services import config_service, event_service, instruction_service, status_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["status"])
|
||||
|
||||
|
||||
@router.get("/healthz", response_model=HealthResponse)
|
||||
def health():
|
||||
return HealthResponse(status="ok", server_time=datetime.now(timezone.utc))
|
||||
|
||||
|
||||
@router.get("/api/status", response_model=StatusResponse)
|
||||
def get_status():
|
||||
agent_row = status_service.get_latest_agent_activity()
|
||||
connected = status_service.is_agent_connected()
|
||||
|
||||
agent_info = AgentInfo(
|
||||
connected=connected,
|
||||
last_seen_at=datetime.fromisoformat(agent_row["last_seen_at"]) if agent_row else None,
|
||||
last_fetch_at=datetime.fromisoformat(agent_row["last_fetch_at"]) if agent_row else None,
|
||||
agent_id=agent_row["agent_id"] if agent_row else None,
|
||||
)
|
||||
|
||||
counts = instruction_service.get_queue_counts()
|
||||
cfg = config_service.get_config()
|
||||
|
||||
return StatusResponse(
|
||||
server=ServerInfo(
|
||||
status="up",
|
||||
started_at=status_service.server_started_at(),
|
||||
),
|
||||
agent=agent_info,
|
||||
queue=QueueCounts(**counts),
|
||||
settings=cfg,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/events")
|
||||
async def sse_events():
|
||||
q = event_service.subscribe()
|
||||
|
||||
async def stream():
|
||||
async for chunk in event_service.event_generator(q):
|
||||
yield chunk
|
||||
|
||||
return StreamingResponse(
|
||||
stream(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Accel-Buffering": "no",
|
||||
"Connection": "keep-alive",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user