summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/instructions/frontend.instructions.md2
-rw-r--r--.github/instructions/rust-backend.instructions.md12
-rw-r--r--AGENTS.md11
-rw-r--r--CHANGELOG.md4
-rw-r--r--license.md1
-rw-r--r--readme.md11
-rw-r--r--rust_backend/.vscode/launch.json45
-rw-r--r--rust_backend/Cargo.toml18
-rw-r--r--rust_backend/src/eve_api.rs319
-rw-r--r--rust_backend/src/eve_service.rs42
-rw-r--r--rust_backend/src/main.rs95
-rw-r--r--rust_backend/src/manager.rs90
-rw-r--r--rust_backend/src/market/mod.rs3
-rw-r--r--rust_backend/src/market/order.rs83
-rw-r--r--rust_backend/src/market/trade.rs63
-rw-r--r--rust_backend/src/pathfinding/graph.rs7
-rw-r--r--rust_backend/src/pathfinding/mod.rs15
-rw-r--r--rust_backend/src/pathfinding/models.rs86
-rw-r--r--rust_backend/src/pathfinding/parser.rs46
-rw-r--r--rust_backend/src/route.rs108
20 files changed, 12 insertions, 1049 deletions
diff --git a/.github/instructions/frontend.instructions.md b/.github/instructions/frontend.instructions.md
index 7ce27c0..997d6c1 100644
--- a/.github/instructions/frontend.instructions.md
+++ b/.github/instructions/frontend.instructions.md
@@ -8,7 +8,7 @@ applyTo:
# Frontend Data Flow
- Keep data fetching in hooks or small container components. `Path.js`, `Info.js`, `Log.js`, and `Options.js` wire backend data into display components; `usePolling.js` and `useSSE.js` own subscription cleanup.
-- Preserve the current backend contract unless the task explicitly coordinates with backend changes. The frontend is hardcoded to `http://localhost:8000`, not the Rust backend on `:8080`.
+- Preserve the current backend contract unless the task explicitly coordinates with backend changes. The frontend is hardcoded to `http://localhost:8000`.
- Keep display components render-focused. Formatting, icons, animation, and simple click handlers belong near `DataDisplay.js`, `InfoDisplay.js`, and `LogComponent.js`; reusable network behavior belongs in hooks.
- Stick to the current stack: React function components, Tailwind utility classes, and local CSS files. Do not introduce a new state library, routing layer, or styling system unless asked.
- If you add polling or SSE behavior, always include cleanup and keep the return shape simple for the consuming component. \ No newline at end of file
diff --git a/.github/instructions/rust-backend.instructions.md b/.github/instructions/rust-backend.instructions.md
deleted file mode 100644
index 719544a..0000000
--- a/.github/instructions/rust-backend.instructions.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-description: "Use when editing the Rust backend, Actix handlers, EVE service client, or pathfinding and market modules under rust_backend/src. Covers experimental status, service boundaries, and integration gaps."
-name: "Rust Backend Boundaries"
-applyTo: "rust_backend/src/**/*.rs"
----
-# Rust Backend Boundaries
-
-- Treat `rust_backend/` as an experimental rewrite, not a drop-in replacement for the Python backend. The current frontend does not target it.
-- Keep `main.rs` as a thin Actix entrypoint. Shared state and polling belong in `manager.rs`; ESI and OAuth transport belongs in `eve_service.rs` and `eve_api.rs`.
-- Keep `pathfinding/` and `market/` self-contained. Avoid pulling HTTP concerns, UI assumptions, or Actix types into those modules.
-- Do not assume feature parity with the Python backend. If a task requires frontend integration, add the missing routes, payload shapes, and CORS deliberately rather than silently reusing Python assumptions.
-- Rust runtime work depends on local OAuth env vars (`CLIENT_ID`, `CLIENT_SECRET`, `CHARACTER_ID`, `REDIRECT_URL`) and `token.json`. Preserve that flow unless the task is specifically about changing auth setup. \ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index e01bd4e..0ba3b7e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,9 +2,8 @@
## Start Here
-- See [readme.md](readme.md) for product context and the Python/React/Rust split.
+- See [readme.md](readme.md) for product context and the Python/React split.
- Treat the Python backend as the active application backend. The React frontend is hardcoded to `http://localhost:8000`.
-- Treat `rust_backend/` as an experimental rewrite. It runs separately on `127.0.0.1:8080` and is not a drop-in replacement for the Python backend.
## Run and Validate
@@ -12,8 +11,6 @@
- Frontend tests: `cd frontend && npm test -- --watchAll=false`
- Frontend build: `cd frontend && npm run build`
- Python backend: `cd backend/src && python -m uvicorn main:app --reload --port 8000`
-- Rust backend: `cd rust_backend && cargo run`
-- Rust tests: `cd rust_backend && cargo test`
- The Python backend has no checked-in dependency manifest. Before running it, confirm the local environment already has the required packages plus the EVE auth/cache files and CSV map data under `backend/data/`.
## Architecture
@@ -22,12 +19,10 @@
- `backend/src/esi/` owns ESI auth, character location, waypoint updates, and other live API side effects.
- `backend/src/graph.py`, `loader.py`, `market.py`, `arbitrage.py`, `pathfinder.py`, and `route.py` form the route-planning and market-analysis pipeline.
- `frontend/src/` uses small function components. `usePolling.js` and `useSSE.js` own long-lived network subscriptions; display components render the returned payloads.
-- `rust_backend/src/main.rs` is a thin Actix entrypoint. `manager.rs` owns shared state and polling, `eve_service.rs` wraps `eve_api.rs`, and `pathfinding/` plus `market/` stay modular.
## Repo-Specific Conventions
-- Preserve the current port split unless the task is explicitly a migration: frontend `:3000`, Python backend `:8000`, Rust backend `:8080`.
+- Preserve the current port split: frontend `:3000`, Python backend `:8000`.
- Keep external API calls and UI side effects out of the route-planning and market-analysis modules.
- Prefer typed models, named thresholds/constants, and small transformations over ad hoc inline logic in backend algorithms.
-- When changing the Rust backend, do not assume Python endpoint parity. Add routes, SSE or polling support, and CORS explicitly before wiring the frontend to it.
-- Be cautious about local-only runtime dependencies: Python expects ESI auth/cache state plus CSV map data under `backend/data/`; Rust expects OAuth env vars and `token.json`. \ No newline at end of file
+- Be cautious about local-only runtime dependencies: Python expects ESI auth/cache state plus CSV map data under `backend/data/`. \ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e05e7b..067d1db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Custom Copilot Chat prompt for documenting changes under `.github/prompts`.
### Changed
+- Claimed fork ownership by adding attribution to `readme.md` and modifying the `license.md` copyright.
- Migrated all primary ESI networking and dataset initialization in `backend/src/` to `asyncio` and `aiohttp` for non-blocking I/O.
- Redesigned `create_route` no longer auto-sets waypoints; players must now use the explicit "Set in Game" button to push route locations to the client.
- Switched frontend components (`Path.js`, `Info.js`) from HTTP polling to strictly using Server-Sent Events (`useSSE`) for real-time dataset rendering.
@@ -32,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Enhanced graph generation to reliably build its parent directory for `PATHS_FILE`.
- Adjusted `.gitignore` to track standard Python and Node virtual environment directories properly.
+### Removed
+- Removed the experimental `rust_backend/` rewrite and all associated instructions/configurations.
+
### Fixed
- Addressed ESI rate limit errors by enforcing an active semaphore limit and 429 backoff mechanism inside ESI client requests.
- Fixed uninitialized class name alias by swapping `Graph` to `NavigationGraph` across references.
diff --git a/license.md b/license.md
index 65202ab..0d29059 100644
--- a/license.md
+++ b/license.md
@@ -1,6 +1,7 @@
MIT License
Copyright (c) 2024-present James Pucula
+Copyright (c) 2026 kboz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
diff --git a/readme.md b/readme.md
index 1daf57d..6b1c8b1 100644
--- a/readme.md
+++ b/readme.md
@@ -3,7 +3,9 @@
*Hyperspace Asset Unloading and Loading*
-**HAUL** is my old route planner and automation tool designed for EVE Online. It simplifies asset transportation by optimizing trade routes, automatically setting waypoints in-game, and identifying profitable trade opportunities through API integrations.
+*This project is a modified fork of the original [lazydancer/eve_haul](https://github.com/lazydancer/eve_haul) repository.*
+
+**HAUL** is a route planner and automation tool designed for EVE Online. It simplifies asset transportation by optimizing trade routes, automatically setting waypoints in-game, and identifying profitable trade opportunities through API integrations.
![screenshot](screenshot.png)
@@ -28,7 +30,6 @@ The project comprises:
- A **Python backend** that handles route calculations, API calls, and real-time updates.
- A **React frontend** providing a clean and dynamic interface.
-- An **experimental Rust rewrite** of the backend focused on performance improvements.
* * * * *
@@ -50,9 +51,6 @@ The project comprises:
- **Framework**: React for building dynamic, responsive user interfaces.
- **State Updates**: Real-time state updates using server-sent events (SSE).
-### **Experimental Rust Backend**
-- A rewrite of the Python backend focused on improving performance in API-intensive operations like route calculations
-
* * * * *
**Key Features**
@@ -117,9 +115,6 @@ The route planning and arbitrage algorithms evaluate the profitability and safet
├── frontend/ # React frontend
│ ├── src/ # React components
│ └── package.json # Frontend dependencies
-├── rust_backend/ # Experimental Rust rewrite
-│ ├── src/ # Rust codebase
-│ └── Cargo.toml # Rust dependencies
├── scripts/ # Setup and run scripts
│ └── haul.sh # Unified script for data/auth setup and app runtime
```
diff --git a/rust_backend/.vscode/launch.json b/rust_backend/.vscode/launch.json
deleted file mode 100644
index cf1efa1..0000000
--- a/rust_backend/.vscode/launch.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "type": "lldb",
- "request": "launch",
- "name": "Debug executable 'rust_backend'",
- "cargo": {
- "args": [
- "build",
- "--bin=rust_backend",
- "--package=rust_backend"
- ],
- "filter": {
- "name": "rust_backend",
- "kind": "bin"
- }
- },
- "args": [],
- "cwd": "${workspaceFolder}"
- },
- {
- "type": "lldb",
- "request": "launch",
- "name": "Debug unit tests in executable 'rust_backend'",
- "cargo": {
- "args": [
- "test",
- "--no-run",
- "--bin=rust_backend",
- "--package=rust_backend"
- ],
- "filter": {
- "name": "rust_backend",
- "kind": "bin"
- }
- },
- "args": [],
- "cwd": "${workspaceFolder}"
- }
- ]
-} \ No newline at end of file
diff --git a/rust_backend/Cargo.toml b/rust_backend/Cargo.toml
deleted file mode 100644
index cace60d..0000000
--- a/rust_backend/Cargo.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-[package]
-name = "rust_backend"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-actix-web = "4"
-tokio = { version = "1", features = ["full"] }
-ctrlc = "3.2"
-serde = { version = "1.0", features = ["derive"] }
-serde_derive = "1.0"
-serde_json = "1.0"
-reqwest = { version = "0.11", features = ["json"] }
-dotenv = "*"
-chrono = { version = "0.4", features = ["serde"] }
-csv = "*" \ No newline at end of file
diff --git a/rust_backend/src/eve_api.rs b/rust_backend/src/eve_api.rs
deleted file mode 100644
index 18e1007..0000000
--- a/rust_backend/src/eve_api.rs
+++ /dev/null
@@ -1,319 +0,0 @@
-use dotenv::dotenv;
-use std::env;
-use std::error::Error;
-use std::fs::{self, File};
-use std::io::{self, Read, Write};
-use std::path::Path;
-use std::time::{SystemTime, UNIX_EPOCH, Duration};
-use std::sync::Arc;
-
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-use tokio::{task, time};
-use tokio::sync::Semaphore;
-
-
-use crate::pathfinding::Location;
-use crate::market::Order;
-
-const TOKEN_FILE: &str = "token.json";
-
-use std::sync::Mutex;
-
-
-#[derive(Deserialize, Serialize, Clone)]
-struct TokenData {
- access_token: String,
- refresh_token: String,
- expires_at: u64,
-}
-
-#[derive(Deserialize)]
-struct TokenResponse {
- access_token: String,
- refresh_token: String,
- expires_in: u64,
-}
-
-pub struct EveApiClient {
- client_id: String,
- client_secret: String,
- character_id: String,
- redirect_url: String,
- access_token: Mutex<Option<String>>,
- expires_at: Mutex<Option<u64>>,
-}
-
-
-impl EveApiClient {
- pub fn new() -> Result<Self, Box<dyn Error>> {
- dotenv().ok();
-
- let client_id = env::var("CLIENT_ID").map_err(|e| format!("CLIENT_ID error: {}", e))?;
- let client_secret = env::var("CLIENT_SECRET").map_err(|e| format!("CLIENT_SECRET error: {}", e))?;
- let character_id = env::var("CHARACTER_ID").map_err(|e| format!("CHARACTER_ID error: {}", e))?;
- let redirect_url = env::var("REDIRECT_URL").map_err(|e| format!("REDIRECT_URL error: {}", e))?;
-
- let token_data = match Self::load_token_from_file() {
- Ok(data) => Some(data),
- Err(_) => None,
- };
-
- Ok(EveApiClient {
- client_id,
- client_secret,
- character_id,
- redirect_url,
- access_token: Mutex::new(token_data.clone().map(|data| data.access_token)),
- expires_at: Mutex::new(token_data.map(|data| data.expires_at)),
- })
- }
-
- pub fn get_authorization_url(&self) -> String {
- let auth_url = format!(
- "https://login.eveonline.com/v2/oauth/authorize/?response_type=code&client_id={}&redirect_uri={}&scope={}",
- self.client_id,
- self.redirect_url,
- "esi-assets.read_assets.v1 esi-location.read_location.v1 esi-location.read_online.v1 esi-ui.open_window.v1 esi-ui.write_waypoint.v1".replace(" ", "%20")
- );
- auth_url
- }
-
- pub async fn exchange_code_for_token(&self, code: &str) -> Result<(), Box<dyn Error>> {
- let client = reqwest::Client::new();
- let res = client
- .post("https://login.eveonline.com/v2/oauth/token")
- .form(&[
- ("client_id", self.client_id.as_str()),
- ("client_secret", self.client_secret.as_str()),
- ("code", code),
- ("redirect_uri", self.redirect_url.as_str()),
- ("grant_type", "authorization_code"),
- ])
- .send()
- .await;
-
- let res = match res {
- Ok(response) => response,
- Err(e) => return Err(Box::new(e)),
- };
-
- let token_response: TokenResponse = match res.json().await {
- Ok(data) => data,
- Err(e) => return Err(Box::new(e)),
- };
-
- let expires_at = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .expect("Time went backwards") // Handle this error as per your need
- .as_secs() + token_response.expires_in;
-
- let token_data = TokenData {
- access_token: token_response.access_token,
- refresh_token: token_response.refresh_token,
- expires_at,
- };
-
- if let Err(e) = Self::save_token_to_file(&token_data) {
- return Err(e);
- }
-
- let mut token = self.access_token.lock().unwrap();
- *token = Some(token_data.access_token);
-
- Ok(())
- }
-
- pub async fn refresh_access_token(&self) -> Result<(), Box<dyn Error>> {
- let token_data = match Self::load_token_from_file() {
- Ok(data) => data,
- Err(_) => return Err("Failed to load token data".into()),
- };
-
-
- let client = reqwest::Client::new();
- let res = client
- .post("https://login.eveonline.com/v2/oauth/token") // Replace with the actual token endpoint
- .form(&[
- ("client_id", self.client_id.as_str()),
- ("client_secret", self.client_secret.as_str()),
- ("refresh_token", token_data.refresh_token.as_str()),
- ("grant_type", "refresh_token"),
- ])
- .send()
- .await?;
-
- let refreshed_token_response: TokenResponse = res.json().await?;
-
- let current_timestamp = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .expect("Time went backwards")
- .as_secs();
-
- let new_expires_at = current_timestamp + refreshed_token_response.expires_in;
-
- let new_token_data = TokenData {
- access_token: refreshed_token_response.access_token,
- refresh_token: refreshed_token_response.refresh_token,
- expires_at: new_expires_at,
- };
-
- Self::save_token_to_file(&new_token_data)?;
-
- let mut access_token = self.access_token.lock().unwrap();
- *access_token = Some(new_token_data.access_token);
- let mut expires_at = self.expires_at.lock().unwrap();
- *expires_at = Some(new_expires_at);
-
- Ok(())
- }
-
-
- pub async fn request_location(&self) -> Result<Location, Box<dyn Error>> {
- self.ensure_valid_token().await?;
-
- let url = format!("https://esi.evetech.net/latest/characters/{}/location/", self.character_id);
-
- println!("{:?}", url);
-
- let client = reqwest::Client::new();
-
- let response = client.get(url)
- .bearer_auth(self.get_access_token().unwrap())
- .send()
- .await?;
-
- println!("{:?}", response);
-
- if response.status().is_success() {
- let data = response.json::<Location>().await?;
- Ok(data)
- } else {
- Err(Box::new(response.error_for_status().unwrap_err()))
- }
- }
-
- pub async fn request_order_metadata(&self, region: u64) -> Result<(u64, DateTime<Utc>), Box<dyn Error>> {
- let url = format!("https://esi.evetech.net/latest/markets/{}/orders/?datasource=tranquility&order_type=all&page=1", region);
-
- let client = reqwest::Client::new();
- let response = client.get(&url)
- .send()
- .await
- .map_err(|e| e.to_string())?;
-
- let x_pages = match response.headers().get("x-pages") {
- Some(x_pages_value) => {
- match x_pages_value.to_str() {
- Ok(value_str) => {
- match value_str.parse::<u64>() {
- Ok(value) => value,
- Err(_) => return Err("Failed to parse x-pages value into u64".into()),
- }
- }
- Err(_) => return Err("Invalid x-pages header value".into()),
- }
- }
- None => return Err("X-Pages header not found".into()),
- };
-
- let expiry_unix_time = match response.headers().get("expires") {
- Some(expiry_value) => {
- match expiry_value.to_str() {
- Ok(value_str) => {
- match DateTime::parse_from_rfc2822(value_str) {
- Ok(expiry_date) => expiry_date.with_timezone(&Utc),
- Err(_) => return Err("Failed to parse expiry date".into()),
- }
- }
- Err(_) => return Err("Invalid expiry header value".into()),
- }
- }
- None => return Err("Expires header not found".into()),
- };
-
- Ok((x_pages, expiry_unix_time))
- }
-
-
-
- pub async fn request_orders(&self, region: u64) -> Result<(Vec<Order>, DateTime<Utc>), Box<dyn Error>> {
- let (x_pages, expiry) = self.request_order_metadata(region).await?;
-
- let client = reqwest::Client::builder()
- .timeout(Duration::from_secs(30))
- .build()?;
-
- let semaphore = Arc::new(Semaphore::new(10));
- let mut tasks = Vec::new();
-
- for page in 1..=x_pages {
- let permit = semaphore.clone().acquire_owned().await?;
- let client = client.clone();
- let url = format!("https://esi.evetech.net/latest/markets/{}/orders/?datasource=tranquility&order_type=all&page={}", region, page);
-
- let task = task::spawn(async move {
- time::sleep(Duration::from_millis(20)).await;
- let response = client.get(&url).send().await?;
- println!("{:?}", &url);
- drop(permit);
- response.error_for_status()?.json::<Vec<Order>>().await
- });
-
- tasks.push(task);
- }
-
- let mut orders = Vec::new();
- for task in tasks {
- match task.await {
- Ok(result) => match result {
- Ok(mut page_orders) => orders.append(&mut page_orders),
- Err(e) => return Err(Box::new(e)),
- },
- Err(e) => return Err(Box::new(e)),
- }
- }
-
- Ok((orders, expiry))
- }
-
- pub async fn ensure_valid_token(&self) -> Result<(), Box<dyn Error>> {
- let current_timestamp = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .map_err(|e| format!("System time error: {}", e))?
- .as_secs();
-
- let should_refresh = {
- let expires_at = self.expires_at.lock().unwrap();
- let expires_at_value = expires_at.unwrap_or(0);
- current_timestamp > expires_at_value
- };
-
- if should_refresh {
- self.refresh_access_token().await?;
- }
-
- Ok(())
- }
- pub fn get_access_token(&self) -> Option<String> {
- let token = self.access_token.lock().unwrap();
- token.clone()
- }
-
- pub fn save_token_to_file(token_data: &TokenData) -> Result<(), Box<dyn Error>> {
- let token_json = serde_json::to_string(token_data)?;
- fs::write(Path::new(TOKEN_FILE), token_json)?;
- Ok(())
- }
-
- fn load_token_from_file() -> Result<TokenData, Box<dyn Error>> {
- let token_json = match fs::read_to_string(Path::new(TOKEN_FILE)) {
- Ok(content) => content,
- Err(_) => return Err("Unable to read token file".into()),
- };
- let token_data: TokenData = serde_json::from_str(&token_json)?;
- Ok(token_data)
- }
-
-
-}
diff --git a/rust_backend/src/eve_service.rs b/rust_backend/src/eve_service.rs
deleted file mode 100644
index c831847..0000000
--- a/rust_backend/src/eve_service.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use crate::eve_api::EveApiClient;
-use crate::pathfinding::Location;
-use crate::market::Orders;
-
-use std::error::Error;
-
-pub struct EveService {
- client: EveApiClient,
-}
-
-impl EveService {
- pub fn new() -> Result<Self, Box<dyn Error>> {
- let eve_api_client = EveApiClient::new()?;
- Ok(EveService { client: eve_api_client, })
- }
-
- pub fn get_authorization_url(&self) -> String {
- self.client.get_authorization_url()
- }
-
- pub async fn exchange_code_for_token(&self, code: &str) -> Result<(), Box<dyn Error>> {
- self.client.exchange_code_for_token(code).await
- }
-
- pub async fn location(&self) -> Result<Location, Box<dyn Error>> {
- self.client.request_location().await.map_err(|e| e.into())
- }
-
- pub async fn updated_orders(&self, current_orders: &Orders) -> Result<Orders, Box<dyn Error>> {
- let mut region_orders = Orders::new();
-
- let regions = current_orders.expired_regions();
-
- for &region in regions.iter() {
- let (orders, expiry) = self.client.request_orders(region).await?;
- region_orders.insert(region, orders, expiry);
- }
-
- Ok(region_orders)
-
- }
-}
diff --git a/rust_backend/src/main.rs b/rust_backend/src/main.rs
deleted file mode 100644
index 4a2e086..0000000
--- a/rust_backend/src/main.rs
+++ /dev/null
@@ -1,95 +0,0 @@
-mod manager;
-mod eve_service;
-mod eve_api;
-mod pathfinding;
-mod market;
-
-use manager::Manager;
-
-use actix_web::{web, App, HttpServer, Responder, HttpResponse, http::header};
-use std::sync::Mutex;
-use tokio::sync::watch;
-use serde::Deserialize;
-
-
-async fn index() -> impl Responder {
- "Hi"
-}
-
-async fn location(manager: web::Data<Manager>) -> impl Responder {
- match manager.location() {
- Some(location) => {
- let station_id = location.station_id.map_or("None".to_string(), |id| id.to_string());
- let structure_id = location.structure_id.map_or("None".to_string(), |id| id.to_string());
- format!(
- "Current location: Solar System ID: {}, Station ID: {}, Structure ID: {}",
- location.solar_system_id, station_id, structure_id
- )
- },
- None => "No location available".to_string(),
- }
-}
-
-async fn orders(manager: web::Data<Manager>) -> impl Responder {
- let orders = manager.orders();
- HttpResponse::Ok().json(orders)
-}
-
-async fn start_oauth(manager: web::Data<Manager>) -> HttpResponse {
- let auth_url = manager.get_authorization_url();
- HttpResponse::Found()
- .append_header((header::LOCATION, auth_url.as_str()))
- .finish()
-}
-
-#[derive(Deserialize)]
-struct AuthCode {
- code: String,
-}
-
-async fn oauth_callback(
- query: web::Query<AuthCode>,
- manager: web::Data<Manager>,
-) -> HttpResponse {
- match manager.exchange_code_for_token(&query.code).await {
- Ok(_) => HttpResponse::Ok().body("Token exchange successful"),
- Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)),
- }
-}
-
-#[actix_web::main]
-async fn main() -> std::io::Result<()> {
- let manager = web::Data::new(Manager::new());
-
-
- let manager_clone = manager.clone();
- let (shutdown_tx, shutdown_rx) = watch::channel(false);
-
- let manager_handle = tokio::spawn(async move {
- manager_clone.run(shutdown_rx).await;
- });
-
- let server = HttpServer::new(move || {
- App::new()
- .app_data(manager.clone())
- .route("/", web::get().to(index))
- .route("/location", web::get().to(location))
- .route("/orders", web::get().to(orders))
- .route("/start_oauth", web::get().to(start_oauth))
- .route("/oauth_callback", web::get().to(oauth_callback))
- })
- .bind("127.0.0.1:8080")?
- .run();
-
- ctrlc::set_handler(move || {
- let _ = shutdown_tx.send(true);
- })
- .expect("Error setting Ctrl-C handler");
-
- tokio::select! {
- _ = server => {},
- _ = manager_handle => {},
- }
-
- Ok(())
-}
diff --git a/rust_backend/src/manager.rs b/rust_backend/src/manager.rs
deleted file mode 100644
index b9cbe08..0000000
--- a/rust_backend/src/manager.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-use std::sync::Mutex;
-use tokio::sync::watch;
-
-use std::error::Error;
-
-use crate::eve_service::EveService;
-use crate::pathfinding::{Location, build};
-use crate::market::{Order, Orders};
-
-pub struct Manager {
- // route_state: Mutex<Route>
- // route_info_state: Mutex<RouteInfo>
- orders_state: Mutex<Orders>,
- location: Mutex<Option<Location>>,
- // log_state: Mutex<Log>
- eve_service: EveService,
-
-}
-
-impl Manager {
- pub fn new() -> Manager {
-
- build();
-
- Manager {
- location: Mutex::new( None ),
- eve_service: EveService::new().expect("Manager to generate EveService"),
- orders_state: Mutex::new( Orders::new() ),
- }
-
- }
-
- pub async fn run(&self, shutdown_signal: watch::Receiver<bool>) {
- loop {
- tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
- if *shutdown_signal.borrow() {
- println!("Manager task is shutting down.");
- break;
- }
-
- match self.eve_service.location().await {
- Ok(result) => {
- match self.location.lock() {
- Ok(mut location) => *location = Some(result),
- Err(e) => eprintln!("Error locking location mutex: {}", e),
- }
- }
- Err(e) => {
- eprintln!("Error fetching location {}", e);
- }
- }
-
-
- let orders_clone = match self.orders_state.lock() {
- Ok(orders_lock) => orders_lock.clone(),
- Err(e) => {
- eprintln!("Error locking orders_state mutex: {}", e);
- continue;
- }
- };
-
- match self.eve_service.updated_orders(&orders_clone).await {
- Ok(updated_orders) => {
- match self.orders_state.lock() {
- Ok(mut orders_state) => orders_state.update(updated_orders),
- Err(e) => eprintln!("Error locking orders_state mutex: {}", e),
- }
- }
- Err(e) => eprintln!("Error updating orders: {}", e),
- }
- }
- }
- pub fn get_authorization_url(&self) -> String {
- self.eve_service.get_authorization_url()
- }
-
- pub async fn exchange_code_for_token(&self, code: &str) -> Result<(), Box<dyn Error>> {
- self.eve_service.exchange_code_for_token(code).await
- }
-
- pub fn location(&self) -> Option<Location> {
- let location = self.location.lock().unwrap();
- location.clone()
- }
-
- pub fn orders(&self) -> Vec<Order> {
- let orders = self.orders_state.lock().unwrap();
- orders.orders()
- }
-} \ No newline at end of file
diff --git a/rust_backend/src/market/mod.rs b/rust_backend/src/market/mod.rs
deleted file mode 100644
index 16d1f37..0000000
--- a/rust_backend/src/market/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-mod order;
-
-pub use order::{Order, Orders}; \ No newline at end of file
diff --git a/rust_backend/src/market/order.rs b/rust_backend/src/market/order.rs
deleted file mode 100644
index 6dbbcf6..0000000
--- a/rust_backend/src/market/order.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use std::error::Error;
-use std::collections::HashMap;
-
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-
-use crate::eve_service::EveService;
-
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Order {
- duration: u64,
- is_buy_order: bool,
- issued: DateTime<Utc>,
- location_id: u64,
- min_volume: u64,
- order_id: u64,
- price: f64,
- range: String,
- system_id: u64,
- type_id: u64,
- volume_remain: u64,
- volume_total: u64
-}
-
-const REGIONS: [u64; 1] = [10000033];
-
-#[derive(Clone, Serialize)]
-pub struct Orders {
- region_orders: HashMap<u64, Vec<Order>>,
- expiry_times: HashMap<u64, DateTime<Utc>>, // region_id to expiration time
-}
-
-
-impl Orders {
- pub fn new() -> Self {
- Orders {
- region_orders: HashMap::new(),
- expiry_times: HashMap::new(),
- }
- }
-
- pub fn insert(&mut self, region: u64, orders: Vec<Order>, expiry: DateTime<Utc>) {
- self.region_orders.insert(region, orders);
- self.expiry_times.insert(region, expiry);
- }
-
- pub fn expired_regions(&self) -> Vec<u64> {
- let current_time = Utc::now();
-
- println!("expiry: {:?}, time now: {:?}", &self.expiry_times, &current_time);
-
- let mut expired: Vec<u64> = self.expiry_times.iter()
- .filter(|&(_region_id, &expiry_time)| expiry_time < current_time)
- .map(|(&region_id, _)| region_id)
- .collect();
-
- for &region in REGIONS.iter() {
- if !self.expiry_times.contains_key(&region) {
- expired.push(region);
- }
- }
-
-
- expired
-
- }
-
- pub fn update(&mut self, updated_orders: Orders) {
- for (key, value) in updated_orders.region_orders {
- self.region_orders.insert(key, value);
-
- if let Some(expiry_time) = updated_orders.expiry_times.get(&key) {
- self.expiry_times.insert(key, *expiry_time);
- }
- }
- }
-
-
- pub fn orders(&self) -> Vec<Order> {
- self.region_orders.values().flat_map(|orders| orders.clone()).collect()
- }
-} \ No newline at end of file
diff --git a/rust_backend/src/market/trade.rs b/rust_backend/src/market/trade.rs
deleted file mode 100644
index 362acbf..0000000
--- a/rust_backend/src/market/trade.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use crate::order::Order;
-
-pub struct Trade<'a> {
- from: &'a Order,
- to: &'a Order,
- quantity: usize,
- gross_profit: f64,
-}
-
-const TAX_RATE: f64 = 0.08
-
-impl<'a> Trade<'a> {
- pub fn new(from: &'a Order, to: &'a Order, quantity: usize) -> Self {
- Self { from, to, quantity }
- }
-
- pub fn create_trades(sell_orders: &'a [Order], buy_orders: &'a [Order]) -> Vec<Trade<'a>> {
- let mut trades: Vec<Trade<'a>> = Vec::new();
- let mut grouped_orders: HashMap<i32, (Vec<&'a Order>, Vec<&'a Order>)> = HashMap::new();
-
- // Grouping sell orders
- for order in sell_orders {
- grouped_orders.entry(order.type_id)
- .or_insert((Vec::new(), Vec::new()))
- .0
- .push(order);
- }
-
- // Grouping buy orders
- for order in buy_orders {
- grouped_orders.entry(order.type_id)
- .or_insert((Vec::new(), Vec::new()))
- .1
- .push(order);
- }
-
- // Iterating over each type_id's sell and buy orders to create trades
- for (_type_id, (sells, buys)) in grouped_orders {
- for &sell_order in sells {
- for &buy_order in buys {
- let quantity = std::cmp::min(sell_order.volume_remain, buy_order.volume_remain);
- let gross_profit = quantity as f64 * (buy_order.price * (1-TAX_RATE) - sell_order.price);
-
- if gross_profit <= 0.0 {
- continue;
- }
-
- let trade = Trade::new(
- sell_order,
- buy_order,
- quantity,
- gross_profit,
- );
-
- trades.push(trade);
- }
- }
- }
-
- trades
- }
-
-}
diff --git a/rust_backend/src/pathfinding/graph.rs b/rust_backend/src/pathfinding/graph.rs
deleted file mode 100644
index 0352007..0000000
--- a/rust_backend/src/pathfinding/graph.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use crate::pathfinding::models::{MapNode, Graph};
-
-use std::collections::HashMap;
-
-pub fn generate_graph(stations: &Vec<MapNode>) -> Graph {
- HashMap::new()
-} \ No newline at end of file
diff --git a/rust_backend/src/pathfinding/mod.rs b/rust_backend/src/pathfinding/mod.rs
deleted file mode 100644
index 32c2831..0000000
--- a/rust_backend/src/pathfinding/mod.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-mod models;
-mod parser;
-mod graph;
-pub use models::{Location, Graph};
-
-
-use crate::pathfinding::parser::parse_stations;
-use crate::pathfinding::graph::generate_graph;
-
-pub fn build() {
- let stations = parse_stations().unwrap();
- let gates_connnections = parse_gate_connections().unwrap()
- let graph = generate_graph(&stations, &gates_connnections);
-
-}
diff --git a/rust_backend/src/pathfinding/models.rs b/rust_backend/src/pathfinding/models.rs
deleted file mode 100644
index 4bbc580..0000000
--- a/rust_backend/src/pathfinding/models.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-use serde::{Deserialize, Deserializer, Serialize};
-use std::str::FromStr;
-use std::collections::HashMap;
-
-
-#[derive(Deserialize, Serialize, Clone)]
-pub struct Location {
- pub solar_system_id: i32,
- pub station_id: Option<i32>,
- pub structure_id: Option<i32>,
-}
-
-#[derive(Deserialize, Serialize, Clone, Debug)]
-pub struct RawNode {
- pub itemID: u64,
- pub typeID: u64,
- pub groupID: u64,
- #[serde(deserialize_with = "deserialize_optional_u64")]
- pub solarSystemID: Option<u64>,
- #[serde(deserialize_with = "deserialize_optional_u64")]
- pub constellationID: Option<u64>,
- #[serde(deserialize_with = "deserialize_optional_u64")]
- pub regionID: Option<u64>,
- #[serde(deserialize_with = "deserialize_optional_u64")]
- pub orbitID: Option<u64>,
- pub x: Option<f64>,
- pub y: Option<f64>,
- pub z: Option<f64>,
- #[serde(deserialize_with = "deserialize_optional_f64")]
- pub radius: Option<f64>,
- pub itemName: Option<String>,
- #[serde(deserialize_with = "deserialize_optional_f64")]
- pub security: Option<f64>,
-}
-
-
-fn deserialize_optional_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
-where
- D: Deserializer<'de>,
-{
- let s: Option<String> = Option::deserialize(deserializer)?;
- match s.as_deref() {
- Some("None") | Some("") => Ok(None), // Handle "None" and empty strings
- Some(str_val) => u64::from_str(str_val)
- .map(Some)
- .map_err(serde::de::Error::custom),
- None => Ok(None),
- }
-}
-
-fn deserialize_optional_f64<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
-where
- D: Deserializer<'de>,
-{
- let s: Option<String> = Option::deserialize(deserializer)?;
- match s.as_deref() {
- Some("None") | Some("") => Ok(None), // Handle "None" and empty strings
- Some(str_val) => str_val.parse::<f64>()
- .map(Some)
- .map_err(serde::de::Error::custom),
- None => Ok(None),
- }
-}
-
-#[derive(Debug)]
-pub struct MapNode {
- pub item_id: u64,
- pub is_station: bool,
- pub solar_system_id: u64,
- pub region_id: u64,
- pub x: f64,
- pub y: f64,
- pub z: f64,
- pub name: String,
- pub security: f64,
-}
-
-#[derive(Deserialize, Serialize, Clone, Debug)]
-pub struct Connection {
- stargate_id: u64,
- destination_id: u64,
-}
-
-
-
-pub type Graph = HashMap<u64, Vec<u64>>; \ No newline at end of file
diff --git a/rust_backend/src/pathfinding/parser.rs b/rust_backend/src/pathfinding/parser.rs
deleted file mode 100644
index 955a448..0000000
--- a/rust_backend/src/pathfinding/parser.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::error::Error;
-use std::fs::File;
-use csv;
-
-use crate::pathfinding::models::{RawNode, MapNode};
-
-pub fn parse_stations() -> Result<Vec<MapNode>, Box<dyn Error>> {
- let file_path = "../mapDenormalize.csv";
- let file = File::open(file_path)?;
-
- let mut rdr = csv::Reader::from_reader(file);
-
- let mut raw_nodes: Vec<RawNode> = Vec::new();
- for result in rdr.deserialize() {
- match result {
- Ok(node) => raw_nodes.push(node),
- Err(e) => println!("Error deserializing record: {}", e),
- }
- }
-
- println!("raw nodes: {:?}", raw_nodes.len());
-
- let processed_nodes: Vec<MapNode> = raw_nodes.into_iter()
- .filter(|node| [15, 10].contains(&node.groupID))
- .map(|node| MapNode {
- item_id: node.itemID,
- is_station: node.groupID == 15,
- solar_system_id: node.solarSystemID.unwrap_or_default(),
- region_id: node.regionID.unwrap_or_default(),
- x: node.x.unwrap_or_default(),
- y: node.y.unwrap_or_default(),
- z: node.z.unwrap_or_default(),
- name: node.itemName.unwrap_or_default(),
- security: node.security.unwrap_or_default(),
- })
- .collect();
-
- println!("processed nodes: {:?}", processed_nodes.len());
-
-
- Ok(processed_nodes)
-}
-
-pub fn parse_gate_connections() -> Result<Vec<u64, u64>, Box<dyn Error>> {
- unimplemented!();
-} \ No newline at end of file
diff --git a/rust_backend/src/route.rs b/rust_backend/src/route.rs
deleted file mode 100644
index a2c477e..0000000
--- a/rust_backend/src/route.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use std::collections::HashMap;
-
-struct Item {
- profit: f32,
- cost: f32,
- cargo: f32,
- quantity: usize,
-}
-
-#[derive(PartialEq, Eq, Hash, Clone)]
-struct DiscreteState {
- cost: isize,
- cargo: isize,
-}
-
-type StateMap = HashMap<DiscreteState, (f32, Vec<(usize, usize)>)>;
-
-fn discretize(value: f32, precision: f32) -> isize {
- (value / precision).round() as isize
-}
-
-fn knapsack(items: &[Item], max_cost: f32, max_cargo: f32) -> (f32, Vec<(usize, usize)>) {
- let mut best_state: StateMap = HashMap::new();
- let precision = 0.01; // Define the precision level for discretization
- best_state.insert(DiscreteState { cost: 0, cargo: 0 }, (0.0, vec![]));
-
- for (index, item) in items.iter().enumerate() {
- let mut new_state = best_state.clone();
-
- for (&DiscreteState { cost: current_cost, cargo: current_cargo }, &(current_profit, ref indices)) in &best_state {
- let max_quantity_cost = ((max_cost - current_cost as f32 * precision) / item.cost).floor() as usize;
- let max_quantity_cargo = ((max_cargo - current_cargo as f32 * precision) / item.cargo).floor() as usize;
-
- let quantity = *[
- item.quantity,
- max_quantity_cost,
- max_quantity_cargo,
- ]
- .iter()
- .min()
- .unwrap();
-
- if quantity == 0 {
- continue;
- }
-
- let total_cost = discretize(current_cost as f32 * precision + item.cost * (quantity as f32), precision);
- let total_cargo = discretize(current_cargo as f32 * precision + item.cargo * (quantity as f32), precision);
- let total_profit = current_profit + item.profit * (quantity as f32);
-
- let mut new_indices = indices.clone();
- new_indices.push((index, quantity));
-
- new_state
- .entry(DiscreteState { cost: total_cost, cargo: total_cargo })
- .and_modify(|(existing_profit, existing_indices)| {
- if *existing_profit < total_profit {
- *existing_profit = total_profit;
- *existing_indices = new_indices.clone();
- }
- })
- .or_insert((total_profit, new_indices));
- }
-
- best_state = new_state;
- }
-
- best_state.values().max_by(|&(p1, _), &(p2, _)| p1.partial_cmp(&p2).unwrap()).unwrap_or(&(0.0, vec![])).clone()
-}
-
-fn main() {
- // Example usage with some items defined
- let items = vec![
- Item {
- profit: 60.0,
- cost: 0.1,
- cargo: 0.2,
- quantity: 100,
- },
- Item {
- profit: 100.0,
- cost: 0.2,
- cargo: 0.5,
- quantity: 50,
- },
- Item {
- profit: 100.0,
- cost: 0.1,
- cargo: 0.5,
- quantity: 50,
- },
- Item {
- profit: 100.0,
- cost: 0.2,
- cargo: 0.3,
- quantity: 50,
- },
- ];
-
- let max_cost = 50.0;
- let max_cargo = 50.0;
-
- let (max_profit, item_selections) = knapsack(&items, max_cost, max_cargo);
- println!("Maximum profit: {}", max_profit);
- for (index, quantity) in item_selections {
- println!("Item {} taken with quantity: {}", index, quantity);
- }
-}