use std::future::Future; use axum::{ http::{HeaderMap, StatusCode}, Json, }; use futures::{SinkExt, StreamExt}; use result_extended::{error, ok, return_error, ResultExt}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::{ asset_db::AssetId, core::Core, database::{CharacterId, SessionId, UserId}, types::{AppError, FatalError, User}, }; async fn check_session( core: &Core, headers: HeaderMap, ) -> ResultExt<Option<User>, AppError, FatalError> { println!("headers: {:?}", headers); println!("auth_header: {:?}", headers.get("Authorization")); match headers.get("Authorization") { Some(token) => { match token .to_str() .unwrap() .split(" ") .collect::<Vec<&str>>() .as_slice() { [_schema, token] => core.session(&SessionId::from(token.to_owned())).await, _ => error(AppError::BadRequest), } } None => ok(None), } } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct HealthCheck { pub ok: bool, } pub async fn healthcheck(core: Core) -> Vec<u8> { match core.status().await { ResultExt::Ok(s) => serde_json::to_vec(&HealthCheck { ok: s.admin_enabled, }) .unwrap(), ResultExt::Err(_) => serde_json::to_vec(&HealthCheck { ok: false }).unwrap(), ResultExt::Fatal(err) => panic!("{}", err), } } #[derive(Clone, Debug, Deserialize, Serialize)] #[typeshare] pub struct AuthRequest { pub username: String, pub password: String, } pub async fn check_password( core: Core, req: Json<AuthRequest>, ) -> (StatusCode, Json<Option<SessionId>>) { let Json(AuthRequest { username, password }) = req; match core.auth(&username, &password).await { ResultExt::Ok(session_id) => (StatusCode::OK, Json(Some(session_id))), ResultExt::Err(_err) => (StatusCode::UNAUTHORIZED, Json(None)), ResultExt::Fatal(err) => panic!("Fatal: {}", err), } } pub async fn authenticated<F, A>( core: Core, headers: HeaderMap, f: F, ) -> (StatusCode, Json<Option<A>>) where F: FnOnce(User) -> (StatusCode, Json<Option<A>>), { match check_session(&core, headers).await { ResultExt::Ok(Some(user)) => f(user), ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)), ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)), ResultExt::Fatal(err) => panic!("{}", err), } } #[derive(Deserialize, Serialize)] #[typeshare] pub struct UserProfile { pub userid: UserId, pub username: String, } pub async fn get_user(core: Core, headers: HeaderMap) -> (StatusCode, Json<Option<UserProfile>>) { authenticated(core.clone(), headers, |user| { ( StatusCode::OK, Json(Some(UserProfile { userid: UserId::from(user.id), username: user.name, })), ) }) .await } /* pub async fn handle_auth( auth_ctx: &AuthDB, auth_token: AuthToken, ) -> Result<http::Response<String>, Error> { match auth_ctx.authenticate(auth_token).await { Ok(Some(session)) => match serde_json::to_string(&session) { Ok(session_token) => Response::builder() .status(StatusCode::OK) .body(session_token), Err(_) => Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body("".to_owned()), }, Ok(None) => Response::builder() .status(StatusCode::UNAUTHORIZED) .body("".to_owned()), Err(_) => Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body("".to_owned()), } } */ /* pub async fn handler<F>(f: F) -> impl Reply where F: Future<Output = ResultExt<Response<Vec<u8>>, AppError, FatalError>>, { match f.await { ResultExt::Ok(response) => response, ResultExt::Err(AppError::NotFound(_)) => Response::builder() .status(StatusCode::NOT_FOUND) .body(vec![]) .unwrap(), ResultExt::Err(err) => { println!("request error: {:?}", err); Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(vec![]) .unwrap() } ResultExt::Fatal(err) => { panic!("Shutting down with fatal error: {:?}", err); } } } pub async fn handle_server_status(core: Core) -> impl Reply { handler(async move { let status = return_error!(core.status().await); ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Content-Type", "application/json") .body(serde_json::to_vec(&status).unwrap()) .unwrap()) }) .await } pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply { handler(async move { let (mime, bytes) = return_error!(core.get_asset(asset_id).await); ok(Response::builder() .header("content-type", mime.to_string()) .body(bytes) .unwrap()) }) .await } pub async fn handle_available_images(core: Core) -> impl Reply { handler(async move { let image_paths: Vec<String> = core .available_images() .await .into_iter() .map(|path| format!("{}", path.as_str())) .collect(); ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Content-Type", "application/json") .body(serde_json::to_vec(&image_paths).unwrap()) .unwrap()) }) .await } #[derive(Deserialize, Serialize)] pub struct RegisterRequest {} #[derive(Deserialize, Serialize)] pub struct RegisterResponse { url: String, } pub async fn handle_register_client(core: Core, _request: RegisterRequest) -> impl Reply { handler(async move { let client_id = core.register_client().await; ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Content-Type", "application/json") .body( serde_json::to_vec(&RegisterResponse { url: format!("ws://127.0.0.1:8001/ws/{}", client_id), }) .unwrap(), ) .unwrap()) }) .await } pub async fn handle_unregister_client(core: Core, client_id: String) -> impl Reply { handler(async move { core.unregister_client(client_id).await; ok(Response::builder() .status(StatusCode::NO_CONTENT) .body(vec![]) .unwrap()) }) .await } pub async fn handle_connect_websocket( core: Core, ws: warp::ws::Ws, client_id: String, ) -> impl Reply { ws.on_upgrade(move |socket| { println!("upgrading websocket"); let core = core.clone(); async move { let (mut ws_sender, _) = socket.split(); let mut receiver = core.connect_client(client_id.clone()).await; tokio::task::spawn(async move { let tabletop = core.tabletop().await; let _ = ws_sender .send(Message::text( serde_json::to_string(&crate::types::Message::UpdateTabletop(tabletop)) .unwrap(), )) .await; while let Some(msg) = receiver.recv().await { println!("Relaying message: {:?}", msg); let _ = ws_sender .send(Message::text(serde_json::to_string(&msg).unwrap())) .await; } println!("process ended for id {}", client_id); }); } }) } pub async fn handle_set_background_image(core: Core, image_name: String) -> impl Reply { handler(async move { let _ = core.set_background_image(AssetId::from(image_name)).await; ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Methods", "*") .header("Content-Type", "application/json") .body(vec![]) .unwrap()) }) .await } pub async fn handle_get_users(core: Core) -> Response<Vec<u8>> { unimplemented!() /* handler(async move { let users = match core.list_users().await { ResultExt::Ok(users) => users, ResultExt::Err(err) => return ResultExt::Err(err), ResultExt::Fatal(err) => return ResultExt::Fatal(err), }; ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Content-Type", "application/json") .body(serde_json::to_vec(&users).unwrap()) .unwrap()) }) .await */ } pub async fn handle_get_games(core: Core) -> impl Reply { handler(async move { let games = match core.list_games().await { ResultExt::Ok(games) => games, ResultExt::Err(err) => return ResultExt::Err(err), ResultExt::Fatal(err) => return ResultExt::Fatal(err), }; ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Content-Type", "application/json") .body(serde_json::to_vec(&games).unwrap()) .unwrap()) }) .await } pub async fn handle_get_charsheet(core: Core, charid: String) -> impl Reply { handler(async move { let sheet = match core.get_charsheet(CharacterId::from(charid)).await { ResultExt::Ok(sheet) => sheet, ResultExt::Err(err) => return ResultExt::Err(err), ResultExt::Fatal(err) => return ResultExt::Fatal(err), }; match sheet { Some(sheet) => ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Content-Type", "application/json") .body(serde_json::to_vec(&sheet).unwrap()) .unwrap()), None => ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(vec![]) .unwrap()), } }) .await } pub async fn handle_set_admin_password(core: Core, password: String) -> impl Reply { handler(async move { let status = return_error!(core.status().await); if status.admin_enabled { return error(AppError::PermissionDenied); } core.set_password(UserId::from("admin"), password).await; ok(Response::builder() .header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Methods", "*") .header("Content-Type", "application/json") .body(vec![]) .unwrap()) }) .await } #[derive(Clone, Debug, Deserialize, Serialize)] #[typeshare] pub struct AuthRequest { username: String, password: String, } pub async fn handle_check_password(core: Core, auth_request: AuthRequest) -> impl Reply { handler(async move { let session_id = return_error!( core.auth(&auth_request.username, &auth_request.password) .await ); println!("handle_check_password: {:?}", session_id); ok(Response::builder() .header("Content-Type", "application/json") .body(serde_json::to_vec(&session_id).unwrap()) .unwrap()) }) .await } */