1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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();
        }
    };
}

// #[debug_handler]
pub async fn zones_list(
    State(state): State<GoatState>,
    mut session: Session,
    OriginalUri(path): OriginalUri,
) -> impl IntoResponse {
    // if let Err(e) = check_logged_in(&state, &mut session, path).await {
    //     return e.into_response();
    // }
    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!");
        // TODO: this should redirect to the current page
        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()),
    };

    // TODO: check the database to make sure they're actually legit and not disabled and blah
    Ok(user)
}

#[derive(Template)]
#[template(path = "dashboard.html")]
struct DashboardTemplate /*<'a>*/ {
    // name: &'a str,
    pub user_is_admin: bool,
}

// #[debug_handler]
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,
    };
    // Html::from()).into_response()
    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())
}