use axum::{ http::HeaderMap, http::StatusCode, routing::get, routing::post, routing::put, Json, Router, }; use serde::Deserialize; use redis::Commands; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Deserialize)] pub struct User { username: String, password: String, } #[derive(Deserialize)] pub struct Progress { document: String, progress: String, percentage: String, device: String, device_id: String, } #[tokio::main] async fn main() { // build our application with a single route let app = Router::new() .route("/", get(root)) .route("/users/create", post(create_user)) .route("/users/auth", get(auth_user)) .route("/syncs/progress", put(update_progress)) .route("/syncs/progress/:document", get(get_progress)) .route("/healthcheck", get(healthcheck)); // run it with hyper on localhost:3000 axum::Server::bind(&"0.0.0.0:3003".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); } async fn root() -> &'static str { "KOreader sync server" } async fn create_user(Json(payload): Json) -> (StatusCode, String) { let client = redis::Client::open("redis://127.0.0.1/").unwrap(); let mut con = client.get_connection().unwrap(); let username = payload.username; let password = payload.password; let user_key = format!("user:{username}:key"); let does_exist: bool = con.exists(&user_key).unwrap(); if does_exist == false { let _: () = con.set(&user_key, password).unwrap(); } else { return ( StatusCode::PAYMENT_REQUIRED, "Username is already registered.".to_owned(), ); } (StatusCode::CREATED, format!("username = {username}")) } fn authorize(username: &str, password: &str) -> bool { let client = redis::Client::open("redis://127.0.0.1/").unwrap(); let mut con = client.get_connection().unwrap(); if username.is_empty() || password.is_empty() { return false; } let user_key = format!("user:{username}:key"); let redis_pw: String = con.get(&user_key).unwrap(); if password != redis_pw { return false; } true } async fn auth_user(headers: HeaderMap) -> StatusCode { let username = headers["x-auth-user"].to_str().unwrap_or(""); let password = headers["x-auth-key"].to_str().unwrap_or(""); if authorize(&username, &password) == false { return StatusCode::UNAUTHORIZED; } StatusCode::OK } async fn update_progress(headers: HeaderMap, Json(payload): Json) -> StatusCode { let username = headers["x-auth-user"].to_str().unwrap_or(""); let password = headers["x-auth-key"].to_str().unwrap_or(""); if authorize(username, password) == false { return StatusCode::UNAUTHORIZED; } let client = redis::Client::open("redis://127.0.0.1/").unwrap(); let mut con = client.get_connection().unwrap(); let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); let document = payload.document; let doc_key = format!("user:{username}:document:{document}"); let _: () = con .hset_multiple( &doc_key, &[ ("percentage", &payload.percentage), ("progress", &payload.progress), ("device", &payload.device), ("device_id", &payload.device_id), ("timestamp", ×tamp.to_string()), ], ) .unwrap(); StatusCode::OK } async fn get_progress() {} async fn healthcheck() -> (StatusCode, String) { (StatusCode::OK, "{ \"state\" : \"OK\" }".to_owned()) }