finish shortest path and adding stuff to db

This commit is contained in:
lukas-heilgenbrunner 2024-05-29 13:40:37 +02:00
parent e589e974db
commit 10557ec498
6 changed files with 177 additions and 48 deletions

View File

@ -1,21 +1,56 @@
use neo4rs::Graph; use crate::backend::types::{City, Road, ShortestPath};
use rocket::{get, Route, State}; use crate::graph::graph::{all_cities, create_city, create_road, shortest_path};
use rocket::response::status::NotFound; use neo4rs::{Graph};
use rocket_okapi::{openapi, openapi_get_routes}; use rocket::response::status::{BadRequest};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{get, post, Route, State};
use rocket_okapi::{openapi, openapi_get_routes};
/// configure available api nodes
pub fn build_api() -> Vec<Route> { pub fn build_api() -> Vec<Route> {
openapi_get_routes![ openapi_get_routes![addcity, addroad, getshortestpath, getallcities]
append
]
} }
/// get general build-server stats /// endpoint to add new city to graph
#[openapi(tag = "stats")] #[openapi]
#[get("/append")] #[post("/addcity", data = "<msg>")]
pub async fn append(graph: &State<Graph>) -> Result<String, NotFound<String>> { pub async fn addcity(graph: &State<Graph>, msg: Json<City>) -> Result<String, BadRequest<String>> {
let graph = graph as &Graph; match create_city(graph, msg.into_inner()).await {
Ok(_) => Ok("ok".to_string()),
Ok("worked".to_string()) Err(e) => Err(BadRequest(e.to_string())),
}
}
/// endpoint to add new road between two cities
#[openapi]
#[post("/road", data = "<msg>")]
pub async fn addroad(graph: &State<Graph>, msg: Json<Road>) -> Result<String, BadRequest<String>> {
match create_road(graph, msg.into_inner()).await {
Ok(_) => Ok("ok".to_string()),
Err(e) => Err(BadRequest(e.to_string())),
}
}
/// endpoint to get all available cities with optional limit
#[openapi]
#[get("/cities?<limit>")]
pub async fn getallcities(graph: &State<Graph>, limit: Option<i32>) -> Result<Json<Vec<String>>, BadRequest<String>> {
match all_cities(graph, limit).await {
Ok(v) => Ok(Json::from(v)),
Err(e) => Err(BadRequest(e.to_string())),
}
}
/// endpoint to get shortest path between two cities with the resulting length
#[openapi]
#[get("/shortestpath?<city1>&<city2>")]
pub async fn getshortestpath(
graph: &State<Graph>,
city1: String,
city2: String,
) -> Result<Json<ShortestPath>, BadRequest<String>> {
match shortest_path(graph, city1, city2).await {
Ok(v) => Ok(Json::from(v)),
Err(e) => Err(BadRequest(e.to_string())),
}
} }

View File

@ -1 +1,2 @@
pub mod backend; pub mod backend;
pub mod types;

24
src/backend/types.rs Normal file
View File

@ -0,0 +1,24 @@
use rocket::serde::{Deserialize, Serialize};
use rocket_okapi::okapi::schemars;
use rocket_okapi::JsonSchema;
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(crate = "rocket::serde")]
pub struct City {
pub name: String,
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(crate = "rocket::serde")]
pub struct Road {
pub city1name: String,
pub city2name: String,
pub distance: i32,
pub oneway: bool,
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(crate = "rocket::serde")]
pub struct ShortestPath {
pub(crate) path: Vec<String>,
pub(crate) distance: i32,
}

90
src/graph/graph.rs Normal file
View File

@ -0,0 +1,90 @@
use crate::backend::types::{City, Road, ShortestPath};
use anyhow::anyhow;
use neo4rs::{query, Graph, Node, Path};
pub async fn shortest_path(
graph: &Graph,
city1: String,
city2: String,
) -> anyhow::Result<ShortestPath> {
if city1 == city2 {
return Err(anyhow!("Two cities must be different"));
}
let mut result = graph.execute(
query("MATCH (c1:City {name: $c1}), (c2:City {name: $c2})
WITH c1, c2
MATCH path = (c1)-[:ROAD*]->(c2)
WITH path, reduce(distance = 0, rel in relationships(path) | distance + rel.distance) AS totalDistance
ORDER BY totalDistance ASC
LIMIT 1
RETURN path, totalDistance AS distance")
.param("c1", city1)
.param("c2", city2)
).await?;
return if let Ok(Some(row)) = result.next().await {
let path: Path = row.get("path")?;
let distance: i32 = row.get("distance")?;
let city_names: Vec<String> = path
.nodes()
.iter()
.map(|node| node.get::<String>("name").unwrap())
.collect();
Ok(ShortestPath {
path: city_names,
distance,
})
} else {
Err(anyhow!("couldn't find a valid path"))
};
}
pub async fn all_cities(graph: &Graph, limit: Option<i32>) -> anyhow::Result<Vec<String>> {
let mut cities = vec![];
let query = match limit {
None => query("MATCH (c:City) RETURN c"),
Some(limit) => query("MATCH (c:City) RETURN c LIMIT $limit").param("limit", limit)
};
let mut result = graph
.execute(query)
.await?;
while let Ok(Some(row)) = result.next().await {
let node: Node = row.get("c")?;
let name: String = node.get("name")?;
cities.push(name);
}
Ok(cities)
}
pub async fn create_road(graph: &Graph, msg: Road) -> anyhow::Result<()> {
let mut txn = graph.start_txn().await?;
txn.run_queries([
query("MATCH (c1:City{name: $city1}) WITH c1 MATCH (c2:City{name: $city2}) WITH c1,c2 CREATE (c1)-[:ROAD{distance: $distance}]->(c2)")
.param("city1", msg.city1name.clone())
.param("city2", msg.city2name.clone())
.param("distance", msg.distance.clone()),
]).await?;
if !msg.oneway {
txn.run_queries([
query("MATCH (c1:City{name: $city1}) WITH c1 MATCH (c2:City{name: $city2}) WITH c1,c2 CREATE (c1)<-[:ROAD{distance: $distance}]-(c2)")
.param("city1", msg.city1name.clone())
.param("city2", msg.city2name.clone())
.param("distance", msg.distance.clone())
]).await?;
}
txn.commit().await?;
Ok(())
}
pub async fn create_city(graph: &Graph, msg: City) -> anyhow::Result<()> {
let mut txn = graph.start_txn().await?;
txn.run_queries([query("CREATE (c:City {name: $name})").param("name", msg.name.clone())])
.await?;
txn.commit().await?;
Ok(())
}

1
src/graph/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod graph;

View File

@ -1,51 +1,29 @@
use neo4rs::{Graph, Node, query}; use crate::backend::backend::build_api;
use neo4rs::Graph;
use rocket::Config; use rocket::Config;
use rocket_okapi::swagger_ui::{make_swagger_ui, SwaggerUIConfig}; use rocket_okapi::swagger_ui::{make_swagger_ui, SwaggerUIConfig};
use crate::backend::backend::build_api;
mod backend; mod backend;
mod graph;
fn main() { fn main() {
println!("Hello, world!"); let tokio = tokio::runtime::Runtime::new().expect("Failed to spawn tokio runtime");
let t = tokio::runtime::Runtime::new().unwrap(); tokio.block_on(async move {
let tt = Box::new("hello".to_string());
let iii = 1;
for i in 1..5 {
if tt == "hello" {
println!("{tt}");
} else {
println!("not");
}
}
println!("{tt}");
t.block_on(async move {
// concurrent queries
let uri: String = "127.0.0.1:7687".to_string(); let uri: String = "127.0.0.1:7687".to_string();
let user = "neo4j"; let user = "";
let pass = "neo"; let pass = "";
// connect to neo4j database
let graph = Graph::new(uri, user, pass).await.unwrap(); let graph = Graph::new(uri, user, pass).await.unwrap();
//println!("{uri}"); // configure api port and interface
let config = Config { let config = Config {
address: "0.0.0.0".parse().unwrap(), address: "0.0.0.0".parse().unwrap(),
port: 8081, port: 8081,
..Default::default() ..Default::default()
}; };
// configure api and swagger ui
let rock = rocket::custom(config) let rock = rocket::custom(config)
.manage(graph) .manage(graph)
.mount("/api/", build_api()) .mount("/api/", build_api())