cont
This commit is contained in:
parent
84e14fedbc
commit
dbbf5c5ef0
|
|
@ -37,6 +37,7 @@ dependencies = [
|
||||||
"rust_xlsxwriter",
|
"rust_xlsxwriter",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@ rust_xlsxwriter = "0.66"
|
||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
zeroize = "1.5"
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -5,7 +5,7 @@
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
Rust utility to fetch product data from Antares B2B and export to Excel.
|
Rust utility to fetch product data from Antares B2B and export to Excel which can be imported to Octopus 8.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -22,9 +22,10 @@ cargo build --release
|
||||||
|
|
||||||
`.env` file:
|
`.env` file:
|
||||||
```env
|
```env
|
||||||
ANTARES_USERCODE=your_usercode
|
URL=https://b2b.antares.hu/YOUR_BASE_URI_HERE
|
||||||
ANTARES_PASSWORD=your_password_hash
|
ANTARES_USERCODE=given_usercode
|
||||||
OUT=out
|
ANTARES_PASSWORD=given_password
|
||||||
|
OUT=out\\test.xlsx
|
||||||
```
|
```
|
||||||
|
|
||||||
`OUT` can be:
|
`OUT` can be:
|
||||||
|
|
@ -68,4 +69,4 @@ cargo test
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License © 2026 Orink Hungary Kft.
|
MIT [License](./LICENSE) © 2026 Orink Hungary Kft.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
pub fn make_url(base: &str, usercode: &str, password: &str, cikkszam: &str) -> String {
|
use crate::template::antares::AntaresLogin;
|
||||||
|
|
||||||
|
pub fn make_url(login: AntaresLogin) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}&USERCODE={}&PASSWORD={}&PIN=&cikkszam={}",
|
"{}&USERCODE={}&PASSWORD={}&PIN=&cikkszam=",
|
||||||
base, usercode, password, cikkszam
|
login.url,
|
||||||
|
login.usercode,
|
||||||
|
login.password.as_str()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
106
src/main.rs
106
src/main.rs
|
|
@ -2,24 +2,22 @@ mod api;
|
||||||
mod template;
|
mod template;
|
||||||
mod tools;
|
mod tools;
|
||||||
|
|
||||||
use reqwest::blocking::Client;
|
use zeroize::Zeroizing;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fs,
|
fs,
|
||||||
time::Duration,
|
|
||||||
io::{
|
io::{
|
||||||
Error as IO_Error,
|
Error as IO_Error,
|
||||||
ErrorKind::{
|
ErrorKind::Other
|
||||||
Other,
|
},
|
||||||
ConnectionRefused
|
path::PathBuf
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
use api::make_url;
|
use api::make_url;
|
||||||
use template::antares::Antares;
|
use template::antares::{AntaresLogin, Antares};
|
||||||
use tools::{
|
use tools::{
|
||||||
Logger,
|
Logger,
|
||||||
get_env,
|
get_env,
|
||||||
|
make_request,
|
||||||
export_to_excel
|
export_to_excel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -28,68 +26,41 @@ const USERCODE_KEY: &str = "ANTARES_USERCODE";
|
||||||
const PASSWORD_KEY: &str = "ANTARES_PASSWORD";
|
const PASSWORD_KEY: &str = "ANTARES_PASSWORD";
|
||||||
const OUT_KEY: &str = "OUT";
|
const OUT_KEY: &str = "OUT";
|
||||||
|
|
||||||
fn make_request(url: &str, logger: &Logger) -> Result<String, Box<dyn Error>> {
|
|
||||||
let client = Client::builder()
|
|
||||||
.timeout(Duration::from_secs(300))
|
|
||||||
.build().map_err(|e| {
|
|
||||||
logger.log_error(&format!("Failed to create HTTP client: {}", e));
|
|
||||||
Box::new(IO_Error::new(Other, e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let response = client.get(url).send().map_err(|e| {
|
|
||||||
logger.log_error(&format!("HTTP request failed: {}", e));
|
|
||||||
Box::new(IO_Error::new(ConnectionRefused, e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if response.status().is_success() {
|
|
||||||
Ok(response.text().map_err(|e| {
|
|
||||||
logger.log_error(&format!("Failed to read response body: {}", e));
|
|
||||||
Box::new(IO_Error::new(Other, e.to_string()))
|
|
||||||
})?)
|
|
||||||
} else {
|
|
||||||
let error_msg = format!("HTTP error: {:?}", response.status());
|
|
||||||
logger.log_error(&error_msg);
|
|
||||||
Err(Box::new(IO_Error::new(ConnectionRefused, error_msg)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let logger = Logger::init()?;
|
let logger = Logger::init()?;
|
||||||
logger.log_info("Starting Antares data export");
|
logger.log("Starting Antares data export");
|
||||||
|
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
let usercode = get_env(USERCODE_KEY)
|
let antares_login = AntaresLogin{
|
||||||
|
url: get_env(URL_KEY)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
logger.log_error(&e);
|
logger.log(&e);
|
||||||
e
|
e
|
||||||
})?;
|
})?,
|
||||||
|
usercode: get_env(USERCODE_KEY)
|
||||||
let password = get_env(PASSWORD_KEY)
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
logger.log_error(&e);
|
logger.log(&e);
|
||||||
e
|
e
|
||||||
})?;
|
})?,
|
||||||
|
password: Zeroizing::new(
|
||||||
|
get_env(PASSWORD_KEY)
|
||||||
|
.map_err(|e| {
|
||||||
|
logger.log(&e);
|
||||||
|
e
|
||||||
|
})?
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let out_path = get_env(OUT_KEY)
|
let out_path = get_env(OUT_KEY)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
logger.log_error(&e);
|
logger.log(&e);
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let url = get_env(URL_KEY)
|
let response = match make_request(&make_url(antares_login), &logger) {
|
||||||
.map_err(|e| {
|
|
||||||
logger.log_error(&e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let cikkszam = ""; // supply a value if needed;
|
|
||||||
|
|
||||||
let url = make_url(&url, &usercode, &password, cikkszam);
|
|
||||||
|
|
||||||
let response = match make_request(&url, &logger) {
|
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
logger.log_info("API request sent successfully");
|
logger.log("API request sent successfully");
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -98,10 +69,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure `temp/` exists and prepare path for `antares.json`.
|
// Ensure `temp/` exists and prepare path for `antares.json`.
|
||||||
let temp_dir = "temp";
|
let temp_dir = PathBuf::from("temp");
|
||||||
let antares_file = format!("{}/antares.json", temp_dir);
|
let antares_file = PathBuf::from(temp_dir.clone())
|
||||||
fs::create_dir_all(temp_dir).map_err(|e| {
|
.join("antares.json");
|
||||||
logger.log_error(&format!("Failed to create temp directory '{}': {}", temp_dir, e));
|
fs::create_dir_all(&temp_dir).map_err(|e| {
|
||||||
|
logger.log(&format!("Failed to create temp directory '{:#?}': {}", temp_dir, e));
|
||||||
Box::new(IO_Error::new(Other, e.to_string()))
|
Box::new(IO_Error::new(Other, e.to_string()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -112,12 +84,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let pretty = match serde_json::to_string_pretty(&items) {
|
let pretty = match serde_json::to_string_pretty(&items) {
|
||||||
Ok(pretty) => pretty,
|
Ok(pretty) => pretty,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logger.log_error(&format!("Failed to serialize items: {}", e));
|
logger.log(&format!("Failed to serialize items: {}", e));
|
||||||
return Err(Box::new(e));
|
return Err(Box::new(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fs::write(&antares_file, &pretty).map_err(|e| {
|
fs::write(&antares_file, &pretty).map_err(|e| {
|
||||||
logger.log_error(&format!("Failed to write {}: {}", antares_file, e));
|
logger.log(&format!("Failed to write {:#?}: {}", antares_file, e));
|
||||||
Box::new(IO_Error::new(Other, e.to_string()))
|
Box::new(IO_Error::new(Other, e.to_string()))
|
||||||
})?;
|
})?;
|
||||||
logger.log(&format!("API call successful - Fetched {} items", items.len()));
|
logger.log(&format!("API call successful - Fetched {} items", items.len()));
|
||||||
|
|
@ -129,21 +101,21 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let pretty = match serde_json::to_string_pretty(&json_val) {
|
let pretty = match serde_json::to_string_pretty(&json_val) {
|
||||||
Ok(pretty) => pretty,
|
Ok(pretty) => pretty,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logger.log_error(&format!("Failed to serialize JSON: {}", e));
|
logger.log(&format!("Failed to serialize JSON: {}", e));
|
||||||
return Err(Box::new(e));
|
return Err(Box::new(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fs::write(&antares_file, &pretty).map_err(|e| {
|
fs::write(&antares_file, &pretty).map_err(|e| {
|
||||||
logger.log_error(&format!("Failed to write {}: {}", antares_file, e));
|
logger.log(&format!("Failed to write {:#?}: {}", antares_file, e));
|
||||||
Box::new(IO_Error::new(Other, e.to_string()))
|
Box::new(IO_Error::new(Other, e.to_string()))
|
||||||
})?;
|
})?;
|
||||||
logger.log_info("Response saved as JSON (schema mismatch)");
|
logger.log("Response saved as JSON (schema mismatch)");
|
||||||
} else {
|
} else {
|
||||||
fs::write(&antares_file, &response).map_err(|e| {
|
fs::write(&antares_file, &response).map_err(|e| {
|
||||||
logger.log_error(&format!("Failed to write {}: {}", antares_file, e));
|
logger.log(&format!("Failed to write {:#?}: {}", antares_file, e));
|
||||||
Box::new(IO_Error::new(Other, e.to_string()))
|
Box::new(IO_Error::new(Other, e.to_string()))
|
||||||
})?;
|
})?;
|
||||||
logger.log_info("Response saved as raw text (not valid JSON)");
|
logger.log("Response saved as raw text (not valid JSON)");
|
||||||
}
|
}
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
@ -155,11 +127,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
logger.log(&format!("Export successful - {} rows exported to {}", items.len(), out_path));
|
logger.log(&format!("Export successful - {} rows exported to {}", items.len(), out_path));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logger.log_error(&format!("Export failed: {}", e));
|
logger.log(&format!("Export failed: {}", e));
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log_info("Export process completed successfully");
|
logger.log("Export process completed successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
//! Generated structs for Antares JSON response
|
//! Generated structs for Antares JSON response
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AntaresLogin {
|
||||||
|
pub url: String,
|
||||||
|
pub usercode: String,
|
||||||
|
pub password: Zeroizing<String>
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
use std::fs;
|
// Logger module
|
||||||
use std::io::Write;
|
use std::{
|
||||||
use std::path::PathBuf;
|
io::Result as IO_Result,
|
||||||
|
fs::{
|
||||||
|
OpenOptions,
|
||||||
|
create_dir_all
|
||||||
|
},
|
||||||
|
io::Write,
|
||||||
|
path::PathBuf
|
||||||
|
};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
|
||||||
pub struct Logger {
|
pub struct Logger {
|
||||||
|
|
@ -8,9 +15,9 @@ pub struct Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
pub fn init() -> std::io::Result<Self> {
|
pub fn init() -> IO_Result<Self> {
|
||||||
let log_dir = PathBuf::from("log");
|
let log_dir = PathBuf::from("log");
|
||||||
fs::create_dir_all(&log_dir)?;
|
create_dir_all(&log_dir)?;
|
||||||
|
|
||||||
let today = Local::now().format("%Y-%m-%d").to_string();
|
let today = Local::now().format("%Y-%m-%d").to_string();
|
||||||
let log_file = log_dir.join(format!("{}.log", today));
|
let log_file = log_dir.join(format!("{}.log", today));
|
||||||
|
|
@ -24,7 +31,7 @@ impl Logger {
|
||||||
|
|
||||||
println!("{}", formatted);
|
println!("{}", formatted);
|
||||||
|
|
||||||
if let Ok(mut file) = fs::OpenOptions::new()
|
if let Ok(mut file) = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(&self.log_file)
|
.open(&self.log_file)
|
||||||
|
|
@ -32,12 +39,4 @@ impl Logger {
|
||||||
let _ = writeln!(file, "{}", formatted);
|
let _ = writeln!(file, "{}", formatted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_info(&self, message: &str) {
|
|
||||||
self.log(&format!("ℹ {}", message));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_error(&self, error: &str) {
|
|
||||||
self.log(&format!("✗ Error: {}", error));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
pub mod excel;
|
pub mod excel;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod env;
|
pub mod env;
|
||||||
|
pub mod request;
|
||||||
|
|
||||||
pub use excel::export_to_excel;
|
|
||||||
pub use logger::Logger;
|
pub use logger::Logger;
|
||||||
pub use env::get_env;
|
pub use env::get_env;
|
||||||
|
pub use request::make_request;
|
||||||
|
pub use excel::export_to_excel;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Request module
|
||||||
|
use crate::tools::Logger;
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
time::Duration,
|
||||||
|
io::{
|
||||||
|
Error as IO_Error, ErrorKind::{
|
||||||
|
ConnectionRefused, Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
use reqwest::blocking::Client;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_client() -> Result<Client, reqwest::Error> {
|
||||||
|
Client::builder()
|
||||||
|
.timeout(Duration::from_secs(600))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn make_request(url: &str, logger: &Logger) -> Result<String, Box<dyn Error>> {
|
||||||
|
let client = get_client()
|
||||||
|
.map_err(|e| {
|
||||||
|
logger.log(&format!("Failed to create HTTP client: {}", e));
|
||||||
|
Box::new(IO_Error::new(Other, e.to_string()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = client.get(url)
|
||||||
|
.send()
|
||||||
|
.map_err(|e| {
|
||||||
|
logger.log(&format!("HTTP request failed: {}", e));
|
||||||
|
Box::new(IO_Error::new(ConnectionRefused, e.to_string()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
Ok(response.text().map_err(|e| {
|
||||||
|
logger.log(&format!("Failed to read response body: {}", e));
|
||||||
|
Box::new(IO_Error::new(ConnectionRefused, e.to_string()))
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
let error_msg = format!("HTTP error: {:?}", response.status());
|
||||||
|
logger.log(&error_msg);
|
||||||
|
Err(Box::new(IO_Error::new(ConnectionRefused, error_msg)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue