kosyncrs/src/main.rs

187 lines
4.9 KiB
Rust

use axum::{
extract::Path, http::HeaderMap, http::StatusCode, routing::get, routing::post, routing::put,
Json, Router,
};
use serde::Deserialize;
use redis::Commands;
use serde::Serialize;
use serde_json::json;
use serde_json::Value;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Deserialize)]
pub struct User {
username: String,
password: String,
}
#[derive(Deserialize)]
pub struct UpdateProgress {
document: String,
progress: String,
percentage: f32,
device: String,
device_id: String,
}
#[derive(Deserialize, Serialize)]
pub struct GetProgress {
document: String,
progress: String,
percentage: f32,
device: String,
device_id: String,
timestamp: u64,
}
#[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<User>) -> (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<UpdateProgress>) -> 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.to_string()),
("progress", &payload.progress),
("device", &payload.device),
("device_id", &payload.device_id),
("timestamp", &timestamp.to_string()),
],
)
.unwrap();
StatusCode::OK
}
async fn get_progress(
headers: HeaderMap,
Path(document): Path<String>,
) -> (StatusCode, Json<Value>) {
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, Json(json!("")));
}
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
let mut con = client.get_connection().unwrap();
let doc_key = format!("user:{username}:document:{document}");
let values: Vec<String> = con
.hget(
doc_key,
&["percentage", "progress", "device", "device_id", "timestamp"],
)
.unwrap();
println!("{values:?}");
let res = GetProgress {
percentage: values[0].parse().unwrap(),
progress: values[1].clone(),
device: values[2].clone(),
device_id: values[3].clone(),
timestamp: values[4].parse().unwrap(),
document,
};
(StatusCode::OK, Json(json!(res)))
}
async fn healthcheck() -> (StatusCode, String) {
(StatusCode::OK, "{ \"state\" : \"OK\" }".to_owned())
}