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}