feat: add commit, push handler
- add new feature commit changes and push to remote Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
2dd165b451
commit
3043f30012
4 changed files with 667 additions and 231 deletions
657
src/app.rs
657
src/app.rs
|
|
@ -1,197 +1,546 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use axum::{extract::{Query, State}, response::IntoResponse, routing::get, Json, Router};
|
||||
use git2::{Repository, RemoteCallbacks, FetchOptions, Cred};
|
||||
use log::{error, warn};
|
||||
use libtbr::recipe_functions::common;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Query, State},
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
};
|
||||
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_json::{Value, json};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{gcm, reg};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
cached_country_names: Vec<&'static str>,
|
||||
configures: gcm::Configure
|
||||
// cached_country_names: Vec<&'static str>,
|
||||
configures: gcm::Configure,
|
||||
repo: Arc<Mutex<Repository>>,
|
||||
redis: Arc<Mutex<redis::Client>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn check_country_existed(&self, req: &str) -> bool {
|
||||
self.cached_country_names.contains(&req)
|
||||
}
|
||||
// pub fn check_country_existed(&self, req: &str) -> bool {
|
||||
// self.cached_country_names.contains(&req)
|
||||
// }
|
||||
|
||||
pub fn get_config(&self, config_name: &str) -> Option<&String> {
|
||||
self.configures.get(config_name)
|
||||
}
|
||||
pub fn get_config(&self, config_name: &str) -> Option<&String> {
|
||||
self.configures.get(config_name)
|
||||
}
|
||||
|
||||
pub fn get_all_configures(self) -> gcm::Configure {
|
||||
self.configures.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CheckoutParams {
|
||||
path: String
|
||||
path: String,
|
||||
}
|
||||
|
||||
async fn checkout_handler(State(state): State<AppState>, Query(param): Query<CheckoutParams>) -> impl IntoResponse {
|
||||
let mut response: HashMap<String, Value> = HashMap::new();
|
||||
async fn checkout_handler(
|
||||
State(state): State<AppState>,
|
||||
Query(param): Query<CheckoutParams>,
|
||||
) -> impl IntoResponse {
|
||||
let mut response: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
let repo_path = state.get_config("GIT_REPO_LOCAL_DEST");
|
||||
if repo_path.is_none() {
|
||||
response.insert("error".to_string(), json!("config repo dest not found".to_string()));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response))
|
||||
);
|
||||
}
|
||||
|
||||
let legit_path = param.path.as_str();
|
||||
|
||||
// match param.path.as_str() {
|
||||
// legit_path if param.path.contains("/") || state.check_country_existed(param.path.as_str()) || param.path.is_empty() => {
|
||||
|
||||
// }
|
||||
// _ => {
|
||||
// let error_log = "requested path is unexpected";
|
||||
// error!("{error_log}");
|
||||
// response.insert("error".to_string(), json!(error_log));
|
||||
// }
|
||||
// }
|
||||
let rpath = repo_path.unwrap().clone();
|
||||
let repo = match Repository::open_bare(rpath) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
let error_log = "unable to open repo as bare";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response))
|
||||
);
|
||||
let repo_path = state.get_config("GIT_REPO_LOCAL_DEST");
|
||||
if repo_path.is_none() {
|
||||
response.insert(
|
||||
"error".to_string(),
|
||||
json!("config repo dest not found".to_string()),
|
||||
);
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let fpath = format!("master:{}", legit_path);
|
||||
let obj = match repo.revparse_single(&fpath){
|
||||
Ok(obj) => obj,
|
||||
Err(e) => {
|
||||
let error_log = format!("unexpected revparse single: {err}", err = e.message());
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log.clone()));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response))
|
||||
);
|
||||
}
|
||||
};
|
||||
let legit_path = param.path.as_str();
|
||||
|
||||
if let Some(blob) = obj.as_blob() {
|
||||
let content = unsafe {
|
||||
str::from_utf8_unchecked(blob.content())
|
||||
// match param.path.as_str() {
|
||||
// legit_path if param.path.contains("/") || state.check_country_existed(param.path.as_str()) || param.path.is_empty() => {
|
||||
|
||||
// }
|
||||
// _ => {
|
||||
// let error_log = "requested path is unexpected";
|
||||
// error!("{error_log}");
|
||||
// response.insert("error".to_string(), json!(error_log));
|
||||
// }
|
||||
// }
|
||||
let rpath = repo_path.unwrap().clone();
|
||||
let repo = match Repository::open_bare(rpath) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
let error_log = "unable to open repo as bare";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response)),
|
||||
);
|
||||
}
|
||||
};
|
||||
response.insert("result".to_string(), json!(content.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));
|
||||
} else {
|
||||
let error_log = "not obj nor tree";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
Json(json!(response))
|
||||
)
|
||||
}
|
||||
|
||||
(
|
||||
axum::http::StatusCode::OK,
|
||||
Json(json!(response))
|
||||
)
|
||||
let fpath = format!("master:{}", legit_path);
|
||||
let obj = match repo.revparse_single(&fpath) {
|
||||
Ok(obj) => obj,
|
||||
Err(e) => {
|
||||
let error_log = format!("unexpected revparse single: {err}", err = e.message());
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log.clone()));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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()));
|
||||
} 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));
|
||||
} else {
|
||||
let error_log = "not obj nor tree";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (axum::http::StatusCode::BAD_REQUEST, Json(json!(response)));
|
||||
}
|
||||
|
||||
(axum::http::StatusCode::OK, Json(json!(response)))
|
||||
}
|
||||
|
||||
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") {
|
||||
let rpath = repo_path.clone();
|
||||
let mut response: HashMap<String, Value> = HashMap::new();
|
||||
if let Some(repo_path) = state.get_config("GIT_REPO_LOCAL_DEST") {
|
||||
let rpath = repo_path.clone();
|
||||
|
||||
let repo = match Repository::open_bare(rpath) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
let error_log = "unable to open bare repo";
|
||||
let repo = match Repository::open_bare(rpath) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
let error_log = "unable to open bare repo";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let mut remote = match repo.find_remote("origin") {
|
||||
Ok(remote) => remote,
|
||||
Err(e) => {
|
||||
let error_log = format!("unable to find remote, {}", e.message());
|
||||
error!("{error_log}");
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if state.get_config("GIT_REPO_USERNAME").is_none()
|
||||
|| state.get_config("GIT_REPO_PASSWORD").is_none()
|
||||
{
|
||||
warn!("username or password not provided may cause fetching fail");
|
||||
}
|
||||
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.credentials(|_, _, _| {
|
||||
Cred::userpass_plaintext(
|
||||
state
|
||||
.get_config("GIT_REPO_USERNAME")
|
||||
.unwrap_or(&"".to_string()),
|
||||
state
|
||||
.get_config("GIT_REPO_PASSWORD")
|
||||
.unwrap_or(&"".to_string()),
|
||||
)
|
||||
});
|
||||
|
||||
let mut fetch_options = FetchOptions::new();
|
||||
fetch_options.remote_callbacks(callbacks);
|
||||
|
||||
match remote.fetch(&["origin"], Some(&mut fetch_options), None) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("error while fetching {}", e.message());
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": format!("error while fetching {}", e.message())})),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let error_log = "cannot find local repo";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response))
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let mut remote = match repo.find_remote("origin") {
|
||||
Ok(remote) => remote,
|
||||
Err(e) => {
|
||||
let error_log = format!("unable to find remote, {}", e.message());
|
||||
error!("{error_log}");
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if state.get_config("GIT_REPO_USERNAME").is_none() || state.get_config("GIT_REPO_PASSWORD").is_none() {
|
||||
warn!("username or password not provided may cause fetching fail");
|
||||
}
|
||||
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.credentials(|_, _, _| {
|
||||
Cred::userpass_plaintext(
|
||||
state.get_config("GIT_REPO_USERNAME").unwrap_or(&"".to_string()),
|
||||
state.get_config("GIT_REPO_PASSWORD").unwrap_or(&"".to_string())
|
||||
)
|
||||
});
|
||||
(
|
||||
axum::http::StatusCode::OK,
|
||||
Json(json!({"result": "fetch success"})),
|
||||
)
|
||||
}
|
||||
|
||||
let mut fetch_options = FetchOptions::new();
|
||||
fetch_options.remote_callbacks(callbacks);
|
||||
// { path: "/path/to/file", signature: {
|
||||
// username: "", email: ""
|
||||
// }, patch_key: "edit_id_from_redis"}
|
||||
#[derive(Deserialize)]
|
||||
struct CommitBody {
|
||||
// Actual file path
|
||||
path: String,
|
||||
// Signature of user
|
||||
signature: Signature,
|
||||
// Key to grep content changes from redis
|
||||
patch_key: String,
|
||||
// user message
|
||||
message: Option<String>,
|
||||
#[serde(flatten)]
|
||||
extra: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
match remote.fetch(&["origin"], Some(&mut fetch_options), None) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("error while fetching {}", e.message());
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": format!("error while fetching {}", e.message())}))
|
||||
);
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Signature {
|
||||
username: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
async fn commit_handler(
|
||||
State(state): State<AppState>,
|
||||
// request body
|
||||
Json(payload): Json<CommitBody>,
|
||||
) -> impl IntoResponse {
|
||||
let mut content = match fetch_content_from_redis(state.redis.clone(), &payload.patch_key).await
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
Json(json!({"error": e})),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let is_patch_file = content.starts_with("patch");
|
||||
// do apply patch first
|
||||
if is_patch_file {
|
||||
content = apply_patch_to_file(state.redis.clone(), &payload.path, &mut content).await;
|
||||
}
|
||||
} else {
|
||||
let error_log = "cannot find local repo";
|
||||
error!("{error_log}");
|
||||
response.insert("error".to_string(), json!(error_log));
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!(response))
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
axum::http::StatusCode::OK,
|
||||
Json(json!({"result": "fetch success"}))
|
||||
)
|
||||
let commit_oid = match commit_file_content(
|
||||
state.repo,
|
||||
&payload.path,
|
||||
&content.as_bytes(),
|
||||
payload.signature,
|
||||
&payload.message.unwrap_or("update: from api".to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(oid) => oid,
|
||||
Err(e) => {
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": e.to_string()})),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// save history
|
||||
let redis_pre_lock = state.redis.clone();
|
||||
{
|
||||
if let Ok(mut rl) = redis_pre_lock.try_lock() {
|
||||
let _ = rl.rpush(format!("{}.history", payload.path), payload.patch_key);
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
axum::http::StatusCode::OK,
|
||||
Json(json!({"result": format!("{commit_oid}")})),
|
||||
)
|
||||
}
|
||||
|
||||
async fn fetch_content_from_redis(
|
||||
redis: Arc<Mutex<redis::Client>>,
|
||||
key: &str,
|
||||
) -> Result<String, String> {
|
||||
match redis.lock().await.get(key) {
|
||||
Ok(s) => {
|
||||
if let Some(res) = s {
|
||||
Ok(res)
|
||||
} else {
|
||||
Err(format!("result error from key: {key}"))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(format!("redis get failed: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_patch_from_str(patch_changes: &str) -> Option<Value> {
|
||||
//
|
||||
|
||||
let pv = patch_changes.replace("patch ", "");
|
||||
|
||||
let change_map: Value = match serde_json::from_str(&pv) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return None,
|
||||
};
|
||||
Some(change_map)
|
||||
}
|
||||
|
||||
fn get_product_code(v: &Value) -> Option<&str> {
|
||||
v.get("productCode")?.as_str()
|
||||
}
|
||||
|
||||
fn diff_apply(target: &mut Value, patch: &Value) -> Result<(), String> {
|
||||
match (target, patch) {
|
||||
(Value::Object(target_map), Value::Object(patch_map)) => {
|
||||
for (k, v_patch) in patch_map {
|
||||
let v_target = target_map
|
||||
.get_mut(k)
|
||||
.ok_or_else(|| format!("Unknown key in patch: {k}"))?;
|
||||
|
||||
diff_apply(v_target, v_patch)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
(Value::Array(target_arr), Value::Array(patch_arr)) => {
|
||||
for patch_elem in patch_arr {
|
||||
// NOTE: support only `Recipe01`
|
||||
let patch_id = get_product_code(patch_elem)
|
||||
.ok_or("Patch array element missing product code")?;
|
||||
let target_elem = target_arr
|
||||
.iter_mut()
|
||||
.find(|e| get_product_code(e) == Some(patch_id))
|
||||
.ok_or_else(|| format!("Unknown id in patch array: {patch_id}"))?;
|
||||
|
||||
diff_apply(target_elem, patch_elem)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
(target_slot, patch_value) => {
|
||||
*target_slot = patch_value.clone();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_patch_to_file(
|
||||
redis: Arc<Mutex<redis::Client>>,
|
||||
path: &str,
|
||||
patch_changes: &mut String,
|
||||
) -> String {
|
||||
use libtbr::*;
|
||||
// expect the path to already has in redis
|
||||
let full_file = match fetch_content_from_redis(redis, path).await {
|
||||
Ok(f) => f,
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
|
||||
if full_file.is_empty() {
|
||||
return full_file;
|
||||
}
|
||||
|
||||
// read into struct
|
||||
//
|
||||
let full_recipe: models::recipe::Recipe = match serde_json::from_str(&full_file) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return String::new(),
|
||||
};
|
||||
|
||||
// read patch
|
||||
let patch_map = read_patch_from_str(patch_changes);
|
||||
let mut current_full_map = match serde_json::to_value(full_recipe.clone()) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return String::new(),
|
||||
};
|
||||
|
||||
if let Some(pm) = patch_map {
|
||||
match diff_apply(&mut current_full_map, &pm) {
|
||||
Ok(_) => {}
|
||||
Err(x) => {
|
||||
error!("error while applied patch: {x}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match serde_json::to_string(¤t_full_map.clone()) {
|
||||
Ok(ss) => ss,
|
||||
Err(_) => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn commit_file_content(
|
||||
repo: Arc<Mutex<Repository>>,
|
||||
path: &str,
|
||||
content: &[u8],
|
||||
author: Signature,
|
||||
message: &str,
|
||||
) -> Result<git2::Oid, Box<dyn std::error::Error>> {
|
||||
let repo_clone = repo.clone();
|
||||
let blob_oid = repo_clone.lock().await.blob(content)?;
|
||||
info!("blob oid: {blob_oid}");
|
||||
let mut index = repo_clone.lock().await.index()?;
|
||||
info!("index pass");
|
||||
let rlock = repo_clone.try_lock()?;
|
||||
let parent = match rlock.head() {
|
||||
Ok(head) => {
|
||||
let commit = head.peel_to_commit()?;
|
||||
index.read_tree(&commit.tree()?)?;
|
||||
Some(commit.clone())
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
info!("parent pass");
|
||||
|
||||
index.add(&git2::IndexEntry {
|
||||
ctime: git2::IndexTime::new(0, 0),
|
||||
mtime: git2::IndexTime::new(0, 0),
|
||||
dev: 0,
|
||||
ino: 0,
|
||||
mode: 0o100644,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
file_size: content.len() as u32,
|
||||
id: blob_oid,
|
||||
flags: 0,
|
||||
flags_extended: 0,
|
||||
path: path.as_bytes().to_vec(),
|
||||
})?;
|
||||
|
||||
info!("index added");
|
||||
|
||||
let tree_oid = index.write_tree()?;
|
||||
|
||||
info!("write to tree");
|
||||
// let tlock = repo_clone.try_lock()?;
|
||||
info!("acquire try lock");
|
||||
let tree = rlock.find_tree(tree_oid)?;
|
||||
info!("find tree ok");
|
||||
|
||||
let sig = git2::Signature::now(&author.username, &author.email)?;
|
||||
info!("generated signature");
|
||||
|
||||
let parents: Vec<&git2::Commit> = parent.iter().collect();
|
||||
let oid = rlock.commit(
|
||||
Some("refs/heads/master"),
|
||||
&sig,
|
||||
&sig,
|
||||
message,
|
||||
&tree,
|
||||
&parents,
|
||||
)?;
|
||||
|
||||
info!("commit oid: {oid}");
|
||||
|
||||
Ok(oid)
|
||||
}
|
||||
|
||||
async fn push_handler(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let config = state.clone().get_all_configures();
|
||||
let repo = state.repo.clone();
|
||||
let remote_name = match state.get_config("GIT_REPO_REMOTE") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": "repo remote not existed"})),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let branch = "master";
|
||||
|
||||
if let Err(e) = push(config, repo, remote_name, branch) {
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": e.to_string()})),
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
axum::http::StatusCode::OK,
|
||||
Json(json!({"result": "push completed"})),
|
||||
)
|
||||
}
|
||||
|
||||
fn push(
|
||||
config: gcm::Configure,
|
||||
repo: Arc<Mutex<Repository>>,
|
||||
remote_name: &str,
|
||||
branch: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Ok(rlock) = repo.try_lock() {
|
||||
let mut rem = rlock.find_remote(remote_name)?;
|
||||
let mut callback = RemoteCallbacks::new();
|
||||
callback.credentials(|_url, _user, _allowed| {
|
||||
Cred::userpass_plaintext(
|
||||
config.get("GIT_REPO_USERNAME").unwrap_or(&"".to_string()),
|
||||
config.get("GIT_REPO_PASSWORD").unwrap_or(&"".to_string()),
|
||||
)
|
||||
});
|
||||
let mut push_opts = PushOptions::new();
|
||||
push_opts.remote_callbacks(callback);
|
||||
let refspec = format!("refs/heads/{0}:refs/heads/{0}", branch);
|
||||
rem.push(&[&refspec], Some(&mut push_opts))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err("cannot lock repo".into())
|
||||
}
|
||||
|
||||
pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
|
||||
let state = AppState {
|
||||
cached_country_names: common::valid_country_name(),
|
||||
configures: config.clone()
|
||||
};
|
||||
let state = AppState {
|
||||
// cached_country_names: common::valid_country_name(),
|
||||
configures: config.clone(),
|
||||
repo: Arc::new(Mutex::new(Repository::open_bare(
|
||||
config.get("GIT_REPO_LOCAL_DEST").unwrap_or(&"".to_string()),
|
||||
)?)),
|
||||
redis: Arc::new(Mutex::new(redis::Client::open(format!(
|
||||
"redis://{}:{}",
|
||||
config.get("REDIS_URI").unwrap_or(&"".to_string()),
|
||||
config.get("REDIS_PORT").unwrap_or(&"".to_string())
|
||||
))?)),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/checkout", get(checkout_handler))
|
||||
.route("/fetch", get(fetch_handler))
|
||||
.route("/healthz", get(reg::health))
|
||||
.with_state(state);
|
||||
let app = Router::new()
|
||||
.route("/checkout", get(checkout_handler))
|
||||
.route("/fetch", get(fetch_handler))
|
||||
.route("/commit", post(commit_handler))
|
||||
.route("/push", get(push_handler))
|
||||
.route("/healthz", get(reg::health))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.clone().get("PUBLIC_PORT").unwrap_or(&"36583".to_string()))).await?;
|
||||
let listener = tokio::net::TcpListener::bind(format!(
|
||||
"0.0.0.0:{}",
|
||||
config
|
||||
.clone()
|
||||
.get("PUBLIC_PORT")
|
||||
.unwrap_or(&"36583".to_string())
|
||||
))
|
||||
.await?;
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue