342 lines
12 KiB
Rust
342 lines
12 KiB
Rust
|
|
use crate::app::*;
|
||
|
|
use crate::stream::model::{
|
||
|
|
IntoStreamMessage, StreamDataChunk, StreamDataEnd, StreamDataExtra, StreamDataStart,
|
||
|
|
};
|
||
|
|
use crate::websocket::{core::*, helper::*, model::*};
|
||
|
|
|
||
|
|
use log::{debug, error, info, warn};
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
use std::collections::HashMap;
|
||
|
|
use std::{fs::File, io::Read, sync::Arc};
|
||
|
|
use tokio::sync::{Mutex, mpsc::Sender};
|
||
|
|
|
||
|
|
use crate::websocket::core::WebsocketMessageResult;
|
||
|
|
|
||
|
|
#[allow(non_snake_case)]
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
struct MenuPrice {
|
||
|
|
ProductCode: String,
|
||
|
|
NewPrice: serde_json::Value,
|
||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||
|
|
StringParam: Option<String>,
|
||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||
|
|
Discount: Option<serde_json::Value>,
|
||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||
|
|
Percent: Option<serde_json::Value>,
|
||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||
|
|
roundup: Option<bool>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
pub struct RecipePrice {
|
||
|
|
#[serde(flatten)]
|
||
|
|
metadata: serde_json::Value,
|
||
|
|
content: Vec<MenuPrice>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl RecipePrice {
|
||
|
|
pub const HIDE_PARAM: &str = "hide=true";
|
||
|
|
|
||
|
|
pub fn import(path: String) -> RecipePrice {
|
||
|
|
debug!("try import {path}");
|
||
|
|
let mut file = File::open(path).expect("file not found");
|
||
|
|
let mut data = String::new();
|
||
|
|
|
||
|
|
file.read_to_string(&mut data).unwrap();
|
||
|
|
|
||
|
|
let res: Result<RecipePrice, _> = serde_json::from_str(&data);
|
||
|
|
match res {
|
||
|
|
Ok(rp) => rp,
|
||
|
|
Err(e) => {
|
||
|
|
error!("error while deserialize price: {e}");
|
||
|
|
RecipePrice {
|
||
|
|
content: Vec::new(),
|
||
|
|
metadata: serde_json::Value::Null,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn import_from_raw_string(raw: String) -> RecipePrice {
|
||
|
|
let res: Result<RecipePrice, _> = serde_json::from_str(&raw);
|
||
|
|
match res {
|
||
|
|
Ok(rp) => rp,
|
||
|
|
Err(e) => {
|
||
|
|
error!("error while deserialize price: {e}");
|
||
|
|
RecipePrice {
|
||
|
|
content: Vec::new(),
|
||
|
|
metadata: serde_json::Value::Null,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// only for getting data without modify
|
||
|
|
pub fn find_by_pd(&self, pd: &str) -> Option<MenuPrice> {
|
||
|
|
self.content
|
||
|
|
.iter()
|
||
|
|
.find(|x| x.ProductCode.eq(pd))
|
||
|
|
.map(|x| x.clone())
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn modify_price_by_pd(&mut self, pd: &str, price: serde_json::Value) {
|
||
|
|
if let Some(mp) = self.content.iter_mut().find(|x| x.ProductCode.eq(pd)) {
|
||
|
|
mp.NewPrice = price;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn modify_string_param_by_pd(&mut self, pd: &str, param: &str) {
|
||
|
|
if let Some(mp) = self.content.iter_mut().find(|x| x.ProductCode.eq(pd)) {
|
||
|
|
mp.StringParam = Some(param.to_string());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn set_visibility_by_pd(&mut self, pd: &str, show: bool) {
|
||
|
|
if !show {
|
||
|
|
self.modify_string_param_by_pd(pd, RecipePrice::HIDE_PARAM);
|
||
|
|
} else {
|
||
|
|
self.modify_string_param_by_pd(pd, "");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// NOTE: disable write to file
|
||
|
|
// pub fn export_to_json_file(self, outpath: Option<String>) {
|
||
|
|
// let json = serde_json::to_string(&self).unwrap();
|
||
|
|
// let json2: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||
|
|
|
||
|
|
// if let Some(outpath) = outpath {
|
||
|
|
// let writer = File::create(outpath).unwrap();
|
||
|
|
// let _ = serde_json::to_writer_pretty(writer, &json2);
|
||
|
|
// } else {
|
||
|
|
// println!("Default save to (execute)/price.json");
|
||
|
|
// let writer = File::create("price.json").unwrap();
|
||
|
|
// let _ = serde_json::to_writer_pretty(writer, &json2);
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get list of price
|
||
|
|
// pub async fn handle_price_list_request(
|
||
|
|
// config: DevConfig,
|
||
|
|
// redis: redis::Client,
|
||
|
|
// tx: Sender<TxControlMessage>,
|
||
|
|
// req: WebsocketMessageRequest,
|
||
|
|
// uid_clone: Arc<Mutex<String>>,
|
||
|
|
// ) -> WebsocketMessageResult {
|
||
|
|
|
||
|
|
// }
|
||
|
|
|
||
|
|
/// Get main price profile of country
|
||
|
|
pub async fn handle_price_request(
|
||
|
|
config: DevConfig,
|
||
|
|
redis: redis::Client,
|
||
|
|
tx: Sender<TxControlMessage>,
|
||
|
|
req: WebsocketMessageRequest,
|
||
|
|
uid_clone: Arc<Mutex<String>>,
|
||
|
|
) -> WebsocketMessageResult {
|
||
|
|
let p = req.payload.unwrap();
|
||
|
|
|
||
|
|
let price_param: PriceRequestPayload = serde_json::from_value(p)?;
|
||
|
|
|
||
|
|
let mut price_file_format = format!(
|
||
|
|
"{}/profile_{}_master.json",
|
||
|
|
price_param.country,
|
||
|
|
price_param.country.to_uppercase()
|
||
|
|
);
|
||
|
|
|
||
|
|
if let Some(override_file) = price_param.override_file {
|
||
|
|
price_file_format = override_file;
|
||
|
|
}
|
||
|
|
|
||
|
|
let price_action = price_param.action;
|
||
|
|
|
||
|
|
let price_content =
|
||
|
|
match invoke_checkout_request(config.clone(), price_file_format.clone()).await {
|
||
|
|
Ok(pc) => pc,
|
||
|
|
Err(e) => return Err(format!("Cannot find price of expected country: {e:?}").into()),
|
||
|
|
};
|
||
|
|
|
||
|
|
info!("price content len: {}", price_content.len());
|
||
|
|
|
||
|
|
let mut rpp = RecipePrice::import_from_raw_string(price_content);
|
||
|
|
let _uid = uid_clone.clone();
|
||
|
|
let uidd = _uid.try_lock().unwrap();
|
||
|
|
|
||
|
|
info!("price action: {price_action:?}");
|
||
|
|
|
||
|
|
let user_info = price_param.user_info.clone();
|
||
|
|
|
||
|
|
match price_action {
|
||
|
|
PriceRequestAction::View(view_opt) => {
|
||
|
|
let viewing_options: HashMap<String, String> = get_extra_parameters(view_opt);
|
||
|
|
// sa=all
|
||
|
|
// sa=get,pd=...
|
||
|
|
// sa=query,list=1|2|3
|
||
|
|
// sa=query,where=contain,kw=...
|
||
|
|
let sub_action = viewing_options.get("sa");
|
||
|
|
let pd = viewing_options.get("pd");
|
||
|
|
|
||
|
|
if let Some(sa) = sub_action {
|
||
|
|
let mut result = Vec::new();
|
||
|
|
let action_done = match sa.as_str() {
|
||
|
|
"all" => {
|
||
|
|
result = rpp.content;
|
||
|
|
|
||
|
|
true
|
||
|
|
}
|
||
|
|
"get"
|
||
|
|
if let Some(pd) = pd
|
||
|
|
&& let Some(mp) = rpp.find_by_pd(pd) =>
|
||
|
|
{
|
||
|
|
result.push(mp);
|
||
|
|
true
|
||
|
|
}
|
||
|
|
_ => false,
|
||
|
|
};
|
||
|
|
|
||
|
|
if action_done {
|
||
|
|
let _ = tx
|
||
|
|
.send(TxControlMessage::Payload(serde_json::json!({
|
||
|
|
"type": "price",
|
||
|
|
"payload": {
|
||
|
|
"req_action": sa,
|
||
|
|
"status": "ok",
|
||
|
|
"content": result,
|
||
|
|
"to": uidd.to_string()
|
||
|
|
}
|
||
|
|
})))
|
||
|
|
.await;
|
||
|
|
} else {
|
||
|
|
let _ = tx
|
||
|
|
.send(TxControlMessage::Payload(serde_json::json!({
|
||
|
|
"type": "price",
|
||
|
|
"payload": {
|
||
|
|
"req_action": sa,
|
||
|
|
"status": "fail",
|
||
|
|
"to": uidd.to_string()
|
||
|
|
}
|
||
|
|
})))
|
||
|
|
.await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
PriceRequestAction::Edit(edit_opt) => {
|
||
|
|
let editing_options: HashMap<String, String> = get_extra_parameters(edit_opt);
|
||
|
|
// sa=change,pd=...,to=...
|
||
|
|
// sa=hide,pd=...
|
||
|
|
// sa=disable,pd=... disable = hide
|
||
|
|
// sa=show,pd=...
|
||
|
|
// sa=toggle,pd=...,state=show|hide
|
||
|
|
let sub_action = editing_options.get("sa");
|
||
|
|
let pd = editing_options.get("pd");
|
||
|
|
let to = editing_options.get("to");
|
||
|
|
let state = editing_options.get("state");
|
||
|
|
|
||
|
|
if let Some(sa) = sub_action {
|
||
|
|
let mut action_message = String::new();
|
||
|
|
let action_done = match sa.as_str() {
|
||
|
|
"change"
|
||
|
|
if let Some(pd) = pd
|
||
|
|
&& let Some(to) = to
|
||
|
|
&& let Some(mp) = rpp.find_by_pd(pd.as_str()) =>
|
||
|
|
{
|
||
|
|
info!(
|
||
|
|
"[CHANGE] price of {pd} from {} to {to}",
|
||
|
|
mp.NewPrice.as_i64().unwrap()
|
||
|
|
);
|
||
|
|
action_message = format!(
|
||
|
|
"[CHANGE] price of {pd} from {} to {to}",
|
||
|
|
mp.NewPrice.as_i64().unwrap()
|
||
|
|
);
|
||
|
|
|
||
|
|
let price_int = to.parse::<i64>()?;
|
||
|
|
|
||
|
|
rpp.modify_price_by_pd(pd, serde_json::json!(price_int));
|
||
|
|
true
|
||
|
|
}
|
||
|
|
"toggle"
|
||
|
|
if let Some(pd) = pd
|
||
|
|
&& let Some(state) = state =>
|
||
|
|
{
|
||
|
|
info!("[TOGGLE] {pd} to {state}");
|
||
|
|
action_message = format!("[TOGGLE] {pd} to {state}");
|
||
|
|
|
||
|
|
match state.as_str() {
|
||
|
|
"show" => rpp.set_visibility_by_pd(pd.as_str(), true),
|
||
|
|
"hide" | "disable" => rpp.set_visibility_by_pd(pd.as_str(), false),
|
||
|
|
_ => {
|
||
|
|
warn!("unknown state toggle");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
true
|
||
|
|
}
|
||
|
|
_ => false,
|
||
|
|
};
|
||
|
|
|
||
|
|
if action_done {
|
||
|
|
let _ = tx
|
||
|
|
.send(TxControlMessage::Payload(serde_json::json!({
|
||
|
|
"type": "price",
|
||
|
|
"payload": {
|
||
|
|
"req_action": sa,
|
||
|
|
"status": "ok",
|
||
|
|
"to": uidd.to_string()
|
||
|
|
}
|
||
|
|
})))
|
||
|
|
.await;
|
||
|
|
|
||
|
|
// send save
|
||
|
|
//
|
||
|
|
|
||
|
|
let all_prices_str = serde_json::to_string_pretty(&rpp)?;
|
||
|
|
let commit_payload = CommitPayload {
|
||
|
|
file_bytes: all_prices_str.as_bytes().to_vec(),
|
||
|
|
path: price_file_format.clone(),
|
||
|
|
signature_username: user_info
|
||
|
|
.get("displayName")
|
||
|
|
.unwrap_or_default()
|
||
|
|
.as_str()
|
||
|
|
.unwrap_or(&"unknown".to_string())
|
||
|
|
.to_string(),
|
||
|
|
signature_email: user_info
|
||
|
|
.get("email")
|
||
|
|
.unwrap_or_default()
|
||
|
|
.as_str()
|
||
|
|
.unwrap_or(&"unknown".to_string())
|
||
|
|
.to_string(),
|
||
|
|
message: action_message,
|
||
|
|
};
|
||
|
|
|
||
|
|
if invoke_pull_sync_request(config.clone()).await.is_err() {
|
||
|
|
// backup
|
||
|
|
let _ = commit_payload.dump_backup();
|
||
|
|
return Err("Fail to sync repo, backing up ...".into());
|
||
|
|
}
|
||
|
|
|
||
|
|
let _ = invoke_commit_request(config.clone(), commit_payload.clone()).await;
|
||
|
|
|
||
|
|
if invoke_push_request(config.clone()).await.is_err() {
|
||
|
|
let _ = commit_payload.dump_backup();
|
||
|
|
return Err("Fail to push repo, backing up ...".into());
|
||
|
|
}
|
||
|
|
|
||
|
|
// push to git
|
||
|
|
} else {
|
||
|
|
let _ = tx
|
||
|
|
.send(TxControlMessage::Payload(serde_json::json!({
|
||
|
|
"type": "price",
|
||
|
|
"payload": {
|
||
|
|
"req_action": sa,
|
||
|
|
"status": "fail",
|
||
|
|
"to": uidd.to_string()
|
||
|
|
}
|
||
|
|
})))
|
||
|
|
.await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|