use crate::datastore::Command;
use crate::db::User;
use crate::web::utils::{redirect_to_dashboard, redirect_to_login, redirect_to_zones_list};
use crate::zones::FileZone;
use askama::Template;
use axum::debug_handler;
use axum::extract::{OriginalUri, Path, State};
use axum::http::{Response, Uri};
use axum::response::{IntoResponse, Redirect};
use axum::routing::get;
use axum::Router;
use tower_sessions::Session;
use super::GoatState;
mod admin_ui;
mod user_settings;
#[derive(Template)]
#[template(path = "view_zones.html")]
struct TemplateViewZones {
zones: Vec<FileZone>,
pub user_is_admin: bool,
}
#[derive(Template)]
#[template(path = "view_zone.html")]
struct TemplateViewZone {
zone: FileZone,
pub user_is_admin: bool,
}
macro_rules! check_logged_in {
( $state:tt, $session:tt, $path:tt ) => {
if let Err(e) = check_logged_in(&mut $session, $path).await {
return e.into_response();
}
};
}
pub async fn zones_list(
State(state): State<GoatState>,
mut session: Session,
OriginalUri(path): OriginalUri,
) -> impl IntoResponse {
check_logged_in!(state, session, path);
let (os_tx, os_rx) = tokio::sync::oneshot::channel();
let offset = 0;
let limit = 20;
let user: User = match session.get("user").await.unwrap() {
Some(val) => {
log::info!("current user: {val:?}");
val
}
None => return redirect_to_login().into_response(),
};
log::trace!("Sending request for zones");
if let Err(err) = state
.read()
.await
.tx
.send(Command::GetZoneNames {
resp: os_tx,
user: user.clone(),
offset,
limit,
})
.await
{
eprintln!("failed to send GetZoneNames command to datastore: {err:?}");
log::error!("failed to send GetZoneNames command to datastore: {err:?}");
return redirect_to_dashboard().into_response();
};
let zones = os_rx.await.expect("Failed to get response: {res:?}");
log::debug!("about to return zone list... found {} zones", zones.len());
let context = TemplateViewZones {
zones,
user_is_admin: user.admin,
};
Response::builder()
.status(200)
.body(context.render().unwrap())
.unwrap()
.into_response()
}
#[debug_handler]
pub async fn zone_view(
OriginalUri(path): OriginalUri,
Path(name_or_id): Path<i64>,
State(state): State<GoatState>,
mut session: Session,
) -> impl IntoResponse {
let user = check_logged_in(&mut session, path)
.await
.map_err(|err| err.into_response())
.unwrap();
let (os_tx, os_rx) = tokio::sync::oneshot::channel();
let cmd = Command::GetZone {
resp: os_tx,
id: Some(name_or_id),
name: None,
};
log::debug!("{cmd:?}");
if let Err(err) = state.read().await.tx.send(cmd).await {
eprintln!("failed to send GetZone command to datastore: {err:?}");
log::error!("failed to send GetZone command to datastore: {err:?}");
return redirect_to_zones_list().into_response();
};
let zone = match os_rx.await.expect("Failed to get response: {res:?}") {
Some(value) => value,
None => todo!("Send a not found"),
};
log::trace!("Returning zone: {zone:?}");
let context = TemplateViewZone {
zone,
user_is_admin: user.admin,
};
Response::new(context.render().unwrap()).into_response()
}
pub async fn check_logged_in(session: &mut Session, path: Uri) -> Result<User, Redirect> {
let authref = session.get::<String>("authref").await.unwrap();
let redirect_path = Some(path.path_and_query().unwrap().to_string());
if authref.is_none() {
session.clear().await;
session
.insert("redirect", redirect_path)
.await
.map_err(|e| log::debug!("Couldn't store redirect for user: {e:?}"))
.unwrap();
log::warn!("Not-logged-in-user tried to log in, how rude!");
return Err(redirect_to_login());
}
log::debug!("session ok!");
let user = match session.get("user").await.unwrap() {
Some(val) => val,
None => return Err(redirect_to_login()),
};
Ok(user)
}
#[derive(Template)]
#[template(path = "dashboard.html")]
struct DashboardTemplate {
pub user_is_admin: bool,
}
pub async fn dashboard(mut session: Session, OriginalUri(path): OriginalUri) -> impl IntoResponse {
let user = match check_logged_in(&mut session, path).await {
Ok(val) => val,
Err(err) => return err.into_response(),
};
let context = DashboardTemplate {
user_is_admin: user.admin,
};
Response::builder()
.status(200)
.body(context.render().unwrap())
.unwrap()
.into_response()
}
pub fn new() -> Router<GoatState> {
Router::new()
.route("/", get(dashboard))
.route("/zones/:id", get(zone_view))
.route("/zones/list", get(zones_list))
.nest("/settings", user_settings::router())
.nest("/admin", admin_ui::router())
}