forked from asklyphe-public/asklyphe
267 lines
9.9 KiB
Rust
267 lines
9.9 KiB
Rust
/*
|
|
* asklyphe-frontend routes/index.rs
|
|
* - http routes for homepages
|
|
*
|
|
* Copyright (C) 2025 Real Microsoft, LLC
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::Ordering;
|
|
use askama::Template;
|
|
use askama_axum::IntoResponse;
|
|
use asklyphe_common::nats::authservice::{AuthError, AuthRequest, AuthResponse, AuthServiceQuery, AuthServiceRequest, AuthServiceResponse, LogoutRequest, LogoutResponse};
|
|
use asklyphe_common::nats::authservice::admin::{Stats, StatsRequest, StatsResponse};
|
|
use asklyphe_common::nats::authservice::announcements::{LatestAnnouncementRequest, LatestAnnouncementResponse};
|
|
use asklyphe_common::nats::authservice::profile::{UserInfoRequest, UserInfoResponse};
|
|
use asklyphe_common::nats::comms;
|
|
use asklyphe_common::nats::comms::{ServiceError, ServiceResponse};
|
|
use async_nats::jetstream;
|
|
use axum::Extension;
|
|
use axum::response::Redirect;
|
|
use axum_extra::extract::CookieJar;
|
|
use serde::Serialize;
|
|
use tokio::sync::Mutex;
|
|
use tracing::error;
|
|
use tracing::log::warn;
|
|
use crate::{BUILT_ON, GIT_COMMIT, Opts, ALPHA, VERSION, WEBSITE_COUNT, YEAR};
|
|
use crate::routes::{authenticate_user, Themes, UserInfo};
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct FrontpageAnnouncement {
|
|
pub url: String,
|
|
pub title: String,
|
|
pub content: String,
|
|
pub date: String,
|
|
}
|
|
|
|
async fn latest_announcement(nats: Arc<jetstream::Context>) -> Option<FrontpageAnnouncement> {
|
|
let response = comms::query_service(
|
|
comms::Query::AuthService(AuthServiceQuery {
|
|
request: AuthServiceRequest::LatestAnnouncementRequest(LatestAnnouncementRequest {}),
|
|
replyto: "".to_string(),
|
|
}),
|
|
&nats,
|
|
false,
|
|
).await;
|
|
if let Err(e) = response {
|
|
error!("nats error: {:?}", e);
|
|
None
|
|
} else {
|
|
let response = response.unwrap();
|
|
match response {
|
|
ServiceResponse::SearchService(_) => {
|
|
error!("sent search service response when asking for auth service!! investigate ASAP!!!");
|
|
None
|
|
}
|
|
ServiceResponse::BingService(_) => {
|
|
error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
|
|
None
|
|
}
|
|
ServiceResponse::AuthService(r) => match r {
|
|
AuthServiceResponse::LatestAnnouncementResponse(v) => {
|
|
match v {
|
|
LatestAnnouncementResponse::Some(v) => {
|
|
Some(FrontpageAnnouncement {
|
|
url: format!("/announcements/{}", v.slug),
|
|
title: v.title,
|
|
content: v.short_text,
|
|
date: chrono::DateTime::from_timestamp(v.date, 0).unwrap().to_rfc2822(),
|
|
})
|
|
}
|
|
LatestAnnouncementResponse::None => {
|
|
None
|
|
}
|
|
LatestAnnouncementResponse::InternalServerError(e) => {
|
|
warn!("internal server error during latest announcement request! {e}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
x => {
|
|
error!("auth service gave {} to our user info request!", x);
|
|
None
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn frontpage_error(error: &str, auth_url: String) -> FrontpageTemplate {
|
|
FrontpageTemplate {
|
|
authurl: auth_url,
|
|
error: Some(error.to_string()),
|
|
version: VERSION.to_string(),
|
|
git_commit: GIT_COMMIT.to_string(),
|
|
built_on: BUILT_ON.to_string(),
|
|
year: YEAR.to_string(),
|
|
alpha: ALPHA,
|
|
count: WEBSITE_COUNT.load(Ordering::Relaxed),
|
|
theme: Themes::Default,
|
|
announcement: None,
|
|
}
|
|
}
|
|
|
|
pub async fn logout(
|
|
jar: CookieJar,
|
|
Extension(nats): Extension<Arc<jetstream::Context>>,
|
|
Extension(opts): Extension<Opts>,
|
|
) -> impl IntoResponse {
|
|
if let Some(token) = jar.get("token") {
|
|
let token = token.value().to_string();
|
|
let response = comms::query_service(
|
|
comms::Query::AuthService(AuthServiceQuery {
|
|
request: AuthServiceRequest::LogoutRequest(LogoutRequest {
|
|
token,
|
|
}),
|
|
replyto: "".to_string(),
|
|
}),
|
|
&nats,
|
|
false,
|
|
).await;
|
|
if let Err(e) = response {
|
|
error!("nats error: {:?}", e);
|
|
frontpage_error("internal server error", opts.auth_url.clone()).into_response()
|
|
} else {
|
|
let response = response.unwrap();
|
|
let mut internal_server_error = false;
|
|
match &response {
|
|
ServiceResponse::SearchService(_) => {
|
|
error!("sent search service response when asking for auth service!! investigate ASAP!!!");
|
|
internal_server_error = true;
|
|
}
|
|
ServiceResponse::BingService(_) => {
|
|
error!("sent bing service response when asking for auth service!! investigate ASAP!!!");
|
|
internal_server_error = true;
|
|
}
|
|
ServiceResponse::AuthService(r) => match r {
|
|
AuthServiceResponse::LogoutResponse(_) => {}
|
|
x => {
|
|
error!("auth service gave {} to our user info request!", x);
|
|
internal_server_error = true;
|
|
}
|
|
}
|
|
}
|
|
if internal_server_error {
|
|
frontpage_error("internal server error", opts.auth_url.clone()).into_response()
|
|
} else {
|
|
match response {
|
|
ServiceResponse::AuthService(AuthServiceResponse::LogoutResponse(r)) => match r {
|
|
LogoutResponse::Success => {
|
|
(jar.remove("token"),
|
|
Redirect::to("/").into_response()).into_response()
|
|
}
|
|
LogoutResponse::InternalServerError(e) => {
|
|
error!("internal server error {e} returned from auth service during logout! investigate ASAP!!!");
|
|
frontpage_error("internal server error", opts.auth_url.clone()).into_response()
|
|
}
|
|
LogoutResponse::BadTokenError => {
|
|
(jar.remove("token"),
|
|
Redirect::to("/").into_response()).into_response()
|
|
}
|
|
}
|
|
_ => unreachable!()
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Redirect::to("/").into_response()
|
|
}
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "frontpage.html")]
|
|
pub struct FrontpageTemplate {
|
|
authurl: String,
|
|
error: Option<String>,
|
|
version: String,
|
|
git_commit: String,
|
|
built_on: String,
|
|
year: String,
|
|
alpha: bool,
|
|
count: u64,
|
|
theme: Themes,
|
|
announcement: Option<FrontpageAnnouncement>,
|
|
}
|
|
|
|
pub async fn frontpage(
|
|
Extension(nats): Extension<Arc<jetstream::Context>>,
|
|
Extension(opts): Extension<Opts>) -> impl IntoResponse {
|
|
let announcement = latest_announcement(nats.clone()).await;
|
|
FrontpageTemplate {
|
|
authurl: opts.auth_url.clone(),
|
|
error: None,
|
|
version: VERSION.to_string(),
|
|
git_commit: GIT_COMMIT.to_string(),
|
|
built_on: BUILT_ON.to_string(),
|
|
year: YEAR.to_string(),
|
|
alpha: ALPHA,
|
|
count: WEBSITE_COUNT.load(Ordering::Relaxed),
|
|
theme: Themes::Default,
|
|
announcement,
|
|
}
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "home.html")]
|
|
struct IndexTemplate {
|
|
info: UserInfo,
|
|
version: String,
|
|
git_commit: String,
|
|
built_on: String,
|
|
year: String,
|
|
alpha: bool,
|
|
count: u64,
|
|
theme: Themes,
|
|
announcement: Option<FrontpageAnnouncement>,
|
|
}
|
|
|
|
pub async fn index(
|
|
jar: CookieJar,
|
|
Extension(nats): Extension<Arc<jetstream::Context>>,
|
|
Extension(opts): Extension<Opts>,
|
|
) -> impl IntoResponse {
|
|
if let Some(token) = jar.get("token") {
|
|
let token = token.value().to_string();
|
|
let info = match authenticate_user(nats.clone(), token).await {
|
|
Ok(i) => i,
|
|
Err(e) => {
|
|
return (jar.remove("token"), frontpage_error(e.as_str(), opts.auth_url.clone())).into_response();
|
|
}
|
|
};
|
|
let theme = info.get_theme();
|
|
|
|
let announcement = latest_announcement(nats.clone()).await;
|
|
|
|
IndexTemplate {
|
|
info,
|
|
version: VERSION.to_string(),
|
|
git_commit: GIT_COMMIT.to_string(),
|
|
built_on: BUILT_ON.to_string(),
|
|
year: YEAR.to_string(),
|
|
alpha: ALPHA,
|
|
count: WEBSITE_COUNT.load(Ordering::Relaxed),
|
|
theme,
|
|
announcement,
|
|
}.into_response()
|
|
} else {
|
|
let announcement = latest_announcement(nats.clone()).await;
|
|
FrontpageTemplate {
|
|
authurl: opts.auth_url.clone(),
|
|
error: None,
|
|
version: VERSION.to_string(),
|
|
git_commit: GIT_COMMIT.to_string(),
|
|
built_on: BUILT_ON.to_string(),
|
|
year: YEAR.to_string(),
|
|
alpha: ALPHA,
|
|
count: WEBSITE_COUNT.load(Ordering::Relaxed),
|
|
theme: Themes::Default,
|
|
announcement,
|
|
}.into_response()
|
|
}
|
|
}
|