sentrystr_api/
lib.rs

1//! # SentryStr API
2//!
3//! REST API server for SentryStr event collection and querying.
4//!
5//! ## Quick Start
6//!
7//! ```rust
8//! use sentrystr_api::create_app;
9//! use sentrystr_collector::EventCollector;
10//! use std::sync::Arc;
11//!
12//! #[tokio::main]
13//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
14//!     let relays = vec!["wss://relay.damus.io".to_string()];
15//!     let collector = Arc::new(EventCollector::new(relays).await?);
16//!
17//!     let app = create_app(collector);
18//!
19//!     let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
20//!     println!("SentryStr API server running on http://localhost:3000");
21//!
22//!     axum::serve(listener, app).await?;
23//!     Ok(())
24//! }
25//! ```
26//!
27//! ## API Endpoints
28//!
29//! ### GET /events
30//! Query events with optional filters:
31//! ```bash
32//! curl "http://localhost:3000/events?limit=10&level=error"
33//! curl "http://localhost:3000/events?author=npub1...&since=2024-01-01T00:00:00Z"
34//! ```
35//!
36//! ### GET /events/{author}
37//! Get events by specific author:
38//! ```bash
39//! curl "http://localhost:3000/events/npub1abc123.../events"
40//! ```
41//!
42//! ### GET /health
43//! Health check endpoint:
44//! ```bash
45//! curl "http://localhost:3000/health"
46//! ```
47//!
48//! ## With Tracing Integration
49//!
50//! ```rust
51//! use sentrystr_api::create_app;
52//! use sentrystr_collector::EventCollector;
53//! use sentrystr_tracing::SentryStrTracingBuilder;
54//! use tracing::{info, error};
55//! use std::sync::Arc;
56//!
57//! #[tokio::main]
58//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
59//!     // Setup tracing for the API server itself
60//!     SentryStrTracingBuilder::new()
61//!         .with_generated_keys_and_relays(vec!["wss://relay.damus.io".to_string()])
62//!         .init()
63//!         .await?;
64//!
65//!     let relays = vec!["wss://relay.damus.io".to_string()];
66//!     let collector = Arc::new(EventCollector::new(relays).await?);
67//!     let app = create_app(collector);
68//!
69//!     info!("Starting SentryStr API server");
70//!
71//!     let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
72//!     axum::serve(listener, app).await?;
73//!
74//!     Ok(())
75//! }
76//! ```
77
78pub mod api;
79pub mod handlers;
80pub mod models;
81
82pub use api::create_app;
83pub use handlers::*;
84pub use models::*;
85
86pub type Result<T> = std::result::Result<T, ApiError>;
87
88#[derive(Debug)]
89pub enum ApiError {
90    Collection(String),
91    Internal(String),
92    BadRequest(String),
93}
94
95impl std::fmt::Display for ApiError {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            ApiError::Collection(msg) => write!(f, "Collection error: {}", msg),
99            ApiError::Internal(msg) => write!(f, "Internal error: {}", msg),
100            ApiError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
101        }
102    }
103}
104
105impl std::error::Error for ApiError {}
106
107impl axum::response::IntoResponse for ApiError {
108    fn into_response(self) -> axum::response::Response {
109        let (status, error_message) = match self {
110            ApiError::Collection(msg) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg),
111            ApiError::Internal(msg) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg),
112            ApiError::BadRequest(msg) => (axum::http::StatusCode::BAD_REQUEST, msg),
113        };
114
115        let body = serde_json::json!({
116            "error": error_message
117        });
118
119        (status, axum::Json(body)).into_response()
120    }
121}