update code

This commit is contained in:
MedzikUser 2022-06-08 21:16:12 +02:00
parent 18b6cb21b1
commit ece96bb3af
No known key found for this signature in database
GPG Key ID: A5FAC1E185C112DB
31 changed files with 210 additions and 135 deletions

2
.gitignore vendored
View File

@ -7,8 +7,6 @@
# database
*.db
# why sqlx?
*.db-shm
*.db-wal

View File

@ -1,26 +0,0 @@
use std::fs::File;
use log::LevelFilter;
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
pub fn init() -> anyhow::Result<()> {
// init better_panic
better_panic::install();
// init logger
CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Debug,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(
LevelFilter::Info,
Config::default(),
File::create("logs.log").expect("create logs file"),
),
])?;
Ok(())
}

View File

@ -1,12 +1,14 @@
mod init;
use homedisk_database::Database;
use homedisk_server::run_http_server;
use homedisk_types::config::types::Config;
use homedisk_server::serve_http;
use homedisk_types::config::Config;
/// Main function
#[tokio::main]
async fn main() -> anyhow::Result<()> {
init::init()?;
// init better_panic
better_panic::install();
// init logger
init_logger()?;
// parse config
let config = Config::parse()?;
@ -22,6 +24,7 @@ async fn main() -> anyhow::Result<()> {
.map(|e| e.parse().expect("parse CORS hosts"))
.collect();
// format host ip and port
let host = format!(
"{host}:{port}",
host = config.http.host,
@ -29,7 +32,31 @@ async fn main() -> anyhow::Result<()> {
);
// start http server
run_http_server(host, origins, db, config).await?;
serve_http(host, origins, db, config).await?;
Ok(())
}
/// Init logger
fn init_logger() -> anyhow::Result<()> {
use std::fs::File;
use log::LevelFilter;
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Debug,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(
LevelFilter::Info,
Config::default(),
File::create("logs.log").expect("create logs file"),
),
])?;
Ok(())
}

View File

@ -1,6 +1,7 @@
mod error;
mod sqlite;
pub use error::*;
/// Imported from `homedisk_types::database::User`.
pub use homedisk_types::database::User;
/// Imported from `homedisk_types::errors::DatabaseError`.
pub use homedisk_types::errors::DatabaseError as Error;
pub use sqlite::*;

View File

@ -4,9 +4,10 @@ use sqlx::{sqlite::SqliteQueryResult, Executor, Row, SqlitePool};
use super::{Error, User};
/// SQL Database
#[derive(Debug, Clone)]
pub struct Database {
/// Sqlite Connection Pool
/// SQLite Connection Pool
pub conn: SqlitePool,
}
@ -15,7 +16,11 @@ impl Database {
/// ```ignore,rust
/// use homedisk_database::Database;
///
/// // open database in memory
/// Database::open("sqlite::memory:").await?;
///
/// // open database from file
/// Database::open("path/to/file.db").await?;
/// ```
pub async fn open(path: &str) -> Result<Self, Error> {
debug!("Opening SQLite database");
@ -29,7 +34,10 @@ impl Database {
/// ```ignore,rust
/// use homedisk_database::{Database, User};
///
/// // create `User` type
/// let user = User::new("username", "password");
///
/// // create a user in database
/// db.create_user(&user).await?;
/// ```
pub async fn create_user(&self, user: &User) -> Result<SqliteQueryResult, Error> {
@ -47,24 +55,32 @@ impl Database {
/// ```ignore,rust
/// use homedisk_database::{Database, User};
///
/// // create `User` type
/// let user = User::new("username", "password");
///
/// // search for a user in database
/// db.find_user(&user.username, &user.password).await?;
/// ```
pub async fn find_user(&self, username: &str, password: &str) -> Result<User, Error> {
debug!("Searching for a user - {}", username);
// create query request to database
let query =
sqlx::query_as::<_, User>("SELECT * FROM user WHERE username = ? AND password = ?")
.bind(username)
.bind(password);
// fetch query
let mut stream = self.conn.fetch(query);
// get rows from query
let row = stream.try_next().await?.ok_or(Error::UserNotFound)?;
// get `id` row
let id = row.try_get("id")?;
// get `username` row
let username = row.try_get("username")?;
// get `password` row
let password = row.try_get("password")?;
Ok(User {
@ -78,21 +94,29 @@ impl Database {
/// ```ignore,rust
/// use homedisk_database::{Database, User};
///
/// // create `User` type
/// let user = User::new("username", "password");
///
/// // search for a user by UUID in database
/// db.find_user_by_id(&user.id).await?;
/// ```
pub async fn find_user_by_id(&self, id: String) -> Result<User, Error> {
debug!("Searching for a user by UUID - {}", id);
// create query request to database
let query = sqlx::query_as::<_, User>("SELECT * FROM user WHERE id = ?").bind(id);
// fetch query
let mut stream = self.conn.fetch(query);
// get rows from query
let row = stream.try_next().await?.ok_or(Error::UserNotFound)?;
// get `id` row
let id = row.try_get("id")?;
// get `username` row
let username = row.try_get("username")?;
// get `password` row
let password = row.try_get("password")?;
Ok(User {

View File

@ -2,7 +2,7 @@ use axum::{extract::rejection::JsonRejection, Extension, Json};
use homedisk_database::{Database, Error, User};
use homedisk_types::{
auth::login::{Request, Response},
config::types::Config,
config::Config,
errors::{AuthError, ServerError},
};

View File

@ -4,12 +4,13 @@ use axum::{extract::rejection::JsonRejection, Extension, Json};
use homedisk_database::{Database, User};
use homedisk_types::{
auth::login::{Request, Response},
config::types::Config,
config::Config,
errors::{AuthError, ServerError},
};
use crate::middleware::{create_token, validate_json};
/// Handle `/auth/register` requests
pub async fn handle(
Extension(db): Extension<Database>,
Extension(config): Extension<Config>,

View File

@ -3,7 +3,7 @@ use axum_auth::AuthBearer;
use homedisk_database::{Database, Error};
use homedisk_types::{
auth::whoami::Response,
config::types::Config,
config::Config,
errors::{AuthError, ServerError},
};
@ -18,6 +18,7 @@ pub async fn handle(
// validate user token
let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?;
// search for a user in database
let response = match db.find_user_by_id(token.claims.sub).await {
Ok(res) => Response {
username: res.username,

View File

@ -1,29 +1,35 @@
// HTTP Error
#[derive(Debug, thiserror::Error)]
pub enum Error {
// axum::Error
#[error("axum error - {0}")]
Axum(axum::Error),
// hyper::Error
#[error("hyper error - {0}")]
Hyper(hyper::Error),
// std::net::AddrParseError
#[error("std::net::AddrParseError - {0}")]
AddrParseError(std::net::AddrParseError),
}
/// Custom Result
pub type Result<T> = std::result::Result<T, Error>;
/// axum::Error
impl From<axum::Error> for Error {
fn from(err: axum::Error) -> Self {
Error::Axum(err)
}
}
/// hyper::Error
impl From<hyper::Error> for Error {
fn from(err: hyper::Error) -> Self {
Error::Hyper(err)
}
}
/// std::net::AddrParseError
impl From<std::net::AddrParseError> for Error {
fn from(err: std::net::AddrParseError) -> Self {
Error::AddrParseError(err)

View File

@ -5,13 +5,14 @@ use axum_auth::AuthBearer;
use homedisk_database::Database;
use homedisk_types::fs::create_dir::{Request, Response};
use homedisk_types::{
config::types::Config,
config::Config,
errors::{FsError, ServerError},
};
use crate::fs::validate_path;
use crate::middleware::{find_user, validate_json, validate_jwt};
/// Handle `/fs/createdir` requests
pub async fn handle(
Extension(db): Extension<Database>,
Extension(config): Extension<Config>,

View File

@ -5,7 +5,7 @@ use axum::Extension;
use axum_auth::AuthBearer;
use homedisk_database::Database;
use homedisk_types::{
config::types::Config,
config::Config,
errors::{FsError, ServerError},
fs::delete::Request,
};

View File

@ -6,10 +6,11 @@ use axum::Extension;
use axum_auth::AuthBearer;
use homedisk_database::Database;
use homedisk_types::fs::upload::Pagination;
use homedisk_types::{config::types::Config, errors::ServerError};
use homedisk_types::{config::Config, errors::ServerError};
use crate::middleware::{find_user, validate_jwt};
/// Handle `/fs/download` requests
pub async fn handle(
Extension(db): Extension<Database>,
Extension(config): Extension<Config>,

View File

@ -8,7 +8,7 @@ use byte_unit::Byte;
use homedisk_database::Database;
use homedisk_types::fs::list::DirInfo;
use homedisk_types::{
config::types::Config,
config::Config,
errors::{FsError, ServerError},
fs::list::{FileInfo, Request, Response},
};

View File

@ -7,7 +7,7 @@ use axum_auth::AuthBearer;
use futures::TryStreamExt;
use homedisk_database::Database;
use homedisk_types::{
config::types::Config,
config::Config,
errors::{FsError, ServerError},
fs::upload::{Pagination, Response},
};
@ -15,6 +15,7 @@ use homedisk_types::{
use crate::fs::validate_path;
use crate::middleware::{find_user, validate_jwt};
/// Handle `/fs/upload` requests
pub async fn handle(
Extension(db): Extension<Database>,
Extension(config): Extension<Config>,

View File

@ -1,12 +1,11 @@
pub mod auth;
pub mod fs;
pub mod middleware;
mod auth;
mod error;
mod fs;
mod middleware;
use axum::{http::HeaderValue, routing::get, Extension, Router, Server};
use homedisk_database::Database;
use homedisk_types::config::types::Config;
use homedisk_types::config::Config;
use log::{debug, info};
use tower_http::cors::{AllowOrigin, CorsLayer};
@ -15,7 +14,8 @@ async fn health_check() -> &'static str {
"I'm alive!"
}
pub async fn run_http_server(
/// Start HTTP server
pub async fn serve_http(
host: String,
origins: Vec<HeaderValue>,
db: Database,

View File

@ -1,3 +1,5 @@
//! `/auth/login` Request and Response types
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};

View File

@ -1,2 +1,4 @@
//! HTTP `/auth/*` types for Request and Response
pub mod login;
pub mod whoami;

View File

@ -1,3 +1,5 @@
//! `/auth/whoami` Response type
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]

View File

@ -1,4 +1,7 @@
pub mod types;
//! Types for configuration file
#[cfg(feature = "config")]
pub mod toml;
mod toml;
mod types;
pub use types::*;

View File

@ -8,6 +8,11 @@ use super::types::Config;
impl Config {
/// Parse configuration file
/// ```
/// use homedisk_types::config::Config;
///
/// let config = Config::parse().unwrap();
/// ```
pub fn parse() -> Result<Config> {
// config file path
let config_dir = option_return!(dirs::config_dir(), "get config dir")?;

View File

@ -1,5 +1,8 @@
//! Configuration file types
use serde::{Deserialize, Serialize};
/// Config type
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub http: ConfigHTTP,
@ -7,6 +10,7 @@ pub struct Config {
pub storage: ConfigStorage,
}
/// HTTP config
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigHTTP {
/// HTTP Host
@ -17,6 +21,7 @@ pub struct ConfigHTTP {
pub cors: Vec<String>,
}
/// Json Web Token config
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigJWT {
/// JWT Secret string
@ -25,6 +30,7 @@ pub struct ConfigJWT {
pub expires: i64,
}
/// Storage config
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigStorage {
/// Directory where user files will be stored

View File

@ -1,3 +1,5 @@
//! Typed for a database
mod user;
pub use user::*;

View File

@ -1,7 +1,7 @@
use rust_utilities::crypto::sha::{encode, Algorithm, CryptographicHash};
use uuid::Uuid;
/// SQL `user` Table
/// SQL user table
#[derive(Debug, sqlx::FromRow)]
pub struct User {
pub id: String,
@ -53,12 +53,14 @@ impl User {
/// assert_eq!(dir, "/home/homedisk/medzik")
/// ```
pub fn user_dir(&self, storage: &str) -> String {
// get a user storage path
let path = format!(
"{path}/{username}",
path = storage,
username = self.username,
);
// return user storage path
path
}
}

View File

@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
/// `/auth/*` Error
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
pub enum Error {
#[error("user not found")]

View File

@ -1,13 +1,14 @@
/// Database Error
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("user not found")]
UserNotFound,
/// sqlx::Error
#[error("sqlx error - {0}")]
SQLx(sqlx::Error),
/// std::io::Error
#[error("std::io error - {0}")]
Io(std::io::Error),
StdIo(std::io::Error),
}
/// sqlx::Error
@ -20,6 +21,6 @@ impl From<sqlx::Error> for Error {
/// std::io::Error
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
Error::StdIo(err)
}
}

View File

@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
/// `/fs/*` Error
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
pub enum Error {
#[error("file already exists")]

View File

@ -1,79 +1,12 @@
//! Error types
mod auth;
#[cfg(feature = "database")]
mod database;
mod fs;
mod server;
pub use auth::Error as AuthError;
pub use database::Error as DatabaseError;
pub use fs::Error as FsError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[serde(tag = "error", content = "error_message", rename_all = "kebab-case")]
pub enum ServerError {
#[error("auth error: {0}")]
AuthError(#[from] AuthError),
#[error("fs error: {0}")]
FsError(#[from] FsError),
#[error("too may requests, please slow down")]
TooManyRequests,
#[error("missing json content type")]
MissingJsonContentType,
#[error("error deserialize json")]
JsonDataError,
#[error("json syntax error")]
JsonSyntaxError,
#[error("failed to extract the request body")]
BytesRejection,
#[error("unexpected error - {0}")]
Other(String),
}
#[cfg(feature = "axum")]
impl axum::response::IntoResponse for ServerError {
fn into_response(self) -> axum::response::Response {
use axum::http::StatusCode;
let status = match self {
Self::AuthError(ref err) => match err {
AuthError::UserNotFound => StatusCode::BAD_REQUEST,
AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE,
AuthError::UsernameTooShort => StatusCode::NOT_ACCEPTABLE,
AuthError::UsernameTooLong => StatusCode::NOT_ACCEPTABLE,
AuthError::PasswordTooShort => StatusCode::NOT_ACCEPTABLE,
AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR,
AuthError::InvalidToken => StatusCode::BAD_REQUEST,
AuthError::UnknownError(_) => StatusCode::INTERNAL_SERVER_ERROR,
},
Self::FsError(ref err) => match err {
FsError::FileAlreadyExists => StatusCode::BAD_REQUEST,
FsError::FileDoesNotExist => StatusCode::BAD_REQUEST,
FsError::MultipartError => StatusCode::BAD_REQUEST,
FsError::CreateFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::CreateDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::DeleteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::DeleteDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::Base64(_) => StatusCode::BAD_REQUEST,
FsError::ReadDir(_) => StatusCode::BAD_REQUEST,
FsError::UnknownError(_) => StatusCode::INTERNAL_SERVER_ERROR,
},
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::MissingJsonContentType => StatusCode::BAD_REQUEST,
Self::JsonDataError => StatusCode::BAD_REQUEST,
Self::JsonSyntaxError => StatusCode::BAD_REQUEST,
Self::BytesRejection => StatusCode::INTERNAL_SERVER_ERROR,
Self::Other(_) => StatusCode::INTERNAL_SERVER_ERROR,
};
let mut response = axum::Json(self).into_response();
*response.status_mut() = status;
response
}
}
pub use server::Error as ServerError;

View File

@ -0,0 +1,76 @@
use serde::{Deserialize, Serialize};
use super::{AuthError, FsError};
/// HTTP Server Error
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[serde(tag = "error", content = "error_message", rename_all = "kebab-case")]
pub enum Error {
#[error("auth error: {0}")]
AuthError(#[from] AuthError),
#[error("fs error: {0}")]
FsError(#[from] FsError),
#[error("too may requests, please slow down")]
TooManyRequests,
#[error("missing json content type")]
MissingJsonContentType,
#[error("error deserialize json")]
JsonDataError,
#[error("json syntax error")]
JsonSyntaxError,
#[error("failed to extract the request body")]
BytesRejection,
#[error("unexpected error - {0}")]
Other(String),
}
#[cfg(feature = "axum")]
impl axum::response::IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
use axum::http::StatusCode;
let status = match self {
Self::AuthError(ref err) => match err {
AuthError::UserNotFound => StatusCode::BAD_REQUEST,
AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE,
AuthError::UsernameTooShort => StatusCode::NOT_ACCEPTABLE,
AuthError::UsernameTooLong => StatusCode::NOT_ACCEPTABLE,
AuthError::PasswordTooShort => StatusCode::NOT_ACCEPTABLE,
AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR,
AuthError::InvalidToken => StatusCode::BAD_REQUEST,
AuthError::UnknownError(_) => StatusCode::INTERNAL_SERVER_ERROR,
},
Self::FsError(ref err) => match err {
FsError::FileAlreadyExists => StatusCode::BAD_REQUEST,
FsError::FileDoesNotExist => StatusCode::BAD_REQUEST,
FsError::MultipartError => StatusCode::BAD_REQUEST,
FsError::CreateFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::CreateDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::DeleteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::DeleteDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::Base64(_) => StatusCode::BAD_REQUEST,
FsError::ReadDir(_) => StatusCode::BAD_REQUEST,
FsError::UnknownError(_) => StatusCode::INTERNAL_SERVER_ERROR,
},
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::MissingJsonContentType => StatusCode::BAD_REQUEST,
Self::JsonDataError => StatusCode::BAD_REQUEST,
Self::JsonSyntaxError => StatusCode::BAD_REQUEST,
Self::BytesRejection => StatusCode::INTERNAL_SERVER_ERROR,
Self::Other(_) => StatusCode::INTERNAL_SERVER_ERROR,
};
let mut response = axum::Json(self).into_response();
*response.status_mut() = status;
response
}
}

View File

@ -1,3 +1,5 @@
//! HTTP `/fs/*` types for Request and Response
pub mod create_dir;
pub mod delete;
pub mod download;

View File

@ -4,4 +4,5 @@ pub mod config;
pub mod database;
pub mod errors;
pub mod fs;
pub mod macros;
mod macros;

View File

@ -1,3 +1,4 @@
/// Return value or error (if None) from Some(T)
#[macro_export]
macro_rules! option_return {
($variable:expr,$err_desc:expr) => {