feat: fetch from cache if existed

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-02-02 14:12:16 +07:00
parent 3043f30012
commit 03263815e6
2 changed files with 98 additions and 31 deletions

View file

@ -8,10 +8,10 @@ use axum::{
};
use axum_macros::debug_handler;
use git2::{Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository};
use libtbr::{models, recipe_functions::common};
use log::{error, info, warn};
use redis::TypedCommands;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use serde_json::{Value, json};
use tokio::sync::Mutex;
@ -23,6 +23,8 @@ pub struct AppState {
configures: gcm::Configure,
repo: Arc<Mutex<Repository>>,
redis: Arc<Mutex<redis::Client>>,
// save already fetched path, further calls should fetch from redis instead.
fetched: Vec<String>,
}
impl AppState {
@ -45,11 +47,30 @@ struct CheckoutParams {
}
async fn checkout_handler(
State(state): State<AppState>,
State(mut state): State<AppState>,
Query(param): Query<CheckoutParams>,
) -> impl IntoResponse {
let mut response: HashMap<String, Value> = HashMap::new();
// quick response from redis
if let Ok(mut rcli) = state.redis.try_lock()
&& state
.fetched
.contains(&param.path.clone().as_str().to_string())
{
if let Ok(res) = rcli.get(param.path.clone().as_str().to_string()) {
match res {
Some(res) => {
response.insert("result".to_string(), json!(res));
return (axum::http::StatusCode::OK, Json(json!(response)));
}
None => {
// continue
}
}
}
}
let repo_path = state.get_config("GIT_REPO_LOCAL_DEST");
if repo_path.is_none() {
response.insert(
@ -105,12 +126,22 @@ async fn checkout_handler(
if let Some(blob) = obj.as_blob() {
let content = unsafe { str::from_utf8_unchecked(blob.content()) };
response.insert("result".to_string(), json!(content.to_string()));
if let Ok(mut rcli) = state.redis.try_lock() {
let _ = rcli.set(legit_path.to_string(), content.to_string());
state.fetched.push(legit_path.to_string());
}
} else if let Some(tree) = obj.as_tree() {
let dir_list = tree
.iter()
.map(|x| x.name().unwrap_or("").to_string())
.collect::<Vec<String>>();
response.insert("result".to_string(), json!(dir_list));
if let Ok(mut rcli) = state.redis.try_lock() {
let _ = rcli.set(legit_path.to_string(), dir_list.join(","));
state.fetched.push(legit_path.to_string());
}
} else {
let error_log = "not obj nor tree";
error!("{error_log}");
@ -123,7 +154,7 @@ async fn checkout_handler(
async fn fetch_handler(State(state): State<AppState>) -> impl IntoResponse {
let mut response: HashMap<String, Value> = HashMap::new();
if let Some(repo_path) = state.get_config("GIT_REPO_LOCAL_DEST") {
if let Some(repo_path) = state.clone().get_config("GIT_REPO_LOCAL_DEST") {
let rpath = repo_path.clone();
let repo = match Repository::open_bare(rpath) {
@ -192,12 +223,33 @@ async fn fetch_handler(State(state): State<AppState>) -> impl IntoResponse {
);
}
tokio::spawn(async move {
let _ = broadcast_need_update_new_data_after_fetch(state.clone()).await;
});
(
axum::http::StatusCode::OK,
Json(json!({"result": "fetch success"})),
)
}
// refetch data after request `/fetch`
async fn broadcast_need_update_new_data_after_fetch(
state: AppState,
) -> Result<(), Box<dyn std::error::Error>> {
// clear out saved state, user must request again
// state.fetched.clear(); // ----> background fetch into redis
//
//
let state_cl = state.clone();
let mut rcli = state_cl.redis.try_lock()?;
for s in state.fetched {
let _ = rcli.publish("recipe_repo_news", s);
}
Ok(())
}
// { path: "/path/to/file", signature: {
// username: "", email: ""
// }, patch_key: "edit_id_from_redis"}
@ -382,7 +434,7 @@ async fn apply_patch_to_file(
}
}
match serde_json::to_string(&current_full_map.clone()) {
match serde_json::to_string_pretty(&current_full_map.clone()) {
Ok(ss) => ss,
Err(_) => String::new(),
}
@ -521,6 +573,7 @@ pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
config.get("REDIS_URI").unwrap_or(&"".to_string()),
config.get("REDIS_PORT").unwrap_or(&"".to_string())
))?)),
fetched: Vec::new(),
};
let app = Router::new()

View file

@ -4,7 +4,10 @@ use env_logger::Builder;
use libtbr::recipe_functions::common;
use log::info;
use crate::{git::setup_git_repo, reg::{heartbeat_loop, registry::registry_client::RegistryClient}};
use crate::{
git::setup_git_repo,
reg::{heartbeat_loop, registry::registry_client::RegistryClient},
};
mod app;
mod gcm;
@ -12,49 +15,60 @@ mod git;
mod reg;
fn setup_log(config: gcm::Configure) -> gcm::StandardResult {
let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?;
let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?;
Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
.target(env_logger::Target::Pipe(Box::new(logfile))).init();
Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
.target(env_logger::Target::Pipe(Box::new(logfile)))
.init();
Ok(())
Ok(())
}
async fn setup_registry(config: gcm::Configure) -> gcm::StandardResult {
let cfg_clone = config.clone();
let register_server_address = cfg_clone.get("REGISTRY_SERVER").expect("not found registry server");
let name = cfg_clone.get("SERVICE_NAME").expect("missing service name");
let url = cfg_clone.get("SERVICE_URL").expect("missing service url");
let cfg_clone = config.clone();
let register_server_address = cfg_clone
.get("REGISTRY_SERVER")
.expect("not found registry server");
let name = cfg_clone.get("SERVICE_NAME").expect("missing service name");
let url = cfg_clone.get("SERVICE_URL").expect("missing service url");
let token = cfg_clone.get("REGISTRY_TOKEN").expect("token not provided");
unsafe {
env::set_var("REGISTRY_TOKEN", token);
}
let token = cfg_clone.get("REGISTRY_TOKEN").expect("token not provided");
unsafe {
env::set_var("REGISTRY_TOKEN", token);
}
let mut client = RegistryClient::connect(register_server_address.clone()).await?;
reg::register_service(&mut client, name, url).await;
let mut client = RegistryClient::connect(register_server_address.clone()).await?;
reg::register_service(&mut client, name, url).await;
tokio::spawn(heartbeat_loop(name.to_string(), url.to_string()));
tokio::spawn(heartbeat_loop(name.to_string(), url.to_string()));
Ok(())
Ok(())
}
#[tokio::main]
async fn main() -> gcm::StandardResult {
let config: gcm::Configure = common::get_config();
setup_log(config.clone())?;
match std::fs::read_dir(config.get("GIT_REPO_LOCAL_DEST").expect("not exist")) {
Ok(_) => {
info!("GIT_REPO `{dest}` already setup", dest = config.get("GIT_REPO_LOCAL_DEST").unwrap())
},
Err(_) => {
setup_git_repo(config.clone())?;
}
Ok(_) => {
info!(
"GIT_REPO `{dest}` already setup",
dest = config.get("GIT_REPO_LOCAL_DEST").unwrap()
)
}
Err(_) => {
setup_git_repo(config.clone())?;
}
}
info!(
"APP VERSION: {}",
config
.get("CONFIG_VERSION")
.expect("config version not defined")
);
setup_registry(config.clone()).await?;
app::run(config.clone()).await?;