sentrystr-api 0.2.0

REST API for serving SentryStr events from Nostr network
Documentation
use axum::{Json, extract::Query};
use chrono::Utc;
use nostr::PublicKey;
use sentrystr::Level;
use sentrystr_collector::{EventCollector, EventFilter};

use crate::models::{EventQuery, EventResponse, EventsResponse, HealthResponse};
use crate::{ApiError, Result};

pub async fn health() -> Json<HealthResponse> {
    Json(HealthResponse {
        status: "ok".to_string(),
        timestamp: Utc::now(),
    })
}

pub async fn get_events(Query(params): Query<EventQuery>) -> Result<Json<EventsResponse>> {
    let relays = vec!["wss://relay.damus.io".to_string()];

    let collector = EventCollector::new(relays)
        .await
        .map_err(|e| ApiError::Collection(e.to_string()))?;

    let mut filter = EventFilter::new();

    if let Some(limit) = params.limit {
        filter = filter.with_limit(limit);
    } else {
        filter = filter.with_limit(100);
    }

    if let Some(author_str) = params.author {
        let author = PublicKey::parse(&author_str)
            .map_err(|e| ApiError::BadRequest(format!("Invalid public key: {}", e)))?;
        filter = filter.with_author(author);
    }

    if let Some(level_str) = params.level {
        let level = match level_str.to_lowercase().as_str() {
            "debug" => Level::Debug,
            "info" => Level::Info,
            "warning" => Level::Warning,
            "error" => Level::Error,
            "fatal" => Level::Fatal,
            _ => return Err(ApiError::BadRequest("Invalid level".to_string())),
        };
        filter = filter.with_level(level);
    }

    if let Some(service) = params.service {
        filter = filter.with_service_filter(service);
    }

    if let Some(environment) = params.environment {
        filter = filter.with_environment_filter(environment);
    }

    if let Some(component) = params.component {
        filter = filter.with_component_filter(component);
    }

    if let Some(severity) = params.severity {
        filter = filter.with_severity_filter(severity);
    }

    if let Some(since) = params.since {
        filter = filter.with_since(since);
    }

    if let Some(until) = params.until {
        filter = filter.with_until(until);
    }

    let events = collector
        .collect_events(filter)
        .await
        .map_err(|e| ApiError::Collection(e.to_string()))?;

    collector
        .disconnect()
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;

    let response_events: Vec<EventResponse> = events
        .into_iter()
        .map(|event| EventResponse {
            nostr_event_id: event.nostr_event_id.to_string(),
            author: event.author.to_string(),
            received_at: event.received_at,
            event: crate::models::EventData {
                event_id: event.event.event_id,
                timestamp: event.event.timestamp,
                platform: event.event.platform,
                level: event.event.level,
                logger: event.event.logger,
                transaction: event.event.transaction,
                server_name: event.event.server_name,
                release: event.event.release,
                environment: event.event.environment,
                message: event.event.message,
                tags: event.event.tags,
                extra: event.event.extra,
            },
        })
        .collect();

    let total = response_events.len();

    Ok(Json(EventsResponse {
        events: response_events,
        total,
    }))
}