Bootstrap web interface

This commit is contained in:
2025-02-01 13:20:09 +01:00
parent f9dafecab7
commit e707abbd65
3 changed files with 339 additions and 0 deletions

134
src/bin/web.rs Normal file
View File

@@ -0,0 +1,134 @@
use anyhow::{
Context,
};
use axum::{
extract::{
State,
},
http::{
StatusCode,
},
response::{
IntoResponse,
Response,
},
Router,
routing::{
get,
},
};
use sqlx::{
FromRow,
SqlitePool,
sqlite::SqlitePoolOptions,
};
#[derive(FromRow)]
struct StorageFlakeRevision {
revision_uri: String,
uri: Option<String>,
nix_store_path: Option<String>,
revision: Option<String>,
nar_hash: Option<String>,
last_modified: Option<i64>,
}
struct AppError(anyhow::Error);
impl std::fmt::Display for AppError {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, formatter)
}
}
impl std::fmt::Debug for AppError {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, formatter)
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Internal server error:\n{:?}", self),
)
.into_response()
}
}
impl<E> From<E> for AppError where E: Into<anyhow::Error> {
fn from(err: E) -> Self {
Self(err.into())
}
}
#[derive(Clone)]
struct AppState {
db: SqlitePool,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let db = SqlitePoolOptions::new()
.connect("sqlite://flake-tracker.db")
.await
.context("Failed to connect to database")?;
let mut state = AppState {
db,
};
let mut app = Router::new()
.route("/", get(route_index))
.route("/flakes", get(route_flakes))
.with_state(state);
let listener = tokio::net::TcpListener::bind("[::]:3000")
.await
.context("Failed to bind to port")?;
axum::serve(listener, app)
.await
.context("Failed to start web server")?;
Ok(())
}
async fn route_index(
State(state): State<AppState>,
) -> String {
String::from("Hello world")
}
async fn route_flakes(
State(state): State<AppState>,
) -> Result<String, AppError> {
let flake_revisions: Vec<StorageFlakeRevision> = sqlx::query_as("
SELECT
revision_uri,
uri,
nix_store_path,
revision,
nar_hash,
last_modified
FROM flake_revisions
GROUP BY uri
ORDER BY uri
")
.fetch_all(&state.db)
.await
.context("Failed to fetch data from database")?;
let mut out = String::new();
for flake_revision in flake_revisions {
if let Some(uri) = &flake_revision.uri {
out.push_str("- ");
out.push_str(&uri);
out.push_str("\n");
}
}
Ok(out)
}