Skip to main content

stint_server/
lib.rs

1//! Local HTTP API server for Stint.
2//!
3//! Provides a JSON API on localhost for editor plugins and integrations.
4
5mod api;
6
7use std::sync::Arc;
8use tokio::sync::Mutex;
9
10use axum::routing::{get, post};
11use axum::Router;
12use stint_core::service::StintService;
13use stint_core::storage::sqlite::SqliteStorage;
14use tokio::net::TcpListener;
15
16/// Runs the local API server on the given port.
17///
18/// Blocks until SIGINT/SIGTERM is received.
19pub fn run_server(port: u16) -> Result<(), Box<dyn std::error::Error>> {
20    let rt = tokio::runtime::Runtime::new()?;
21    rt.block_on(async { serve(port).await })
22}
23
24/// Async server implementation.
25async fn serve(port: u16) -> Result<(), Box<dyn std::error::Error>> {
26    let path = SqliteStorage::default_path();
27    let storage = SqliteStorage::open(&path)?;
28    let service = StintService::new(storage);
29    let state = Arc::new(Mutex::new(service));
30
31    let app = Router::new()
32        .route("/api/health", get(api::health))
33        .route("/api/status", get(api::status))
34        .route("/api/entries", get(api::entries))
35        .route("/api/projects", get(api::projects))
36        .route("/api/start", post(api::start))
37        .route("/api/stop", post(api::stop))
38        .with_state(state);
39
40    let addr = format!("127.0.0.1:{port}");
41    let listener = TcpListener::bind(&addr).await?;
42    println!("Stint API listening on http://{addr}");
43
44    axum::serve(listener, app)
45        .with_graceful_shutdown(shutdown_signal())
46        .await?;
47
48    println!("Server stopped.");
49    Ok(())
50}
51
52/// Waits for SIGINT or SIGTERM.
53async fn shutdown_signal() {
54    let ctrl_c = tokio::signal::ctrl_c();
55    #[cfg(unix)]
56    {
57        match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
58            Ok(mut sigterm) => {
59                tokio::select! {
60                    _ = ctrl_c => {},
61                    _ = sigterm.recv() => {},
62                }
63            }
64            Err(_) => {
65                // SIGTERM handler unavailable — fall back to ctrl-c only
66                ctrl_c.await.ok();
67            }
68        }
69    }
70    #[cfg(not(unix))]
71    {
72        ctrl_c.await.ok();
73    }
74}