143 lines
3.1 KiB
Rust
143 lines
3.1 KiB
Rust
use anyhow::{
|
|
Context,
|
|
};
|
|
use askama::Template;
|
|
use axum::{
|
|
extract::{
|
|
Path,
|
|
State,
|
|
},
|
|
http::{
|
|
StatusCode,
|
|
},
|
|
response::{
|
|
IntoResponse,
|
|
Response,
|
|
},
|
|
Router,
|
|
routing::{
|
|
get,
|
|
},
|
|
};
|
|
use flake_tracker::{
|
|
storage::{
|
|
Storage,
|
|
},
|
|
templates::{
|
|
FlakeInfoTemplate,
|
|
FlakesTemplate,
|
|
IndexTemplate,
|
|
RevisionTemplate,
|
|
},
|
|
};
|
|
|
|
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())
|
|
}
|
|
}
|
|
|
|
fn render_template<T: Template>(t: &T) -> anyhow::Result<Response> {
|
|
let body = t.render()
|
|
.context("Failed to render template")?;
|
|
|
|
Ok((
|
|
[
|
|
("Content-Type", T::MIME_TYPE),
|
|
],
|
|
body,
|
|
).into_response())
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct AppState {
|
|
storage: Storage,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let storage = Storage::connect("sqlite://flake-tracker.db")
|
|
.await
|
|
.context("Failed to connect to database")?;
|
|
|
|
let mut state = AppState {
|
|
storage,
|
|
};
|
|
|
|
let mut app = Router::new()
|
|
.route("/", get(route_index))
|
|
.route("/flakes", get(route_flakes))
|
|
.route("/f/{uri}", get(route_flake_info))
|
|
.route("/r/{revision_uri}", get(route_revision))
|
|
.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>,
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
Ok(render_template(&IndexTemplate {})?)
|
|
}
|
|
|
|
async fn route_flakes(
|
|
State(state): State<AppState>,
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
Ok(render_template(&FlakesTemplate {
|
|
flakes: state.storage.flakes().await?,
|
|
})?)
|
|
}
|
|
|
|
async fn route_flake_info(
|
|
State(state): State<AppState>,
|
|
Path(uri): Path<String>,
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
Ok(render_template(&FlakeInfoTemplate {
|
|
uri: uri.clone(),
|
|
flake_revisions: state.storage.revisions_from_flake(&uri).await?,
|
|
})?)
|
|
}
|
|
|
|
async fn route_revision(
|
|
State(state): State<AppState>,
|
|
Path(revision_uri): Path<String>,
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
|
|
Ok(render_template(&RevisionTemplate {
|
|
revision_uri: revision_uri.clone(),
|
|
inputs: state.storage.inputs_for_revision(&revision_uri).await?,
|
|
})?)
|
|
}
|