Add proper logging
This commit is contained in:
parent
9ab0cb2a6e
commit
1c77822b49
75
Cargo.lock
generated
75
Cargo.lock
generated
|
@ -31,6 +31,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.61"
|
||||
|
@ -937,6 +946,15 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
|
@ -1382,6 +1400,15 @@ dependencies = [
|
|||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
|
@ -1533,6 +1560,15 @@ dependencies = [
|
|||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
|
@ -1826,6 +1862,8 @@ dependencies = [
|
|||
"toml",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1943,6 +1981,7 @@ dependencies = [
|
|||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1988,6 +2027,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"matchers",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2127,6 +2196,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
|
@ -18,6 +18,8 @@ thiserror = "1.0.32"
|
|||
tokio = { version = "1.20.1", features = ["full"] }
|
||||
toml = "0.5.9"
|
||||
tower = "0.4.13"
|
||||
tower-http = { version = "0.3.4", features = ["fs"] }
|
||||
tower-http = { version = "0.3.4", features = ["fs", "trace"] }
|
||||
tracing = "0.1.36"
|
||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||
|
||||
[features]
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -20,6 +20,7 @@ use axum_sessions::{async_session::MemoryStore, SessionLayer};
|
|||
use serde::Deserialize;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tasks::RawTask;
|
||||
use tasks::Task;
|
||||
|
@ -27,6 +28,10 @@ use tera::Tera;
|
|||
use thiserror::Error as ThisError;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
#[derive(ThisError, Debug)]
|
||||
pub enum Error {
|
||||
|
@ -97,27 +102,42 @@ pub struct Config {
|
|||
connection_string: String,
|
||||
template_dir: Option<String>,
|
||||
static_dir: Option<std::path::PathBuf>,
|
||||
address: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_env_filter(EnvFilter::from_env("TMTD_LOG"))
|
||||
.with_ansi(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.finish();
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
|
||||
let filename = std::env::var("TMTD_CONFIG").unwrap_or_else(|_| "config.toml".into());
|
||||
info!("Loading file {}", filename);
|
||||
let contents = std::fs::read_to_string(filename)?;
|
||||
let config: Config = toml::from_str(&contents)?;
|
||||
info!("Config loaded, connecting to database...");
|
||||
let pool = Arc::new(
|
||||
PgPoolOptions::new()
|
||||
.connect(&config.connection_string)
|
||||
.await?,
|
||||
);
|
||||
|
||||
info!("Creating default tables (if needed)");
|
||||
sqlx::query("create table if not exists users(id serial primary key, username text not null unique, password_hash text not null)").execute(&*pool).await?;
|
||||
sqlx::query("create table if not exists tasks(id serial primary key, owner int not null, title text not null, description text not null, status int not null)").execute(&*pool).await?;
|
||||
|
||||
info!("Loading templates...");
|
||||
let tera = Arc::new(Tera::new(&format!(
|
||||
"{}/**/*.html",
|
||||
config.template_dir.as_deref().unwrap_or("templates")
|
||||
))?);
|
||||
|
||||
info!("Creating session layer");
|
||||
let store = MemoryStore::new();
|
||||
if config.secret.len() < 64 {
|
||||
return Err("Secret must be at least 64 bytes!".to_string().into());
|
||||
|
@ -154,10 +174,16 @@ async fn main() -> Result<(), Error> {
|
|||
ServiceBuilder::new()
|
||||
.layer(Extension(pool))
|
||||
.layer(Extension(tera))
|
||||
.layer(session_layer),
|
||||
.layer(session_layer)
|
||||
.layer(TraceLayer::new_for_http()),
|
||||
);
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
info!("Starting server...");
|
||||
let address = config
|
||||
.address
|
||||
.unwrap_or_else(|| "0.0.0.0:3000".parse().unwrap());
|
||||
info!("Server started on {}", address);
|
||||
axum::Server::bind(&address)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -170,7 +196,10 @@ macro_rules! login_or_redirect {
|
|||
($session:expr, $to:literal) => {
|
||||
match $session.get::<String>("logged_in_as") {
|
||||
Some(username) => username,
|
||||
None => return Err(::axum::response::Redirect::to($to).into()),
|
||||
None => {
|
||||
::tracing::warn!("User not logged in, redirecting to {}", $to);
|
||||
return Err(::axum::response::Redirect::to($to).into());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -195,21 +224,25 @@ async fn homepage(
|
|||
) -> Result<Html<String>, Error> {
|
||||
let username = login_or_redirect!(session, "/login");
|
||||
|
||||
info!("Getting user ID...");
|
||||
let (id,): (i32,) = sqlx::query_as("select id from users where username=$1")
|
||||
.bind(&username)
|
||||
.fetch_one(&*pool)
|
||||
.await?;
|
||||
|
||||
info!("Getting tasks");
|
||||
let tasks: Vec<RawTask> =
|
||||
sqlx::query_as("select id,title,description,status from tasks where owner=$1")
|
||||
.bind(id)
|
||||
.fetch_all(&*pool)
|
||||
.await?;
|
||||
|
||||
info!("Sorting tasks");
|
||||
let mut tasks: Vec<Task> = tasks.into_iter().map(Task::from).collect();
|
||||
tasks.sort_unstable();
|
||||
tasks.reverse();
|
||||
|
||||
info!("rendering pagee");
|
||||
let rendered = tera.render(
|
||||
"home.html",
|
||||
&ctx! {
|
||||
|
@ -217,5 +250,6 @@ async fn homepage(
|
|||
},
|
||||
)?;
|
||||
|
||||
info!("Rendering finished");
|
||||
Ok(Html(rendered))
|
||||
}
|
||||
|
|
12
src/tasks.rs
12
src/tasks.rs
|
@ -16,6 +16,7 @@ use sqlx::FromRow;
|
|||
use sqlx::{Pool, Postgres};
|
||||
use std::sync::Arc;
|
||||
use tera::Tera;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BareTask {
|
||||
|
@ -101,11 +102,13 @@ pub async fn update_form(
|
|||
) -> Result<Html<String>, Error> {
|
||||
let username = login_or_redirect!(session, "/login");
|
||||
|
||||
info!("Getting user id");
|
||||
let (user_id,): (i32,) = sqlx::query_as("select id from users where username=$1")
|
||||
.bind(&username)
|
||||
.fetch_one(&*pool)
|
||||
.await?;
|
||||
|
||||
info!("Getting task");
|
||||
let task: Option<RawTask> =
|
||||
sqlx::query_as("select id,title,description,status from tasks where id=$1 and owner=$2")
|
||||
.bind(id)
|
||||
|
@ -114,6 +117,7 @@ pub async fn update_form(
|
|||
.await?;
|
||||
|
||||
if let Some(task) = task {
|
||||
info!("Rendering template");
|
||||
let c = ctx! {
|
||||
"task" => task,
|
||||
"url" => uri.to_string()
|
||||
|
@ -121,6 +125,7 @@ pub async fn update_form(
|
|||
|
||||
let rendered = tera.render("tasks/update.html", &c)?;
|
||||
|
||||
info!("Rendering finished, returning");
|
||||
#[allow(clippy::needless_return)] // it's a compile error, clippy
|
||||
return Ok(Html(rendered));
|
||||
}
|
||||
|
@ -167,11 +172,13 @@ pub async fn create_backend(
|
|||
) -> Result<Redirect, Error> {
|
||||
let username = login_or_redirect!(session, "/login");
|
||||
|
||||
info!("Getting user id");
|
||||
let (user_id,): (i32,) = sqlx::query_as("select id from users where username=$1")
|
||||
.bind(&username)
|
||||
.fetch_one(&*pool)
|
||||
.await?;
|
||||
|
||||
info!("Inserting task...");
|
||||
let (id,): (i32,) = sqlx::query_as(
|
||||
"insert into tasks (owner,title, description, status) values ($1, $2, $3, $4) returning id",
|
||||
)
|
||||
|
@ -182,6 +189,7 @@ pub async fn create_backend(
|
|||
.fetch_one(&*pool)
|
||||
.await?;
|
||||
|
||||
info!("Done, redirecting");
|
||||
Ok(Redirect::to(&format!("/tasks/{}", id)))
|
||||
}
|
||||
|
||||
|
@ -193,11 +201,13 @@ pub async fn task_detail(
|
|||
) -> Result<Html<String>, Error> {
|
||||
let username = login_or_redirect!(session, "/login");
|
||||
|
||||
info!("Getting user id");
|
||||
let (user_id,): (i32,) = sqlx::query_as("select id from users where username=$1")
|
||||
.bind(&username)
|
||||
.fetch_one(&*pool)
|
||||
.await?;
|
||||
|
||||
info!("Getting task");
|
||||
let task: Option<RawTask> =
|
||||
sqlx::query_as("select id,title,description,status from tasks where id=$1 and owner=$2")
|
||||
.bind(id)
|
||||
|
@ -206,12 +216,14 @@ pub async fn task_detail(
|
|||
.await?;
|
||||
|
||||
if let Some(task) = task.map(Task::from) {
|
||||
info!("Rendering");
|
||||
let c = ctx! {
|
||||
"task" => task
|
||||
};
|
||||
|
||||
let rendered = tera.render("tasks/detail.html", &c)?;
|
||||
|
||||
info!("Rendering complete, returning");
|
||||
return Ok(Html(rendered));
|
||||
}
|
||||
|
||||
|
|
14
src/users.rs
14
src/users.rs
|
@ -14,6 +14,8 @@ use sqlx::{Pool, Postgres};
|
|||
use std::sync::Arc;
|
||||
use tera::Context;
|
||||
use tera::Tera;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct RawUser {
|
||||
|
@ -26,6 +28,7 @@ pub async fn create_user(
|
|||
Extension(pool): Extension<Arc<Pool<Postgres>>>,
|
||||
) -> Result<Redirect, Error> {
|
||||
let handle = tokio::task::spawn_blocking(move || {
|
||||
info!("Encrypting password");
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let password_hash = Pbkdf2.hash_password(data.password.as_bytes(), &salt);
|
||||
|
||||
|
@ -34,6 +37,7 @@ pub async fn create_user(
|
|||
|
||||
let hash = handle.await??;
|
||||
|
||||
info!("Inserting into database");
|
||||
sqlx::query("INSERT INTO users (username, password_hash) VALUES ($1, $2)")
|
||||
.bind(data.username)
|
||||
.bind(hash)
|
||||
|
@ -48,17 +52,20 @@ pub async fn login_backend(
|
|||
Extension(pool): Extension<Arc<Pool<Postgres>>>,
|
||||
mut session: WritableSession,
|
||||
) -> Result<Redirect, Error> {
|
||||
info!("Fetching user hash");
|
||||
let hash: Option<(String,)> =
|
||||
sqlx::query_as("SELECT password_hash FROM users WHERE username=$1")
|
||||
.bind(&data.username)
|
||||
.fetch_optional(&*pool)
|
||||
.await?;
|
||||
|
||||
let hash = hash
|
||||
.map(|(h,)| h)
|
||||
.ok_or_else(|| "Error: user not found. Have you registered?".to_string())?;
|
||||
let hash = hash.map(|(h,)| h).ok_or_else(|| {
|
||||
warn!("User was not found in the database");
|
||||
"Error: user not found. Have you registered?".to_string()
|
||||
})?;
|
||||
|
||||
let handle = tokio::task::spawn_blocking(move || {
|
||||
info!("Verifying password");
|
||||
let parsed_hash = PasswordHash::new(&hash)?;
|
||||
Pbkdf2
|
||||
.verify_password(data.password.as_bytes(), &parsed_hash)
|
||||
|
@ -77,6 +84,7 @@ pub async fn login_backend(
|
|||
macro_rules! login_and_redirect {
|
||||
($session:expr, $to:literal) => {
|
||||
if $session.get::<String>("logged_in_as").is_some() {
|
||||
::tracing::warn!("User already logged in, redirecting to {}", $to);
|
||||
return Err(::axum::response::Redirect::to($to).into());
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue