Stats and error handling

This commit is contained in:
2025-07-15 13:24:50 +02:00
parent 6a5149fd4c
commit 412a84150e
13 changed files with 247 additions and 28 deletions

View File

@ -124,7 +124,7 @@ class ClientOperations:
existing_id = await self.get_client_id(FlexID.name(create_model.name))
if existing_id:
raise HTTPException(
status_code=400, detail="Error: A client already exists with this name."
status_code=400, detail="A client already exists with this name.", headers={"X-Model-Field": "name"}
)
deleted_id = await resolve_client_id(
self.session, create_model.name, include_deleted=True

View File

@ -8,6 +8,8 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from sshecret_backend.api.common import get_system_stats
from sshecret_backend.api.schemas import SystemStats
from sshecret_backend.types import AsyncDBSessionDep
from sshecret_backend.api.clients.schemas import (
ClientCreate,
@ -149,4 +151,11 @@ def create_client_router(get_db_session: AsyncDBSessionDep) -> APIRouter:
client_op = ClientOperations(session, request)
return await client_op.update_client_policies(client_id, policy_update)
@router.get("/stats")
async def get_stats(
session: Annotated[AsyncSession, Depends(get_db_session)],
) -> SystemStats:
"""Get system stats."""
return await get_system_stats(session)
return router

View File

@ -6,11 +6,12 @@ import uuid
from dataclasses import dataclass, field
import bcrypt
from sqlalchemy import Select
from sqlalchemy import Select, distinct, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from sshecret_backend.models import Client, ClientAccessPolicy
from sshecret_backend.api.schemas import SystemStats
from sshecret_backend.models import AuditLog, Client, ClientAccessPolicy, ClientSecret
LOG = logging.getLogger(__name__)
RE_UUID = re.compile(
@ -137,3 +138,24 @@ async def create_new_client_version(
await session.flush()
await refresh_client(session, new_client)
return new_client
async def get_system_stats(session: AsyncSession) -> SystemStats:
"""Get system stats."""
client_count_expr = (
select(func.count("*"))
.select_from(Client)
.where(Client.is_deleted.is_not(True))
.where(Client.is_active.is_not(False))
.where(Client.is_system.is_not(True))
)
client_count = (await session.scalars(client_count_expr)).one()
secret_count_expr = select(func.count(distinct(ClientSecret.name))).where(
ClientSecret.deleted.is_not(True)
)
secret_count = (await session.scalars(secret_count_expr)).one()
audit_count_expr = select(func.count("*")).select_from(AuditLog)
audit_count = (await session.scalars(audit_count_expr)).one()
return SystemStats(
clients=client_count, secrets=secret_count, audit_events=audit_count
)

View File

@ -7,3 +7,11 @@ class BodyValue(BaseModel):
"""A generic model with just a value parameter."""
value: str
class SystemStats(BaseModel):
"""Generic system stats."""
clients: int
secrets: int
audit_events: int

View File

@ -146,6 +146,7 @@ class ClientSecretOperations:
raise HTTPException(
status_code=400,
detail="Cannot add a secret. A different secret with the same name already exists.",
headers={"X-Model-Field": "name"},
)
secret = ClientSecret(
name=name,