molten_api/startup.rs
1//! This module handles the application startup process for the Molten API.
2//!
3//! It includes logic for connecting to the database, running migrations,
4//! building the Axum application, and starting the HTTP server.
5use tokio::net::TcpListener;
6
7use crate::handlers;
8use crate::{error::BuildError, state::AppState};
9use axum::{
10 Router,
11 http::StatusCode,
12 routing::{get, post},
13};
14use molten_config::settings_parser::Settings;
15use sea_orm::{Database, DatabaseConnection, DbErr};
16
17/// Represents the Molten API application, encapsulating the server's network listener,
18/// application-wide state, and the port it is bound to.
19pub struct Application {
20 listener: TcpListener,
21 state: AppState,
22 port: u16,
23}
24
25impl Application {
26 /// Builds a new `Application` instance by connecting to the database,
27 /// running pending migrations, and setting up the TCP listener.
28 ///
29 /// # Arguments
30 /// * `config` - The application settings loaded from configuration.
31 ///
32 /// # Returns
33 /// A `Result` which is `Ok` with the `Application` instance if successful,
34 /// or `Err` with a `BuildError` if any step fails.
35 pub async fn build(config: Settings) -> Result<Self, BuildError> {
36 // Connect to Database
37 let db: DatabaseConnection = Self::get_db_connection(&config).await?;
38 tracing::info!("Connected to database: {}", &config.database.database_name);
39
40 let state = AppState::new(db);
41 let addr = format!("{}:{}", config.application.host, config.application.port);
42 tracing::info!("Listening on {}", addr);
43 let listener = TcpListener::bind(addr).await?;
44 let port = listener.local_addr().unwrap().port();
45
46 // Return Application
47 Ok(Self {
48 listener,
49 state,
50 port,
51 })
52 }
53
54 // This is useful because when the port in config is 0, a random port will be assigned
55 // which we need to know post hoc.
56 /// Port number getter
57 pub fn port(&self) -> u16 {
58 self.port
59 }
60
61 /// Establishes a database connection using the provided application settings.
62 ///
63 /// # Arguments
64 /// * `config` - A reference to the application settings.
65 ///
66 /// # Returns
67 /// A `Result` which is `Ok` with the `DatabaseConnection` if successful,
68 /// or `Err` with a `DbErr` if the connection fails.
69 async fn get_db_connection(config: &Settings) -> Result<DatabaseConnection, DbErr> {
70 Database::connect(config.database.get_connect_options()).await
71 }
72
73 /// Creates the Axum router with all routes and state attached.
74 ///
75 /// # Arguments
76 /// * `db` - The database connection
77 ///
78 /// # Returns
79 /// An axum Router
80 fn define_router(db: DatabaseConnection) -> Router {
81 let state = AppState::new(db);
82
83 Router::new()
84 .route("/health", get(|| async { StatusCode::OK }))
85 .route("/documents", post(handlers::create_document))
86 .route("/documents/{id}", get(handlers::get_document))
87 .route("/forms", post(handlers::create_form))
88 .route("/forms/{id}", get(handlers::get_form))
89 .route("/workflows", post(handlers::create_workflow))
90 .route("/workflows/{id}", get(handlers::get_workflow))
91 .with_state(state)
92 }
93
94 /// Runs the application
95 pub async fn run(self) -> Result<(), std::io::Error> {
96 let Application {
97 listener,
98 state,
99 port: _,
100 } = self;
101 let router = Self::define_router(state.db);
102 axum::serve(listener, router.into_make_service()).await
103 }
104}