Initialize project with basic backend setup
This commit is contained in:
5
.env
Normal file
5
.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// .env.example (copy to .env for local use)
|
||||||
|
|
||||||
|
RUST_LOG=info
|
||||||
|
BIND_ADDRESS=127.0.0.1:3000
|
||||||
|
# DATABASE_URL=postgres://gerard@localhost/db (not used yet)
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
2345
Cargo.lock
generated
Normal file
2345
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "employee-tracking-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.8.4"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.142"
|
||||||
|
sqlx = { version = "0.8.6", features = ["runtime-tokio", "tls-native-tls"] }
|
||||||
|
tokio = { version = "1.47.1", features = ["full", "rt-multi-thread", "signal"] }
|
||||||
|
tower-http = { version = "0.6.6", features = ["trace"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
36
src/config.rs
Normal file
36
src/config.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// src/config.rs
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub bind_address: SocketAddr,
|
||||||
|
pub database_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn from_env() -> Result<Self, String> {
|
||||||
|
let bind_address_str =
|
||||||
|
env::var("BIND_ADDRESS").unwrap_or_else(|_| "127.0.0.1:3000".to_string());
|
||||||
|
|
||||||
|
let bind_address = SocketAddr::from_str(&bind_address_str)
|
||||||
|
.map_err(|e| format!("Invalid BIND_ADDRESS: {}", e))?;
|
||||||
|
|
||||||
|
#[cfg(feature = "no-auth")]
|
||||||
|
if bind_address.ip() != std::net::IpAddr::from([127, 0, 0, 1]) {
|
||||||
|
error!("In no-auth mode, BIND_ADDRESS must be 127.0.0.1");
|
||||||
|
return Err("In no-auth mode, BIND_ADDRESS must be 127.0.0.1".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let database_url = env::var("DATABASE_URL").ok();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
bind_address,
|
||||||
|
database_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/health.rs
Normal file
10
src/health.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// src/health.rs
|
||||||
|
|
||||||
|
use axum::Json;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn health() -> impl IntoResponse {
|
||||||
|
(StatusCode::OK, Json(json!({ "status": "ok" })))
|
||||||
|
}
|
||||||
82
src/main.rs
Normal file
82
src/main.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// src/main.rs
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use axum::{Router, routing::get};
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
use tokio::signal;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use tracing::{error, info};
|
||||||
|
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod health;
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Load environment variables from .env file
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
// Initialize tracing
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(fmt::layer())
|
||||||
|
.with(EnvFilter::from_env("RUST_LOG"))
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
let config = match Config::from_env() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to load config: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "no-auth")]
|
||||||
|
info!("NO-AUTH MODE ENABLED");
|
||||||
|
|
||||||
|
info!("Server starting on {}", config.bind_address);
|
||||||
|
|
||||||
|
// Build the Axum router
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/health", get(health::health))
|
||||||
|
.layer(TraceLayer::new_for_http());
|
||||||
|
|
||||||
|
// Run the server
|
||||||
|
let listener = tokio::net::TcpListener::bind(config.bind_address)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown_signal() {
|
||||||
|
let ctrl_c = async {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("Failed to install Ctrl+C handler");
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let terminate = async {
|
||||||
|
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||||
|
.expect("Failed to install signal handler")
|
||||||
|
.recv()
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let terminate = std::future::pending::<()>();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = ctrl_c => {},
|
||||||
|
_ = terminate => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Signal received, shutting down server gracefully");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user