pub mod db; use anyhow::Result; use axum::{ body::Body, response::{IntoResponse, Response}, routing::{get, post}, Json, Router }; use serde::{Deserialize, Serialize}; use crate::ApiResult; use db::{get_database, get_users, LogEntry, Mole, User}; use http::{header::HeaderMap, StatusCode}; use mongodb::bson::{doc, oid::ObjectId, to_document, Document}; use serde_json::json; pub fn router() -> Router { Router::new() .route("/sign-in", get(get_sign_in) .post(post_sign_in) ) .route("/sign-up", post(post_sign_up)) .route("/sign-out", post(post_sign_out)) .route("/backup", post(post_backup) ) .route("/restore", get(get_restore) ) } pub async fn auth(api: &str) -> Result> { let db = get_users().await?; let query = doc! { "$expr": { "$eq": ["$_auth._api", ObjectId::parse_str(api)?] } }; Ok(db.find_one(query, None).await?) } #[derive(Serialize, Deserialize)] struct BackupData { moles: Vec, logs: Vec, } async fn get_sign_in(Json(body): Json) -> ApiResult { let db = get_users().await?; let query = doc! { "$expr": { "$eq": ["$username", body.username] } }; match db.find_one(query, None).await? { Some(user) => { Ok(Response::builder() .status(StatusCode::CREATED) .body(Json(json!({ "_salt": user.auth.unwrap().salt.unwrap() })).into_response().into_body())?) }, None => { Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("User does not exist"))?) } } } async fn post_sign_in(Json(body): Json) -> ApiResult { let db = get_users().await?; let api = ObjectId::new(); let query = doc! { "$expr": { "$and": [ { "$eq": ["$username", body.username]}, { "$eq": ["$_auth._hash", body.auth.unwrap_or_default().hash]} ] } }; let update = doc! { "$set": { "_auth._api": api } }; match db.count_documents(query.clone(), None).await? { count if count != 0 => { db.update_one(query, update, None).await?; Ok(Response::builder() .status(StatusCode::OK) .body(Json(json!({ "_api_key": api.to_string() })).into_response().into_body())?) }, _ => { Ok(Response::builder() .status(StatusCode::UNAUTHORIZED) .body(Body::from("Username or password is incorrect"))?) }, } } async fn post_sign_up(Json(body): Json) -> ApiResult { let db = get_users().await?; let auth = body.clone().auth.unwrap_or_default(); let query = doc! { "$expr": { "$and": [ { "$eq": ["$username", &body.username] }, { "$eq": ["$_auth._hash", &auth.hash]}, { "$eq": ["$_auth._salt", &auth.salt]} ] } }; match db.count_documents(query, None).await? { count if count != 0 => { Ok(Response::builder() .status(StatusCode::NOT_ACCEPTABLE) .body(Body::from("Username is already taken"))?) }, _ => { db.insert_one(body, None).await?; Ok(Response::builder() .status(StatusCode::CREATED) .body(Body::from("Account created successfully"))?) } } } async fn post_sign_out(headers: HeaderMap) -> ApiResult { let db = get_users().await?; let api = headers["api_key"].to_str()?; let query = doc! { "$expr": { "$eq": ["$_auth._api", ObjectId::parse_str(api)?] } }; let update = doc! { "$unset": { "_auth._api": ObjectId::new() } }; match auth(api).await? { Some(_user) => { db.update_one(query, update, None).await?; Ok(Response::builder() .status(StatusCode::OK) .body(Body::from("Sign out successful"))?) }, None => { Ok(Response::builder() .status(StatusCode::NOT_ACCEPTABLE) .body(Body::from("User does not exist"))?) }, } } async fn get_restore() { //TODO: Restore //find all moles that match user_id //find all logs that match mole_ids //Return as Json } async fn post_backup(headers: HeaderMap, Json(user_data): Json) -> ApiResult { let api = headers["api_key"].to_str()?; match auth(api).await? { Some(_user) => { let _ = backup_docs_if_new(user_data.moles, "moles").await; let _ = backup_docs_if_new(user_data.logs, "logs").await; Ok(Response::builder() .status(StatusCode::CREATED) .body(Body::from("Account backup successful"))?) }, None => Ok(Response::builder() .status(StatusCode::UNAUTHORIZED) .body(Body::from("API Key is incorrect"))?), } } async fn backup_docs_if_new(docs: Vec, collection: &'static str) -> Result<()> { let _ = docs.iter() .map(|doc| to_document(doc).unwrap()) .map(|doc| { tokio::spawn(async move { let _ = insert_if_new(doc, collection).await; }) }); Ok(()) } async fn insert_if_new(doc: Document, collection: &str) -> Result<()> { let db = get_database().await?.collection(collection); match db.count_documents(doc.clone(), None).await? { 0 => Some(db.insert_one(doc, None)), _ => None, }; Ok(()) }