mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 10:26:21 +00:00
ref(backend) start of refactor
This commit is contained in:
36 files changed
+183
-42
No files matched your search
@@ -0,0 +1,192 @@
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::{env, fs};
|
||||
|
||||
use axum::extract::{Query, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::Json;
|
||||
use futures::{stream, StreamExt};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Map, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
use sqlx::{query, PgPool};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api::post::errors::refresh::PostIdxError;
|
||||
use crate::api::post::utils::{build_post_idx, post};
|
||||
use crate::model::blog::{POST_BASE_MODEL, POST_HASH_MODEL, POST_META_MODEL, TAG_MODEL};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Params {
|
||||
/// refresh level
|
||||
level: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn refresh(
|
||||
State(db_connection): State<PgPool>,
|
||||
Query(params): Query<Params>,
|
||||
) -> Result<StatusCode, (StatusCode, Json<Value>)> {
|
||||
let is_force_refresh = params.level.unwrap_or("".to_string()) == "force";
|
||||
|
||||
// search post
|
||||
let data_repo_dir = match env::var("GIT_WORK_DIR") {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json::from(json!((
|
||||
"error",
|
||||
"error at parsing environment variable: GIT_WORK_DIR"
|
||||
))),
|
||||
));
|
||||
}
|
||||
};
|
||||
let post_dir = Path::new(&data_repo_dir).join("posts");
|
||||
|
||||
if is_force_refresh {
|
||||
if let Err(e) = query("DROP TABLE IF EXISTS hash, meta, post, tag;")
|
||||
.execute(&db_connection)
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"DB query error when deleting table: `hash`, `meta`, `post`, `tag`: {}",
|
||||
e.to_string()
|
||||
);
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json::from(json!(("error", e.to_string()))),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// generate post idx
|
||||
let post_path = search_md(&post_dir);
|
||||
|
||||
let post_list_with_hash: Vec<(Map<_, _>, Vec<_>)> = post_path
|
||||
.into_iter()
|
||||
.filter_map(|post_path| match post::from_path(&post_path) {
|
||||
Ok(post) => match fs::read(&post_path) {
|
||||
Ok(bytes) => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(bytes);
|
||||
let hash = hasher.finalize().to_vec();
|
||||
Some((post, hash))
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to read file while hashing the post <{}>, skipping: {}",
|
||||
post_path,
|
||||
e.to_string()
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to parse post <{}>, skipping: {}",
|
||||
post_path,
|
||||
e.to_string()
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// db create
|
||||
query(POST_BASE_MODEL)
|
||||
.execute(&db_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
query(POST_META_MODEL)
|
||||
.execute(&db_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
query(TAG_MODEL).execute(&db_connection).await.unwrap();
|
||||
query(POST_HASH_MODEL)
|
||||
.execute(&db_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let res: Vec<_> = stream::iter(post_list_with_hash)
|
||||
.map(|(post, hash)| {
|
||||
let db_clone = db_connection.clone();
|
||||
async move { build_post_idx(&db_clone, post, hash).await }
|
||||
})
|
||||
.buffer_unordered(usize::MAX)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let errs: Vec<_> = res
|
||||
.into_iter()
|
||||
.filter_map(|res| res.err())
|
||||
.map(|e| e.to_string())
|
||||
.collect();
|
||||
|
||||
if !errs.is_empty() {
|
||||
Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json::from(json!({"error": errs})),
|
||||
))
|
||||
} else {
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
}
|
||||
|
||||
/// Search a dir, return a list of markdown files
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `entry`: dir path
|
||||
///
|
||||
/// # Returns
|
||||
/// Vec<String, Global>
|
||||
/// * `String`: path of a markdown file
|
||||
///
|
||||
fn search_md(entry: &Path) -> Vec<String> {
|
||||
if entry.ends_with(".git") {
|
||||
return vec![];
|
||||
}
|
||||
if entry.is_dir() {
|
||||
if let Ok(files) = entry.read_dir() {
|
||||
files
|
||||
.flat_map(|file| {
|
||||
let file_path = file.unwrap().path();
|
||||
search_md(file_path.as_path())
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
} else if entry.extension().is_some_and(|ext| ext == "md") {
|
||||
vec![entry.to_str().unwrap().to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_post_idx(
|
||||
db_connection: &PgPool,
|
||||
post: Map<String, Value>,
|
||||
hash: Vec<u8>,
|
||||
) -> Result<(), PostIdxError> {
|
||||
let meta = &post["meta"];
|
||||
|
||||
// extract post id
|
||||
let post_id = match Uuid::from_str(post["post_id"].as_str().unwrap()) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Err(PostIdxError::InvalidUUID {
|
||||
id: post["post_id"].as_str().unwrap().to_string(),
|
||||
err_msg: e.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
if build_post_idx::hash(db_connection, post_id, hash).await? {
|
||||
build_post_idx::base(db_connection, &post, post_id, meta).await?;
|
||||
build_post_idx::tag(db_connection, post_id, meta).await?;
|
||||
build_post_idx::meta(db_connection, post_id, meta).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in new issue
Block a user