Restructure some of the folder structure in unshell-server

This commit is contained in:
Michael Mikovsky
2025-12-20 18:19:08 -07:00
parent c8cfa685ec
commit 338eb93bfc
18 changed files with 98 additions and 198 deletions
@@ -9,11 +9,7 @@ use unshell_lib::{debug, info};
// axum_extra::
use crate::{
api::{auth, structs::CurrentUser},
logger::Logger,
server::Server,
};
use crate::{auth, auth::structs::CurrentUser, logger::Logger, server::Server};
macro_rules! route_get {
($router:expr, $path:expr, $func:expr) => {{
-20
View File
@@ -1,20 +0,0 @@
use chrono::Duration;
use jsonwebtoken::{DecodingKey, EncodingKey};
use static_init::dynamic;
extern crate unshell_lib;
pub mod app;
mod auth;
mod structs;
pub use structs::CurrentUser;
static EXPIRE_DURATION: Duration = Duration::hours(12);
#[dynamic]
static JWT_SECRET: String = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
#[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());
@@ -1,3 +1,5 @@
pub mod structs;
use axum::{
body::Body,
extract::{Json, Request},
@@ -10,10 +12,9 @@ use jsonwebtoken::{Header, TokenData, Validation, decode, encode};
use serde_json::{Value, json};
use unshell_lib::{debug, info};
use crate::api::{
EXPIRE_DURATION, JWT_DECODING_KEY, JWT_ENCODING_KEY,
structs::{AuthError, Cliams, CurrentUser, SignInData},
};
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)?;
-5
View File
@@ -1,8 +1,4 @@
mod blob;
// pub mod interface;
pub use blob::Blob;
// pub use interface::InterfaceWrapper;
use std::{
collections::HashMap,
@@ -27,7 +23,6 @@ struct ComponentMetadata {
// Other components that can be pointed to by this component
#[serde(default)]
child_components: Vec<PathBuf>,
// config: Option<HashMap<String, ConfigStructField>>,
}
#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
+18 -4
View File
@@ -1,15 +1,14 @@
// #![macro_use]
mod api;
mod auth;
mod config;
pub mod logger;
mod modules;
mod server;
pub use api::app::start_api;
pub use server::Server;
use static_init::dynamic;
#[static_init::dynamic]
pub static DATABASE_TREES: Vec<&'static str> = vec!["users"];
@@ -24,3 +23,18 @@ pub static SERVER_CONFIG: unshell_lib::config::PayloadConfig = unshell_lib::conf
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 = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
#[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());
+4 -101
View File
@@ -1,42 +1,33 @@
use axum::extract::{Path, State};
use axum::{Extension, Json};
use chrono::Local;
use unshell_lib::debug;
// use lazy_static::lazy_static;
use std::fs::{self, File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use unshell_lib::debug;
use crate::Server;
use crate::api::CurrentUser;
use crate::auth::structs::CurrentUser;
// --- Constants ---
/// The directory where log files will be stored.
const LOG_DIR: &str = "logs";
/// The maximum number of logs to return in one call to `poll_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 = {
// 1. Determine the log directory path
let log_dir_path = PathBuf::from(LOG_DIR);
// 2. Create the log directory if it does not exist
if let Err(e) = fs::create_dir_all(&log_dir_path) {
eprintln!("Error creating log directory {:?}: {}", log_dir_path, e);
// Panic or handle error as appropriate for your application's needs
// Panicking here to ensure the logger can't be used if the path is invalid/unwritable
panic!("Failed to initialize log directory.");
}
// 3. Generate the unique filename based on the current local time
let now = Local::now();
let filename = format!("{}.log", now.format("%Y%m%d_%H%M%S"));
// 4. Combine the directory path and the filename
log_dir_path.join(filename)
};
@@ -44,19 +35,9 @@ static LOG_FILE_PATH: PathBuf = {
pub struct Logger;
impl Logger {
/// Writes a log entry to the current log file.
///
/// The log entry includes a timestamp and the provided message.
///
/// # Arguments
///
/// * `message` - The string content of the log entry.
pub fn log(message: String) {
// 1. Format the complete log line with timestamp
let log_line = format!("{}\n", message);
// 2. Open the file in append mode, creating it if it doesn't exist.
// The file path is guaranteed to be valid due to the `lazy_static` initialization.
match OpenOptions::new()
.create(true)
.append(true)
@@ -74,65 +55,38 @@ impl Logger {
}
}
/// Reads and returns the most recent logs from the file.
///
/// The total number of logs returned is limited by `LOG_COUNT`.
/// The `offset` determines how far back in history to start reading.
///
/// # Arguments
///
/// * `offset` - The number of most recent logs to skip.
///
/// # Returns
///
/// A `Vec<String>` containing the logs, or an empty `Vec` on failure.
pub fn poll_logs(offset: usize) -> Vec<String> {
// Array of String is not a idiomatic return type in Rust,
// so we return a Vector, which serves the same purpose.
// The size constraint (LOG_COUNT) is applied internally.
match File::open(&*LOG_FILE_PATH) {
Ok(file) => {
let reader = BufReader::new(file);
// Collect all lines into a vector
let lines: Vec<String> = reader
.lines()
.filter_map(|line| line.ok()) // Ignore lines that fail to read
.collect();
// Determine the starting index for the slice (from the end of the vector)
// This logic correctly handles the offset and LOG_COUNT limits.
let total_lines = lines.len();
if offset >= total_lines {
// Offset is past the beginning of the file, return nothing
return Vec::new();
}
// Start from the end, minus the offset, minus the number of logs to read.
// We use checked subtraction to prevent panic if it would result in a negative number.
let start_index = total_lines
.checked_sub(offset)
.and_then(|i| i.checked_sub(LOG_COUNT))
.unwrap_or(0); // If either subtraction fails, start from 0
.unwrap_or(0);
// End index is determined by subtracting the offset from the total length.
let end_index = total_lines.checked_sub(offset).unwrap_or(total_lines);
// Get the slice of the lines
let slice = &lines[start_index..end_index];
slice.iter().cloned().collect()
}
Err(e) => {
// Return an empty vector and print an error message if the file cannot be read
eprintln!("Error reading log file {:?}: {}", *LOG_FILE_PATH, e);
Vec::new()
}
}
}
// Route the "keys" api for each tree
pub async fn poll_logs_api(
State(_): State<Server>,
Extension(_): Extension<CurrentUser>,
@@ -144,54 +98,3 @@ impl Logger {
Json(serde_json::to_value(result).unwrap())
}
}
// --- Example Usage ---
// fn main() {
// // Write some logs
// Logger::log("Application started.".to_string());
// Logger::log("Configuration loaded.".to_string());
// for i in 1..=20 {
// Logger::log(format!("Processing request #{}", i));
// }
// Logger::log("Task completed.".to_string());
// // --- Test 1: Get the 10 most recent logs (offset 0) ---
// println!("--- Most Recent Logs (LOG_COUNT={}) ---", LOG_COUNT);
// let recent_logs = Logger::poll_logs(0);
// for log in &recent_logs {
// println!("{}", log);
// }
// // The output should be:
// // [timestamp] Task completed.
// // [timestamp] Processing request #20
// // [timestamp] Processing request #19
// // ...
// // [timestamp] Processing request #12
// println!(
// "\n--- Logs from the past (Offset 15, LOG_COUNT={}) ---",
// LOG_COUNT
// );
// // --- Test 2: Skip the 15 most recent logs, then get the next 10 ---
// let historical_logs = Logger::poll_logs(15);
// for log in &historical_logs {
// println!("{}", log);
// }
// // The output should be:
// // [timestamp] Processing request #6
// // [timestamp] Processing request #5
// // ...
// // [timestamp] Application started.
// println!("\n--- Testing a high offset (Offset 100) ---");
// // --- Test 3: Test an offset that goes beyond the log file length ---
// let empty_logs = Logger::poll_logs(100);
// if empty_logs.is_empty() {
// println!("Correctly returned empty set for high offset.");
// }
// }
+1 -1
View File
@@ -8,7 +8,7 @@ use serde_json::Value;
use sled::Tree;
use unshell_lib::{debug, error};
use crate::{api::CurrentUser, server::Server};
use crate::{auth::structs::CurrentUser, server::Server};
impl Server {
fn get_tree(&self, tree_name: &str) -> Result<Tree, String> {
+1 -3
View File
@@ -1,5 +1,3 @@
use std::collections::HashMap;
use axum::{
Extension, Json,
extract::{Path, State},
@@ -8,7 +6,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use unshell_lib::debug;
use crate::{Server, api::CurrentUser};
use crate::{Server, auth::structs::CurrentUser};
pub trait Tree {
fn is_folder() -> bool {