runboard: initial commit
This commit is contained in:
Generated
+1090
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["runboard"]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "runboard"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
axum = { version = "0.8.3", features = ["json"] }
|
||||
figment = { version = "0.10.19", features = ["toml"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
time = { version = "0.3.41", features = ["serde"] }
|
||||
tokio = { version = "1.44.2", features = ["rt-multi-thread"] }
|
||||
tokio-valkey = "0.0.0-alpha2"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
@@ -0,0 +1,29 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
// Get full commit hash
|
||||
let output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.expect("Failed to execute git");
|
||||
|
||||
let mut git_hash = if output.status.success() {
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
};
|
||||
|
||||
// Check if working tree is dirty
|
||||
let output = Command::new("git")
|
||||
.args(["diff", "--quiet"])
|
||||
.status()
|
||||
.expect("Failed to check git dirty status");
|
||||
|
||||
let dirty = !output.success();
|
||||
|
||||
if dirty {
|
||||
git_hash.push_str("-dirty");
|
||||
}
|
||||
|
||||
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
redis_url = "redis:6879"
|
||||
listen_address = "127.0.0.1"
|
||||
port = 9684
|
||||
|
||||
[agencies]
|
||||
[agencies.rtd]
|
||||
url = "https://example.com"
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use figment::{
|
||||
Figment,
|
||||
providers::{Format, Toml},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
redis_url: Url,
|
||||
pub listen_address: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
pub fn get_config() -> Result<Config> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
return Err(anyhow!("usage: runboard config.toml"));
|
||||
}
|
||||
|
||||
let config_path = &args[1];
|
||||
|
||||
Figment::new()
|
||||
.merge(Toml::file(config_path))
|
||||
.extract()
|
||||
.map_err(anyhow::Error::msg)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use axum::{Json, extract::Path, extract::State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LatestResponse {}
|
||||
|
||||
pub async fn post_latest(
|
||||
Path(agency): Path<String>,
|
||||
State(_): State<std::sync::Arc<crate::AppState>>,
|
||||
) -> Json<LatestResponse> {
|
||||
Json(LatestResponse {})
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use figment::{
|
||||
Figment,
|
||||
providers::{Format, Toml},
|
||||
};
|
||||
|
||||
use url::Url;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
mod config;
|
||||
mod latest;
|
||||
mod status;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct AppState {
|
||||
status: status::StatusState,
|
||||
config: config::Config,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let app_state = std::sync::Arc::new(AppState {
|
||||
status: status::StatusState {
|
||||
start_time: time::UtcDateTime::now(),
|
||||
},
|
||||
config: config::get_config()?,
|
||||
});
|
||||
|
||||
let listener = tokio::net::TcpListener::bind((
|
||||
app_state.config.listen_address.clone(),
|
||||
app_state.config.port,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// build our application with a single route
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { "Hello, World!" }))
|
||||
.route("/status", get(status::get_status))
|
||||
.route("/latest/{agency}", post(latest::post_latest))
|
||||
.with_state(app_state);
|
||||
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use axum::{Json, extract::State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct StatusState {
|
||||
pub start_time: time::UtcDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct StatusResponse {
|
||||
version: String,
|
||||
uptime: time::Duration,
|
||||
}
|
||||
|
||||
pub async fn get_status(
|
||||
State(state): State<std::sync::Arc<crate::AppState>>,
|
||||
) -> Json<StatusResponse> {
|
||||
Json(StatusResponse {
|
||||
version: env!("GIT_HASH").to_string(),
|
||||
uptime: (time::UtcDateTime::now() - state.status.start_time),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user