Bootstrap web interface
This commit is contained in:
134
src/bin/web.rs
Normal file
134
src/bin/web.rs
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user