diff --git a/backend/src/api/get/get_post.rs b/backend/src/api/get/get_post.rs index b4b566b..06663bf 100644 --- a/backend/src/api/get/get_post.rs +++ b/backend/src/api/get/get_post.rs @@ -1,17 +1,35 @@ use std::str::FromStr; -use axum::extract::{Path, State}; -use axum::http::StatusCode; -use axum::Json; +use axum::{ + extract::{Path, State}, + http::StatusCode, + Json, +}; use chrono::{DateTime, Utc}; use serde_json::{json, Map, Value}; use sqlx::{Error, PgPool, query, Row}; +use tracing::error; use uuid::Uuid; pub async fn get_post( State(db_connection): State, Path(post_id): Path, ) -> Result, (StatusCode, Json)> { + let post_id = match Uuid::from_str(post_id.as_str()) { + Ok(id) => id, + Err(e) => { + error!( + "Failed to parse UUID <{}>: {}", + post_id.as_str(), + e.to_string() + ); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json::from(json!(("error", e.to_string()))), + )); + } + }; + let meta = match query( r#" SELECT * @@ -19,12 +37,13 @@ FROM Post WHERE post_id = $1; "#, ) - .bind(Uuid::from_str(post_id.as_str()).expect("Failed to parse UUID")) + .bind(post_id) .fetch_one(&db_connection) .await { Ok(value) => value, Err(e) => { + error!("Database query error on fetching meta: {}", e.to_string()); return match e { Error::RowNotFound => Err(( StatusCode::BAD_REQUEST, @@ -43,10 +62,19 @@ WHERE post_id = $1; SELECT tag FROM Tag WHERE post_id = $1; "#, ) - .bind(Uuid::from_str(post_id.as_str()).expect("Failed to parse UUID")) + .bind(post_id) .fetch_all(&db_connection) - .await - .expect("Failed to execute db query"); + .await; + let tags = match tags { + Ok(tags) => tags, + Err(e) => { + error!("Database query error :{}", e.to_string()); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json::from(json!(("error", e.to_string()))), + )); + } + }; let mut res = Map::new(); @@ -54,6 +82,18 @@ SELECT tag FROM Tag WHERE post_id = $1; "title".to_string(), json!(meta.get::("title")), ); + res.insert( + "sub_title".to_string(), + json!(meta.get::("sub_title")), + ); + res.insert( + "category".to_string(), + json!(meta.get::("category")), + ); + res.insert( + "header_img".to_string(), + json!(meta.get::("header_img")), + ); res.insert( "summary".to_string(), json!(meta.get::("summary")), diff --git a/backend/src/api/get/get_post_list.rs b/backend/src/api/get/get_post_list.rs index 329a807..af1dcc4 100644 --- a/backend/src/api/get/get_post_list.rs +++ b/backend/src/api/get/get_post_list.rs @@ -1,11 +1,9 @@ -use axum::extract::State; -use axum::http::StatusCode; -use axum::Json; +use axum::{extract::State, http::StatusCode, Json}; use chrono::{DateTime, Utc}; use futures::future::join_all; use serde_json::{json, Map, Value}; -use sqlx::{PgPool, query, Row}; -use sqlx::postgres::PgRow; +use sqlx::{postgres::PgRow, query, PgPool, Row}; +use tracing::error; use uuid::Uuid; pub async fn get_post_list( @@ -15,8 +13,20 @@ pub async fn get_post_list( "select post_id, title, sub_title, summary, last_update, first_update, header_img, category from post", ) .fetch_all(&db_connection) - .await - .unwrap(); + .await; + let post_list = match post_list { + Ok(post_list) => post_list, + Err(e) => { + error!( + "Database query error on fetching post list: {}", + e.to_string() + ); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json::from(json!(("error", e.to_string()))), + )); + } + }; let mut res = Map::new(); let posts: Vec<_> = post_list diff --git a/backend/src/api/get/mod.rs b/backend/src/api/get/mod.rs index 44008d0..a30b1d7 100644 --- a/backend/src/api/get/mod.rs +++ b/backend/src/api/get/mod.rs @@ -1,2 +1,2 @@ pub mod get_post; -pub mod get_post_list; \ No newline at end of file +pub mod get_post_list; diff --git a/backend/src/api/post/refresh.rs b/backend/src/api/post/refresh.rs index ac3c318..f073eb8 100644 --- a/backend/src/api/post/refresh.rs +++ b/backend/src/api/post/refresh.rs @@ -8,6 +8,7 @@ use axum::Json; use chrono::DateTime; use serde_json::{json, Map, Value}; use sqlx::{PgPool, query}; +use tracing::error; use uuid::Uuid; use crate::constants::GLOBAL_TIME_FORMAT; @@ -45,7 +46,17 @@ pub async fn refresh( let post_list: Vec> = md_list .into_iter() - .map(|post| post::from_path(post).unwrap()) + .filter_map(|post_path| match post::from_path(&post_path) { + Ok(post) => Some(post), + Err(e) => { + error!( + "Failed to parse post <{}>, skipping: {}", + post_path, + e.to_string() + ); + None + } + }) .collect(); query(POST_DB_MODEL).execute(&db_connection).await.unwrap(); @@ -56,6 +67,64 @@ pub async fn refresh( for post in post_list { let meta = &post["meta"]; + let post_id = match Uuid::from_str(post["post_id"].as_str().unwrap()) { + Ok(id) => id, + Err(e) => { + error!( + "Failed to parse UUID <{}>: {}", + post["post_id"].as_str().unwrap(), + e.to_string() + ); + continue; + } + }; + let title = post["title"].as_str(); + let summary = post + .get("summary") + .and_then(|summary| summary.as_str()) + .unwrap_or(""); + let content = post["content"].as_str(); + let last_update = match DateTime::parse_from_str( + post["last_update"].as_str().unwrap(), + GLOBAL_TIME_FORMAT, + ) { + Ok(time) => time, + Err(e) => { + error!( + "Failed to parse last update time for <{}> on db table initialization: {}", + post_id.to_string(), + e.to_string() + ); + continue; + } + }; + let first_update = match DateTime::parse_from_str( + post["first_update"].as_str().unwrap(), + GLOBAL_TIME_FORMAT, + ) { + Ok(time) => time, + Err(e) => { + error!( + "Failed to parse first update time for <{}> on db table initialization: {}", + post_id.to_string(), + e.to_string() + ); + continue; + } + }; + let sub_title = meta + .get("sub_title") + .and_then(|sub_title| sub_title.as_str()) + .unwrap_or(""); + let category = meta + .get("category") + .and_then(|category| category.as_str()) + .unwrap_or("N/A"); + let header_img = meta + .get("header_img") + .and_then(|header_img| header_img.as_str()) + .unwrap_or(""); + // table Post match query( r##" @@ -63,55 +132,24 @@ INSERT INTO Post (post_id, title, summary, content, last_update, first_update, s VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "##, ) - .bind(Uuid::from_str(post["post_id"].as_str().unwrap()).unwrap()) - .bind(post["title"].as_str()) - .bind( - post.get("summary") - .and_then(|summary| summary.as_str()) - .unwrap_or(""), - ) - .bind(post["content"].as_str()) - .bind( - DateTime::parse_from_str( - post["last_update"] - .as_str() - .expect("Failed to parse `last_update`"), - GLOBAL_TIME_FORMAT, - ) - .expect("Failed to convert `last_update` to NaiveDateTime"), - ) - .bind( - DateTime::parse_from_str( - post["first_update"] - .as_str() - .expect("Failed to parse `first_update`"), - GLOBAL_TIME_FORMAT, - ) - .expect("Failed to convert `first_update` to NaiveDateTime"), - ) - .bind( - meta.get("sub_title") - .and_then(|sub_title| sub_title.as_str()) - .unwrap_or(""), - ) - .bind( - meta.get("category") - .and_then(|category| category.as_str()) - .unwrap_or("N/A"), - ) - .bind( - meta.get("header_img") - .and_then(|header_img| header_img.as_str()) - .unwrap_or(""), - ) + .bind(post_id) + .bind(title) + .bind(summary) + .bind(content) + .bind(last_update) + .bind(first_update) + .bind(sub_title) + .bind(category) + .bind(header_img) .execute(&db_connection) .await { Ok(_) => {} Err(e) => { + error!("Database error on initializing post data (insert):<{}> : {}", post_id.to_string(), e.to_string()); return Err(( StatusCode::INTERNAL_SERVER_ERROR, - Json::from(json!(format!("{{error: {}}}", e.to_string()))), + Json::from(json!(("error:", e.to_string()))), )); } } diff --git a/backend/src/logger.rs b/backend/src/logger.rs index f5c4421..974a706 100644 --- a/backend/src/logger.rs +++ b/backend/src/logger.rs @@ -1,14 +1,13 @@ use std::{env, io}; use chrono::Local; -use tracing::{error, info, Level}; +use tracing::info; use tracing_appender::{non_blocking, rolling}; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::{fmt, Registry}; use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::fmt::MakeWriter; use tracing_subscriber::fmt::time::FormatTime; -use tracing_subscriber::fmt::writer::MakeWriterExt; use tracing_subscriber::layer::SubscriberExt; use crate::constants::GLOBAL_TIME_FORMAT; @@ -21,7 +20,7 @@ impl FormatTime for LocalTimer { } } -type LogWriter = dyn for<'writer> MakeWriter<'writer, Writer = dyn io::Write + 'static> + 'static; +type _LogWriter = dyn for<'writer> MakeWriter<'writer, Writer = dyn io::Write + 'static> + 'static; pub fn init() -> (WorkerGuard, WorkerGuard) { let log_path = env::var("LOG_ROOT_DIR").unwrap_or_else(|e| { @@ -31,10 +30,8 @@ pub fn init() -> (WorkerGuard, WorkerGuard) { let log_path = format!("{}/backend", log_path); let (info_log_writer, info_guard) = non_blocking(rolling::never(&log_path, "info.log")); - // let info_log = info_log.with_max_level(Level::TRACE); let (err_log_writer, error_guard) = non_blocking(rolling::never(&log_path, "error.log")); - // let error_log = error_logging_handle.with_filter(|log| log.level().eq(&Level::ERROR)); let log_format = tracing_subscriber::fmt::format() .with_level(true) @@ -42,12 +39,10 @@ pub fn init() -> (WorkerGuard, WorkerGuard) { .with_source_location(true) .with_timer(LocalTimer); - // todo: separate errors/warnings/info logs let subscribers = Registry::default() .with( fmt::Layer::default() .with_writer(info_log_writer) - // .with_max_level(Level::TRACE) .with_ansi(false), ) .with( diff --git a/backend/src/main.rs b/backend/src/main.rs index f641aa4..17550e5 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -4,11 +4,11 @@ use std::num::ParseIntError; use std::process::exit; use std::str::FromStr; -use axum::Router; use axum::routing::{get, post}; +use axum::Router; use dotenv::dotenv; -use sqlx::ConnectOptions; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; +use sqlx::ConnectOptions; use tracing::{error, warn}; use crate::api::get::get_post::get_post; diff --git a/backend/src/model/post.rs b/backend/src/model/post.rs index 0777b80..ae72e23 100644 --- a/backend/src/model/post.rs +++ b/backend/src/model/post.rs @@ -6,17 +6,20 @@ use std::str::FromStr; use chrono::{DateTime, Utc}; use serde_json::{Map, Value}; +use tracing::{error, warn}; use uuid::Uuid; use crate::constants::GLOBAL_TIME_FORMAT; use crate::markdown::meta::parse_meta; -pub fn from_path(path: String) -> Result, Box> { +pub fn from_path(path: &str) -> Result, Box> { let mut res = Map::new(); - let md_content = fs::read_to_string(&path)?; + // field `content` + let md_content = fs::read_to_string(path)?; res.insert("content".to_string(), Value::from(md_content.to_owned())); + // field `meta` let meta = parse_meta(&md_content); let meta_map: Map = meta .clone() @@ -25,25 +28,35 @@ pub fn from_path(path: String) -> Result, Box> { .collect(); res.insert("meta".to_string(), Value::from(meta_map)); + // field `post_id` let post_id = match meta.get("id") { None => Uuid::new_v4(), - Some(id) => Uuid::from_str(id.as_str()).expect("Invalid UUID String"), + Some(id) => match Uuid::from_str(id.as_str()) { + Ok(id) => id, + Err(e) => { + error!("Failed to parse UUID <{}>: {}", id.as_str(), e.to_string()); + return Err(Box::new(e)); + } + }, }; res.insert("post_id".to_string(), Value::from(post_id.to_string())); + // field `title` let title = match meta.get("title") { None => "Title Not Found".to_string(), Some(title) => title.to_owned(), }; res.insert("title".to_string(), Value::from(title)); + // field `summary` let summary = match meta.get("summary") { None => "".to_string(), Some(summary) => summary.to_owned(), }; res.insert("summary".to_string(), Value::from(summary)); - let git_output = Command::new("git") + // field `last_update` + let git_output = match Command::new("git") .arg("--no-pager") .arg("log") .arg("-1") @@ -52,7 +65,13 @@ pub fn from_path(path: String) -> Result, Box> { .current_dir(env::var("GIT_WORK_DIR")?) .arg(&path) .output() - .expect("Failed to execute git command"); + { + Ok(res) => res, + Err(e) => { + error!("Failed to execute `git` command: {}", e.to_string()); + return Err(Box::new(e)); + } + }; let last_update: DateTime = if git_output.status.success() { chrono::DateTime::parse_from_str( str::from_utf8(&git_output.stdout).unwrap().trim_end(), @@ -68,11 +87,28 @@ pub fn from_path(path: String) -> Result, Box> { Value::from(last_update.format(GLOBAL_TIME_FORMAT).to_string()), ); + // field `first_update` let first_update: DateTime = match meta.get("date") { - None => Utc::now().to_utc(), + None => { + warn!( + "Failed to parse first update time for post {}, using current time instead", + post_id.to_string() + ); + Utc::now().to_utc() + } Some(date) => DateTime::from( - DateTime::parse_from_str(format!("{}T000000", date).as_str(), "%Y-%m-%d%zT%H%M%S") - .expect("Failed to parse first_update time"), + match DateTime::parse_from_str(format!("{}T000000", date).as_str(), "%Y-%m-%d%zT%H%M%S") + { + Ok(time) => time, + Err(e) => { + warn!( + "Failed to parse first update time for post {}, using current time instead: {}", + post_id.to_string(), + e.to_string() + ); + DateTime::from(Utc::now()) + } + }, ), }; res.insert(