mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Rename things to ush for brevity. Add Tree system.
This commit is contained in:
Generated
+1937
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "ush-server"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
include.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["log_debug"]
|
||||
log = ["unshell/log"]
|
||||
log_debug = ["unshell/log_debug"]
|
||||
|
||||
[dependencies]
|
||||
unshell = { path = "../" }
|
||||
# ush-manager = { path = "../unshell-manager" }
|
||||
|
||||
chrono = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
static_init = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
sled = "0.34.7"
|
||||
|
||||
clap = {version = "4.5.53", features = ["derive"]}
|
||||
axum = "0.8.7"
|
||||
axum-extra = {version="0.12.2", features = ["typed-header"]}
|
||||
tokio = {version="1.48.0", features = ["full"] }
|
||||
|
||||
jsonwebtoken = {version = "10.2.0", features = ["aws_lc_rs"]}
|
||||
bcrypt = "0.17.1"
|
||||
@@ -0,0 +1,3 @@
|
||||
GET /api/interface/*/some_config -> Returns the interface and struct at that location
|
||||
|
||||
GET /api/interface/*/some_folder -> Returns the list of sub-folders and interfaces at that location
|
||||
@@ -0,0 +1,105 @@
|
||||
use axum::{
|
||||
Extension, Json, Router,
|
||||
extract::{Path, State},
|
||||
middleware,
|
||||
routing::{get, post},
|
||||
};
|
||||
use tokio::net::TcpListener;
|
||||
use unshell::{debug, info};
|
||||
|
||||
// axum_extra::
|
||||
|
||||
use crate::{auth, auth::structs::CurrentUser, logger::Logger, server::Server};
|
||||
|
||||
macro_rules! route_get {
|
||||
($router:expr, $path:expr, $func:expr) => {{
|
||||
{
|
||||
$router.route(
|
||||
$path,
|
||||
get($func).layer(middleware::from_fn(auth::authorize)),
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! route_post {
|
||||
($router:expr, $path:expr, $func:expr) => {{
|
||||
{
|
||||
$router.route(
|
||||
$path,
|
||||
post($func).layer(middleware::from_fn(auth::authorize)),
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub async fn start_api(address: &str, server: Server) {
|
||||
let listener = TcpListener::bind(address)
|
||||
.await
|
||||
.expect("Unable to start listener");
|
||||
|
||||
info!("Listening on {}", listener.local_addr().unwrap());
|
||||
|
||||
let mut router = Router::new().route("/api/auth", post(auth::sign_in));
|
||||
|
||||
router = route_trees(router);
|
||||
|
||||
router = route_get!(router, "/api/log/{*offset}", Logger::poll_logs_api);
|
||||
router = route_get!(router, "/api/trees", Server::get_trees_api);
|
||||
router = route_get!(router, "/api/keys/{*path}", Server::all_tree_keys_api);
|
||||
router = route_get!(router, "/api/values/{*path}", Server::all_tree_values_api);
|
||||
|
||||
router = route_get!(router, "/api/interface/", Server::get_tree2_root);
|
||||
router = route_get!(router, "/api/interface/{*path}", Server::get_tree2);
|
||||
|
||||
// router = router.route("/api/interface", get(Server::get_tree2_root));
|
||||
|
||||
// router = router.route("/api/interface/{*path}", post(Server::post_tree2));
|
||||
|
||||
router = route_post!(router, "/api/interface/{*path}", Server::post_tree2);
|
||||
|
||||
// router = route_get_log(router);
|
||||
|
||||
axum::serve(listener, router.with_state(server))
|
||||
.await
|
||||
.expect("Error serving application");
|
||||
}
|
||||
|
||||
// Loop through all trees and add /api/<tree>/<path> POST aand GET listeners for them
|
||||
fn route_trees(mut router: Router<Server>) -> Router<Server> {
|
||||
for tree in crate::DATABASE_TREES.iter() {
|
||||
router = router
|
||||
// Route GET requests to this tree
|
||||
.route(
|
||||
&format!("/api/{}/{{*path}}", tree),
|
||||
get(
|
||||
async |State(server): State<Server>,
|
||||
Path(path): Path<String>,
|
||||
Extension(_): Extension<CurrentUser>| {
|
||||
let result = server.get_value(tree, &path);
|
||||
debug!("GET /api/{}/{}", tree.to_string(), path);
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
},
|
||||
)
|
||||
.layer(middleware::from_fn(auth::authorize)),
|
||||
)
|
||||
// Route POST requests to this tree
|
||||
.route(
|
||||
&format!("/api/{}/{{*path}}", tree),
|
||||
post(
|
||||
async |State(server): State<Server>,
|
||||
Path(path): Path<String>,
|
||||
Extension(_): Extension<CurrentUser>,
|
||||
body: String| {
|
||||
let result = server.put_value(tree, &path, &body);
|
||||
debug!("POST /api/{}/{}", tree.to_string(), path);
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
},
|
||||
)
|
||||
.layer(middleware::from_fn(auth::authorize)),
|
||||
);
|
||||
}
|
||||
router
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
pub mod structs;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Json, Request},
|
||||
http::{self, Response, StatusCode},
|
||||
middleware::Next,
|
||||
};
|
||||
use bcrypt::{DEFAULT_COST, hash, verify};
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{Header, TokenData, Validation, decode, encode};
|
||||
use serde_json::{Value, json};
|
||||
use unshell::{debug, info};
|
||||
|
||||
use crate::{EXPIRE_DURATION, JWT_DECODING_KEY, JWT_ENCODING_KEY};
|
||||
|
||||
use structs::{AuthError, Cliams, CurrentUser, SignInData};
|
||||
|
||||
pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
|
||||
let hash = hash(password, DEFAULT_COST)?;
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn encode_jwt(email: String) -> Result<(String, usize), StatusCode> {
|
||||
let now = Utc::now();
|
||||
let exp = (now + EXPIRE_DURATION).timestamp() as usize;
|
||||
let iat = now.timestamp() as usize;
|
||||
|
||||
let claim = Cliams { iat, exp, email };
|
||||
|
||||
let token = encode(&Header::default(), &claim, &JWT_ENCODING_KEY)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok((token, exp))
|
||||
}
|
||||
|
||||
pub fn decode_jwt(jwt: String) -> Result<TokenData<Cliams>, StatusCode> {
|
||||
decode(&jwt, &JWT_DECODING_KEY, &Validation::default())
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub async fn authorize(mut req: Request, next: Next) -> Result<Response<Body>, AuthError> {
|
||||
let auth_header = req.headers_mut().get(http::header::AUTHORIZATION);
|
||||
|
||||
let auth_header = match auth_header {
|
||||
Some(header) => header.to_str().map_err(|_| AuthError {
|
||||
message: "Empty header is not allowed".to_string(),
|
||||
status_code: StatusCode::FORBIDDEN,
|
||||
})?,
|
||||
None => {
|
||||
return Err(AuthError {
|
||||
message: "Please add the JWT token to the header".to_string(),
|
||||
status_code: StatusCode::FORBIDDEN,
|
||||
});
|
||||
}
|
||||
};
|
||||
let mut header = auth_header.split_whitespace();
|
||||
|
||||
let (_, token) = (header.next(), header.next());
|
||||
|
||||
let _token_data: TokenData<Cliams> = match decode_jwt(token.unwrap().to_string()) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return Err(AuthError {
|
||||
message: "Invalid Session".to_string(),
|
||||
status_code: StatusCode::UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// // Fetch the user details from the database
|
||||
// let current_user = match retrieve_user_by_email(&token_data.claims.email) {
|
||||
// Some(user) => user,
|
||||
// None => {
|
||||
// return Err(AuthError {
|
||||
// message: "Unauthorized".to_string(),
|
||||
// status_code: StatusCode::UNAUTHORIZED,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// req.extensions_mut().insert(current_user);
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
pub async fn sign_in(Json(user_data): Json<SignInData>) -> Result<Json<Value>, StatusCode> {
|
||||
// 1. Retrieve user from the database
|
||||
let user = match retrieve_user_by_email(&user_data.username) {
|
||||
Some(user) => user,
|
||||
None => {
|
||||
debug!(
|
||||
"Denied user {}: Could not find user data",
|
||||
user_data.username
|
||||
);
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
} // User not found
|
||||
};
|
||||
|
||||
// 2. Compare the password
|
||||
if !verify(&user_data.password, &user.password_hash)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
// Handle bcrypt errors
|
||||
{
|
||||
debug!("Denied user {}: Incorrect password hash", user.username);
|
||||
return Err(StatusCode::UNAUTHORIZED); // Wrong password
|
||||
}
|
||||
|
||||
info!(
|
||||
"Authenticated user {} for {}",
|
||||
user_data.username, EXPIRE_DURATION
|
||||
);
|
||||
|
||||
// 3. Generate JWT
|
||||
let (token, experation) =
|
||||
encode_jwt(user.username).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 4. Return the token
|
||||
Ok(Json(json!({
|
||||
"token": token,
|
||||
"expiration": experation,
|
||||
})))
|
||||
}
|
||||
|
||||
fn retrieve_user_by_email(_email: &str) -> Option<CurrentUser> {
|
||||
let current_user: CurrentUser = CurrentUser {
|
||||
username: "foo".to_string(),
|
||||
password_hash: hash_password("bar").unwrap(),
|
||||
};
|
||||
Some(current_user)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use axum::{
|
||||
Json,
|
||||
body::Body,
|
||||
http::{Response, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SignInData {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CurrentUser {
|
||||
pub username: String,
|
||||
pub password_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Cliams {
|
||||
// pub exp: u128,
|
||||
// pub iat: u128,
|
||||
//
|
||||
pub exp: usize,
|
||||
pub iat: usize,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
pub struct AuthError {
|
||||
pub message: String,
|
||||
pub status_code: StatusCode,
|
||||
}
|
||||
|
||||
impl IntoResponse for AuthError {
|
||||
fn into_response(self) -> Response<Body> {
|
||||
let body = Json(json!({
|
||||
"error": self.message,
|
||||
}));
|
||||
|
||||
(self.status_code, body).into_response()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
mod api;
|
||||
mod auth;
|
||||
// mod config;
|
||||
pub mod logger;
|
||||
mod server;
|
||||
|
||||
// use math
|
||||
|
||||
pub use server::Server;
|
||||
|
||||
use static_init::dynamic;
|
||||
|
||||
#[static_init::dynamic]
|
||||
pub static DATABASE_TREES: Vec<&'static str> = vec!["users"];
|
||||
|
||||
#[static_init::dynamic]
|
||||
pub static DEFAULT_HOST: String = "localhost".to_string();
|
||||
#[static_init::dynamic]
|
||||
pub static DATABASE_NAME: String = "database".to_string();
|
||||
|
||||
#[static_init::dynamic]
|
||||
pub static SERVER_CONFIG: unshell_manager::PayloadConfig = unshell_manager::PayloadConfig {
|
||||
id: "Server",
|
||||
components: Vec::new(),
|
||||
runtime_config: Vec::new(),
|
||||
};
|
||||
|
||||
// Constants for server config
|
||||
pub use api::start_api;
|
||||
use chrono::Duration;
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||
|
||||
static EXPIRE_DURATION: Duration = Duration::hours(12);
|
||||
|
||||
#[dynamic]
|
||||
|
||||
static JWT_SECRET: String = {
|
||||
if let Ok(env_secret) = std::env::var("JWT_SECRET") {
|
||||
env_secret
|
||||
} else {
|
||||
println!(
|
||||
r#"
|
||||
##############
|
||||
# WARNING: You are using the default JWT secret, used for creating user sessions
|
||||
# With this default key, anyone can login as any user.
|
||||
##############"#
|
||||
);
|
||||
"DEFAULT_SECRET".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
// std::env::var("JWT_SECRET").unwrap_or(|| -> String {
|
||||
// return "TEST".to_string();
|
||||
// }());
|
||||
|
||||
#[dynamic]
|
||||
static JWT_ENCODING_KEY: EncodingKey = EncodingKey::from_secret(JWT_SECRET.as_bytes());
|
||||
#[dynamic]
|
||||
static JWT_DECODING_KEY: DecodingKey = DecodingKey::from_secret(JWT_SECRET.as_bytes());
|
||||
@@ -0,0 +1,100 @@
|
||||
use axum::extract::{Path, State};
|
||||
use axum::{Extension, Json};
|
||||
use chrono::Local;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::path::PathBuf;
|
||||
use unshell::debug;
|
||||
|
||||
use crate::Server;
|
||||
use crate::auth::structs::CurrentUser;
|
||||
|
||||
const LOG_DIR: &str = "logs";
|
||||
|
||||
const LOG_COUNT: usize = 100;
|
||||
|
||||
/// The full path to the log file.
|
||||
/// Initialized once based on the startup time.
|
||||
#[static_init::dynamic]
|
||||
static LOG_FILE_PATH: PathBuf = {
|
||||
let log_dir_path = PathBuf::from(LOG_DIR);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(&log_dir_path) {
|
||||
eprintln!("Error creating log directory {:?}: {}", log_dir_path, e);
|
||||
|
||||
panic!("Failed to initialize log directory.");
|
||||
}
|
||||
|
||||
let now = Local::now();
|
||||
let filename = format!("{}.log", now.format("%Y%m%d_%H%M%S"));
|
||||
|
||||
log_dir_path.join(filename)
|
||||
};
|
||||
|
||||
/// A static utility module for logging operations.
|
||||
pub struct Logger;
|
||||
|
||||
impl Logger {
|
||||
pub fn log(message: String) {
|
||||
let log_line = format!("{}\n", message);
|
||||
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&*LOG_FILE_PATH)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
// 3. Write the log line to the file
|
||||
if let Err(e) = file.write_all(log_line.as_bytes()) {
|
||||
eprintln!("Error writing log to file {:?}: {}", *LOG_FILE_PATH, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error opening log file {:?}: {}", *LOG_FILE_PATH, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_logs(offset: usize) -> Vec<String> {
|
||||
match File::open(&*LOG_FILE_PATH) {
|
||||
Ok(file) => {
|
||||
let reader = BufReader::new(file);
|
||||
let lines: Vec<String> = reader
|
||||
.lines()
|
||||
.filter_map(|line| line.ok()) // Ignore lines that fail to read
|
||||
.collect();
|
||||
|
||||
let total_lines = lines.len();
|
||||
if offset >= total_lines {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let start_index = total_lines
|
||||
.checked_sub(offset)
|
||||
.and_then(|i| i.checked_sub(LOG_COUNT))
|
||||
.unwrap_or(0);
|
||||
|
||||
let end_index = total_lines.checked_sub(offset).unwrap_or(total_lines);
|
||||
|
||||
let slice = &lines[start_index..end_index];
|
||||
|
||||
slice.iter().cloned().collect()
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading log file {:?}: {}", *LOG_FILE_PATH, e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn poll_logs_api(
|
||||
State(_): State<Server>,
|
||||
Extension(_): Extension<CurrentUser>,
|
||||
Path(offset): Path<usize>,
|
||||
) -> axum::Json<serde_json::Value> {
|
||||
debug!("GET /api/log/{}", offset);
|
||||
let result = Self::poll_logs(offset);
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::{error::Error, path::PathBuf};
|
||||
|
||||
use unshell_server::{Server, start_api};
|
||||
|
||||
use clap::Parser;
|
||||
use unshell_server::{DATABASE_NAME, DEFAULT_HOST};
|
||||
|
||||
/// A fictional versioning CLI
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "unshell-server")]
|
||||
#[command(about = "UnShell server", long_about = None)]
|
||||
pub struct Args {
|
||||
/// Host to listen on
|
||||
#[clap(long, default_value_t = DEFAULT_HOST.clone())]
|
||||
host: String,
|
||||
|
||||
/// Port to listen
|
||||
#[arg(short, long, default_value_t = 3000)]
|
||||
port: usize,
|
||||
|
||||
/// Name of database folder
|
||||
#[clap(short, long, default_value_t = DATABASE_NAME.clone())]
|
||||
database_name: String,
|
||||
|
||||
/// Load config from path
|
||||
#[clap(short, long, value_parser)]
|
||||
pub config: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
unshell::logger::PrettyLogger::init_output(|message| {
|
||||
if let Ok(json) = serde_json::to_string(message) {
|
||||
unshell_server::logger::Logger::log(json);
|
||||
}
|
||||
});
|
||||
|
||||
let database = Server::new(args.config, args.database_name)?;
|
||||
|
||||
start_api(&format!("{}:{}", args.host, args.port), database).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::Server;
|
||||
|
||||
impl Server {
|
||||
pub fn get_blobs(&self) -> Result<Vec<Value>, String> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::{
|
||||
Extension, Json,
|
||||
extract::{Path, State},
|
||||
};
|
||||
use serde_json::Value;
|
||||
use sled::Tree;
|
||||
use unshell::{debug, error};
|
||||
|
||||
use crate::{auth::structs::CurrentUser, server::Server};
|
||||
|
||||
impl Server {
|
||||
fn get_tree(&self, tree_name: &str) -> Result<Tree, String> {
|
||||
self.db.open_tree(tree_name).map_err(|e| {
|
||||
error!("DB Failed to open tree: {}", e);
|
||||
"Internal server error".to_string()
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_trees_api(State(server): State<Server>) -> Json<Value> {
|
||||
debug!("GET tree list");
|
||||
|
||||
let result = server
|
||||
.db
|
||||
.tree_names()
|
||||
.iter()
|
||||
.map(|n| String::from_utf8_lossy(&n.to_vec()).to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
}
|
||||
|
||||
pub fn put_value(&self, tree_name: &str, key: &str, value: &str) -> Result<(), String> {
|
||||
match self.get_tree(tree_name)?.insert(key, value) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("Failed to load '{}' from database: {}", key, e);
|
||||
Err("Internal server error".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_value(&self, tree_name: &str, key: &str) -> Result<String, String> {
|
||||
match self.get_tree(tree_name)?.get(key) {
|
||||
Ok(v) => match v {
|
||||
Some(v) => Ok(String::from_utf8_lossy(&v.to_vec()).to_string()),
|
||||
None => Err(format!("Could not find key '{}'", key)),
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to load '{}' from database: {}", key, e);
|
||||
Err("Internal server error".to_string())
|
||||
// Err(e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_keys(&self, tree_name: &str) -> Result<Vec<String>, String> {
|
||||
Ok(self
|
||||
.get_tree(tree_name)?
|
||||
.iter()
|
||||
.keys()
|
||||
.map(|key| {
|
||||
String::from_utf8_lossy(&key.expect("This key should exist").to_vec()).to_string()
|
||||
})
|
||||
.collect::<Vec<String>>())
|
||||
}
|
||||
|
||||
// Route the "keys" api for each tree
|
||||
pub async fn all_tree_keys_api(
|
||||
State(server): State<Server>,
|
||||
Path(tree_name): Path<String>,
|
||||
Extension(_): Extension<CurrentUser>,
|
||||
) -> Json<Value> {
|
||||
let result = server.get_keys(&tree_name);
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
}
|
||||
|
||||
// Route the "values" api to get all the values for each tree
|
||||
pub async fn all_tree_values_api(
|
||||
State(server): State<Server>,
|
||||
Path(tree_name): Path<String>,
|
||||
Extension(_): Extension<CurrentUser>,
|
||||
) -> Json<Value> {
|
||||
let result = || -> Result<HashMap<String, String>, String> {
|
||||
Ok(server
|
||||
.get_keys(&tree_name)?
|
||||
.iter()
|
||||
.map(|key| -> Result<(String, String), String> {
|
||||
Ok((key.clone(), server.get_value(&tree_name, &key)?))
|
||||
})
|
||||
.collect::<Result<Vec<(String, String)>, String>>()?
|
||||
.into_iter()
|
||||
.collect::<HashMap<String, String>>())
|
||||
}();
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use unshell::{
|
||||
ModuleError, Result,
|
||||
config::{ConfigStructField, Tree, TreeMessage, config_struct::Config},
|
||||
};
|
||||
use unshell_manager::Manager;
|
||||
|
||||
mod blobs;
|
||||
mod database;
|
||||
mod tree2;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Server {
|
||||
// pub component_configs: Vec<crate::config::ComponentState>,
|
||||
// pub interface: InterfaceWrapper,
|
||||
pub manager: Arc<Mutex<Manager>>,
|
||||
pub db: sled::Db,
|
||||
// pub tree: Tree2,
|
||||
test_thing: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(_config_paths: Vec<PathBuf>, database: String) -> Result<Self> {
|
||||
// let mut component_configs: Vec<crate::config::ComponentState> = Vec::new(1);
|
||||
|
||||
// for config in &config_paths {
|
||||
// component_configs.extend(crate::config::load_config(config)?);
|
||||
// }
|
||||
|
||||
Ok(Self {
|
||||
// component_configs,
|
||||
manager: Manager::start(&crate::SERVER_CONFIG, Vec::new()),
|
||||
db: sled::open(database).map_err(|e| ModuleError::DatabaseError(e.to_string()))?,
|
||||
|
||||
test_thing: Arc::new(Mutex::new(Config::new(vec![
|
||||
ConfigStructField::Header("Test Heading".into()),
|
||||
ConfigStructField::Text("Test Texttttttttttttttt".into()),
|
||||
ConfigStructField::String {
|
||||
default: "Test Texttttttttttttttt".into(),
|
||||
max_length: None,
|
||||
protected: true,
|
||||
},
|
||||
ConfigStructField::String {
|
||||
default: "Test ".into(),
|
||||
max_length: Some(15),
|
||||
protected: false,
|
||||
},
|
||||
]))),
|
||||
// tree: Tree2::default(),
|
||||
// interface: get_test_interface(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Tree for Server {
|
||||
fn is_folder() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn get_children_string(&self) -> Vec<String> {
|
||||
vec!["connection_count".into()]
|
||||
}
|
||||
|
||||
fn select_child(&mut self, child: &str, message: TreeMessage) -> Result<TreeMessage> {
|
||||
match child {
|
||||
"connection_count" => self.test_thing.lock().unwrap().get(message),
|
||||
_ => Err("No such child".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.db.flush().expect("Failed to flush database on drop");
|
||||
// Manager::join(self.manager.clone());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
};
|
||||
|
||||
use serde_json::Value;
|
||||
use unshell::{
|
||||
ModuleError,
|
||||
config::{Tree, TreeMessage},
|
||||
debug,
|
||||
};
|
||||
|
||||
use crate::Server;
|
||||
|
||||
impl Server {
|
||||
pub async fn get_tree2_root(
|
||||
State(server): State<Server>,
|
||||
// Extension(extension): Extension<CurrentUser>,
|
||||
) -> Json<Value> {
|
||||
Self::get_tree2(State(server), Path("".into())).await
|
||||
}
|
||||
|
||||
pub async fn get_tree2(
|
||||
State(mut server): State<Server>,
|
||||
Path(path): Path<String>,
|
||||
// Extension(_): Extension<CurrentUser>,
|
||||
) -> Json<Value> {
|
||||
debug!("GET /api/interface/{}", path);
|
||||
|
||||
let result = server
|
||||
.get(&path, TreeMessage::RequestStructAndValue)
|
||||
.map_err(|e| ModuleError::CryptError(e.to_string()));
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
}
|
||||
|
||||
pub async fn post_tree2(
|
||||
State(mut server): State<Server>,
|
||||
Path(path): Path<String>,
|
||||
// Extension(_): Extension<CurrentUser>,
|
||||
Json(tree_message): Json<TreeMessage>,
|
||||
) -> Json<Value> {
|
||||
debug!("POST /api/interface/");
|
||||
|
||||
// Json(Value::Null)
|
||||
|
||||
let result = server
|
||||
.get(&path, tree_message)
|
||||
.map_err(|e| ModuleError::CryptError(e.to_string()));
|
||||
|
||||
Json(serde_json::to_value(result).unwrap())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user