systemprompt-oauth 0.2.1

OAuth 2.0 / OIDC with PKCE, token introspection, and audience/issuer validation for systemprompt.io AI governance infrastructure. WebAuthn and JWT auth for the MCP governance pipeline.
Documentation
use serde::{Deserialize, Serialize};
use systemprompt_identifiers::{ClientId, ClientType};

#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ClientAnalyticsRow {
    pub client_id: ClientId,
    pub session_count: i64,
    pub unique_users: i64,
    pub total_requests: i64,
    pub total_tokens: i64,
    pub total_cost_microdollars: i64,
    pub avg_session_duration_seconds: f64,
    pub avg_response_time_ms: f64,
    pub first_seen: chrono::DateTime<chrono::Utc>,
    pub last_seen: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientAnalytics {
    pub client_id: ClientId,
    pub client_type: ClientType,
    pub session_count: i64,
    pub unique_users: i64,
    pub total_requests: i64,
    pub total_tokens: i64,
    pub total_cost_microdollars: i64,
    pub avg_session_duration_seconds: f64,
    pub avg_response_time_ms: f64,
    pub first_seen: String,
    pub last_seen: String,
}

#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ClientErrorAnalyticsRow {
    pub client_id: ClientId,
    pub error_count: i64,
    pub affected_sessions: i64,
    pub last_error: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientErrorAnalytics {
    pub client_id: ClientId,
    pub error_count: i64,
    pub affected_sessions: i64,
    pub last_error: String,
}

impl From<ClientAnalyticsRow> for ClientAnalytics {
    fn from(row: ClientAnalyticsRow) -> Self {
        let client_type = row.client_id.client_type();
        Self {
            client_id: row.client_id,
            client_type,
            session_count: row.session_count,
            unique_users: row.unique_users,
            total_requests: row.total_requests,
            total_tokens: row.total_tokens,
            total_cost_microdollars: row.total_cost_microdollars,
            avg_session_duration_seconds: row.avg_session_duration_seconds,
            avg_response_time_ms: row.avg_response_time_ms,
            first_seen: row.first_seen.to_rfc3339(),
            last_seen: row.last_seen.to_rfc3339(),
        }
    }
}

impl From<ClientErrorAnalyticsRow> for ClientErrorAnalytics {
    fn from(row: ClientErrorAnalyticsRow) -> Self {
        Self {
            client_id: row.client_id,
            error_count: row.error_count,
            affected_sessions: row.affected_sessions,
            last_error: row.last_error.unwrap_or_else(String::new),
        }
    }
}