//! Backend API server for `StA` (Smart Temperature & Appliance Control) //! //! `StA` is a web-based Modbus relay control system that provides `RESTful` API access //! to 8-channel relay devices. The system eliminates the need for specialized Modbus //! software, enabling browser-based relay control for automation and remote management. //! //! # Architecture //! //! This crate follows **Hexagonal Architecture** (Clean Architecture) with strict //! layer separation and inward-pointing dependencies: //! //! - **[`domain`]**: Pure business logic with no external dependencies (relay entities, value objects) //! - **[`application`]**: Use cases and orchestration logic (relay control, label management) //! - **[`infrastructure`]**: External integrations (Modbus TCP, `SQLite` persistence) //! - **[`presentation`]**: API contracts and DTOs (not yet used - see [`route`] for current API) //! //! Traditional modules (will be migrated to hexagonal layers): //! - **[`route`]**: HTTP API endpoints (will move to `presentation`) //! - **[`middleware`]**: Custom middleware (rate limiting, CORS) //! - **[`settings`]**: Configuration management from YAML + env vars //! - **[`startup`]**: Application builder and server configuration //! - **[`telemetry`]**: Logging and tracing setup //! //! # Current Features //! //! - Health check endpoints //! - Application metadata endpoints //! - Rate limiting middleware //! - CORS support //! - `OpenAPI` documentation //! //! # Planned Features (001-modbus-relay-control) //! //! - Modbus RTU over TCP communication with 8-channel relay devices //! - Real-time relay status monitoring //! - Individual relay control (on/off toggle) //! - Bulk relay operations (all on, all off) //! - Persistent relay labels (`SQLite` with `SQLx`) //! - Device health monitoring //! //! See `specs/001-modbus-relay-control/` for detailed specification. #![deny(clippy::all)] #![deny(clippy::pedantic)] #![deny(clippy::nursery)] #![warn(missing_docs)] #![allow(clippy::unused_async)] /// Custom middleware implementations pub mod middleware; /// API route handlers and endpoints pub mod route; /// Application configuration settings pub mod settings; /// Application startup and server configuration pub mod startup; /// Logging and tracing setup pub mod telemetry; /// Domain layer - Pure business logic with no external dependencies /// /// Contains domain entities, value objects, and business rules for the relay /// control system. This layer has no dependencies on frameworks or infrastructure. /// /// See `specs/constitution.md` for hexagonal architecture principles. pub mod domain; /// Application layer - Use cases and orchestration logic /// /// Coordinates domain entities to implement business use cases such as relay /// control, label management, and device health monitoring. pub mod application; /// Infrastructure layer - External integrations and adapters /// /// Implements interfaces defined in domain/application layers for external /// systems: Modbus TCP communication, SQLite persistence, HTTP clients. pub mod infrastructure; /// Presentation layer - API contracts and DTOs /// /// Defines data transfer objects and API request/response types. Currently /// unused - API handlers are in [`route`] module (legacy structure). pub mod presentation; type MaybeListener = Option>; async fn prepare(listener: MaybeListener) -> startup::Application { dotenvy::dotenv().ok(); let settings = settings::Settings::new().expect("Failed to read settings"); if !cfg!(test) { let subscriber = telemetry::get_subscriber(settings.debug); telemetry::init_subscriber(subscriber); } tracing::event!( target: "backend", tracing::Level::DEBUG, "Using these settings: {:?}", settings ); let application = startup::Application::build(settings, listener).await .expect("Failed to build application"); tracing::event!( target: "backend", tracing::Level::INFO, "Listening on http://{}:{}/", application.host(), application.port() ); tracing::event!( target: "backend", tracing::Level::INFO, "Documentation available at http://{}:{}/", application.host(), application.port() ); application } /// Runs the application with the specified TCP listener. /// /// # Errors /// /// Returns a `std::io::Error` if the server fails to start or encounters /// an I/O error during runtime (e.g., port already in use, network issues). #[cfg(not(tarpaulin_include))] pub async fn run(listener: MaybeListener) -> Result<(), std::io::Error> { let application = prepare(listener).await; application.make_app().run().await } #[cfg(test)] fn make_random_tcp_listener() -> poem::listener::TcpListener { let tcp_listener = std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener"); let port = tcp_listener.local_addr().unwrap().port(); poem::listener::TcpListener::bind(format!("127.0.0.1:{port}")) } #[cfg(test)] async fn get_test_app() -> startup::App { let tcp_listener = make_random_tcp_listener(); prepare(Some(tcp_listener)).await.make_app().into() }