2025-03-12 12:32:15 -07:00
/*
* asklyphe - frontend main . rs
* - entrypoint for the asklyphe frontend
*
* 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/>.
* /
pub mod searchbot ;
pub mod wikipedia ;
pub mod unit_converter ;
2025-05-08 15:03:00 +12:00
pub mod bangs ;
2025-03-12 12:32:15 -07:00
pub mod routes ;
use std ::{ env , process } ;
use std ::collections ::HashMap ;
2025-09-04 15:53:53 +12:00
use std ::net ::{ SocketAddr , ToSocketAddrs } ;
2025-03-12 12:32:15 -07:00
use std ::ops ::Deref ;
use std ::sync ::Arc ;
use std ::sync ::atomic ::{ AtomicU64 , Ordering } ;
use std ::time ::Duration ;
use askama ::Template ;
use asklyphe_common ::nats ;
use asklyphe_common ::nats ::comms ;
use asklyphe_common ::nats ::searchservice ::{ SearchSrvcQuery , SearchSrvcRequest , SearchSrvcResponse } ;
use async_nats ::jetstream ;
use axum ::{ Extension , Router } ;
use axum ::extract ::Query ;
use axum ::http ::{ HeaderValue , Method } ;
use axum ::response ::IntoResponse ;
use axum ::routing ::{ get , post } ;
use serde ::Serialize ;
use tokio ::sync ::Mutex ;
use tower_http ::cors ::{ AllowOrigin , CorsLayer } ;
use tracing ::error ;
use tracing_loki ::url ::Url ;
use tracing_subscriber ::layer ::SubscriberExt ;
use tracing_subscriber ::util ::SubscriberInitExt ;
use crate ::routes ::admin ::{ admin_announcement , admin_announcement_post , admin_home , admin_invitecode , admin_invitecode_post , admin_user_list } ;
use crate ::routes ::announcement ::announcement_full ;
use crate ::routes ::index ::{ frontpage , index , logout } ;
use crate ::routes ::search ::{ image_proxy , search , search_json } ;
use crate ::routes ::semaphore ::semaphore ;
use crate ::routes ::user_settings ::{ theme_change_post , user_settings } ;
use crate ::unit_converter ::UnitConversion ;
use crate ::wikipedia ::WikipediaSummary ;
const ALPHA : bool = {
let alpha = option_env! ( " ALPHA " ) ;
alpha . is_some ( )
} ;
const VERSION : & str = env! ( " CARGO_PKG_VERSION " ) ;
const GIT_COMMIT : & str = env! ( " GIT_COMMIT " ) ;
const BUILT_ON : & str = env! ( " BUILT_ON " ) ;
const YEAR : & str = env! ( " YEAR " ) ;
#[ derive(Clone) ]
pub struct Opts {
pub bind_addr : SocketAddr ,
pub nats_addr : SocketAddr ,
nats_cert : String ,
nats_key : String ,
auth_url : String ,
pub loki_addr : Option < Url > ,
pub datacenter_id : i64 ,
pub emergency : bool ,
}
pub static WEBSITE_COUNT : AtomicU64 = AtomicU64 ::new ( 0 ) ;
async fn website_count_update_thread ( nats : Arc < jetstream ::Context > ) {
loop {
searchbot ::update_website_counter ( nats . clone ( ) ) . await ;
tokio ::time ::sleep ( Duration ::from_secs ( 30 ) ) . await ;
}
}
#[ tokio::main ]
async fn main ( ) {
env_logger ::init ( ) ;
let opts = Opts {
bind_addr : env ::var ( " BIND_ADDR " ) . unwrap_or ( " 0.0.0.0:5842 " . to_string ( ) ) . parse ( ) . expect ( " Badly formed BIND_ADDR (Needs to be SocketAddr) " ) ,
2025-09-04 15:53:53 +12:00
nats_addr : env ::var ( " NATS_ADDR " ) . unwrap_or ( " 127.0.0.1:4222 " . to_string ( ) ) . to_socket_addrs ( ) . expect ( " Badly formed NATS_ADDR (Needs to be SocketAddr) " ) . nth ( 0 ) . unwrap ( ) ,
2025-03-12 12:32:15 -07:00
nats_cert : env ::var ( " NATS_CERT " ) . expect ( " NATS_CERT needs to be set " ) ,
nats_key : env ::var ( " NATS_KEY " ) . expect ( " NATS_KEY needs to be set " ) ,
auth_url : env ::var ( " AUTH_URL " ) . unwrap_or ( " https://auth.asklyphe.com " . to_string ( ) ) ,
loki_addr : match env ::var ( " LOKI_ADDR " ) {
Ok ( url ) = > {
Some ( Url ::parse ( & url ) . expect ( " Badly formed LOKI_ADDR (Needs to be Url) " ) )
}
Err ( _ ) = > {
None
}
} ,
datacenter_id : env ::var ( " DATACENTER_ID " ) . unwrap_or ( " 1 " . to_string ( ) ) . parse ( ) . expect ( " Badly formed DATACENTER_ID (Need to be i64) " ) ,
emergency : env ::var ( " EMERGENCY " ) . unwrap_or ( " 0 " . to_string ( ) ) . eq ( " 1 " ) ,
} ;
if opts . loki_addr . is_some ( ) {
//let (layer, task) = tracing_loki::builder()
// .label("environment", "dev").unwrap()
// .extra_field("pid", process::id().to_string()).unwrap()
// .extra_field("run_id", snowflake_factory.generate().to_string()).unwrap()
// .build_url(opts.loki_addr.clone().unwrap()).unwrap();
//// Register Loki layer
//tracing_subscriber::registry()
// .with(layer)
// .init();
//// Spawn Loki background task
//tokio::spawn(task);
} else {
//tracing_subscriber::fmt::init()
}
let nats = async_nats ::ConnectOptions ::new ( )
. add_client_certificate ( opts . nats_cert . as_str ( ) . into ( ) , opts . nats_key . as_str ( ) . into ( ) )
. connect ( opts . nats_addr . to_string ( ) )
. await ;
if let Err ( e ) = nats {
eprintln! ( " FATAL ERROR, COULDN'T CONNECT TO NATS: {} " , e ) ;
return ;
}
let nats = nats . unwrap ( ) ;
let nats = jetstream ::new ( nats ) ;
let nats = Arc ::new ( nats ) ;
tokio ::spawn ( website_count_update_thread ( nats . clone ( ) ) ) ;
let app = Router ::new ( )
. route ( " / " , get ( index ) )
. route ( " /semaphore " , get ( semaphore ) )
. route ( " /frontpage " , get ( frontpage ) )
. route ( " /ask " , get ( search ) )
. route ( " /ask_json " , get ( search_json ) )
. route ( " /imgproxy " , get ( image_proxy ) )
. route ( " /logout " , post ( logout ) )
. route ( " /user_settings " , get ( user_settings ) )
. route ( " /user_settings/set_theme " , post ( theme_change_post ) )
. route ( " /admin " , get ( admin_home ) )
. route ( " /admin/invitecode " , get ( admin_invitecode ) . post ( admin_invitecode_post ) )
. route ( " /admin/announcement " , get ( admin_announcement ) . post ( admin_announcement_post ) )
. route ( " /admin/allusers " , get ( admin_user_list ) )
. route ( " /announcements/:slug " , get ( announcement_full ) )
. layer ( CorsLayer ::new ( ) . allow_methods ( [ Method ::GET , Method ::POST ] ) . allow_origin ( AllowOrigin ::exact ( HeaderValue ::from_str ( " localhost " ) . unwrap ( ) ) ) )
. layer ( Extension ( nats . clone ( ) ) )
. layer ( Extension ( opts . clone ( ) ) )
. fallback ( routes ::not_found ) ;
let listener = tokio ::net ::TcpListener ::bind ( opts . bind_addr ) . await . expect ( " Failed to get listener " ) ;
axum ::serve ( listener , app ) . await . expect ( " Failed to serve " ) ;
}