runboard: initial commit

This commit is contained in:
2025-04-26 20:17:40 -06:00
parent ddd295b089
commit 1712d2d192
10 changed files with 1260 additions and 0 deletions
Generated
+1090
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1,2 +1,3 @@
[workspace]
resolver = "3"
members = ["runboard"]
+14
View File
@@ -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"] }
+29
View File
@@ -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);
}
+1
View File
@@ -0,0 +1 @@
+9
View File
@@ -0,0 +1,9 @@
redis_url = "redis:6879"
listen_address = "127.0.0.1"
port = 9684
[agencies]
[agencies.rtd]
url = "https://example.com"
+29
View File
@@ -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)
}
+12
View File
@@ -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 {})
}
+53
View File
@@ -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(())
}
+22
View File
@@ -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),
})
}