Initial commit

This commit is contained in:
Techno Duck 2023-09-13 23:35:28 -04:00
commit 1d7df1f152
Signed by: technoduck
GPG key ID: 0418ACC82FFA9D04
15 changed files with 2906 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target/*
db.sqlite

2558
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[workspace]
members = [
"api/lib",
"api/axum",
"shared",
]
resolver = "2"

1
api/axum/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

17
api/axum/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "api-id-assigner"
description = "Axum powered API to manage ids for hostnames of a sulrm cluster"
version = "0.1.0"
edition = "2021"
[dependencies]
api-id-assigner-lib = { path = "../lib" }
axum = "0.6.18"
axum-extra = { version = "0.7.7" }
serde = { version = "1.0.188", features = ["serde_derive"] }
sqlx = { version = "0.7.1", features = ["sqlite"] }
tokio = {version = "1.28.2", features = ["full"]}
toml = "0.8.0"
tower = "0.4.13"
tower-http = { version = "0.4.3", features = ["fs","cors"] }
tracing = "0.1.37"

View file

@ -0,0 +1,6 @@
drop table if exists nodes;
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
ip string not null
);

95
api/axum/src/main.rs Normal file
View file

@ -0,0 +1,95 @@
use api_id_assigner_lib::AppState;
use api_id_assigner_lib::router;
use sqlx::{sqlite::SqliteConnectOptions, SqlitePool};
use axum::Router;
use std::{net::SocketAddr, str::FromStr,fs, path::Path};
use tower_http::cors::{Any, CorsLayer};
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
ip: String,
port: u16,
db_location: String
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error>{
if !cfg!(debug_assertions){
//check if running in production or debug env.
if !std::path::Path::new("/var/assigner").exists() {
println!("in release creating folder");
match fs::create_dir_all("/var/assigner") {
Ok(_res) => println!("Directory created succesfully"),
Err(err) => panic!("cannot create dir {err}")
}
}
//check if config file exists, if not, write default values in it
if !std::path::Path::new("/var/assigner/config.toml").exists() {
println!("config file doesnt exist");
let _ = match fs::write(Path::new("/var/assigner/config.toml"), b"ip = '0.0.0.0'\nport = 2000\ndb_location = '/var/assigner/db.sqlite'"){
Ok(res) => res,
Err(err) => panic!("error: {err}")
};
}
}
// read the toml config file into a string, if file not found give default values
let config_string = match fs::read_to_string("/var/assigner/config.toml") {
Ok(res) => res,
Err(_) => "
ip = '0.0.0.0'
port = 2000
db_location = '/var/assigner/db.sqlite'".to_string()
};
let config:Config = match toml::from_str(&config_string) {
Ok(res) => res,
Err(_) => Config { ip: "0.0.0.0".to_string(), port: 2000, db_location: "db.sqlite".to_string() }
};
// there is a better way but for now there isnt, if in development will be using a db.sqlite
// file in the cargo run location, in release will be using a db located at the path specified.
println!("Using db: {}",config.db_location);
let options = SqliteConnectOptions::new()
.filename(config.db_location)
.create_if_missing(true);
let pool = SqlitePool::connect_with(options).await?;
sqlx::migrate!().run(&pool).await.expect("Migrations failed :(");
let domain:String = format!("{}:{}",config.ip,config.port);
let state = AppState { domain,pool};
let cors = CorsLayer::new().allow_origin(Any);
let api_router = router::create_api_router(state.clone());
let app = Router::new()
.nest("/api", api_router)
.layer(cors);
//let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let addr = SocketAddr::from_str(&state.domain).unwrap();
println!("listening on {}", addr.to_string());
axum::Server::bind(&addr)
.serve(
//app.into_make_service()
app.into_make_service_with_connect_info::<SocketAddr>()
)
.await
.unwrap();
Ok(())
}

23
api/lib/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "api-id-assigner-lib"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
shared = { path = "../../shared" ,features = ["backend"]}
sqlx = { version = "0.7.1", default-features = false, features = [ "runtime-async-std-native-tls", "macros", "postgres", "uuid", "chrono", "json" ] }
tracing = "0.1.37"
async-traits = "0.0.0"
uuid = { version = "1.4.1", features = ["serde", "v4", "js"] }
async-trait = "0.1.73"
axum = "0.6.20"
tower-http = { version = "0.4.3", features = ["cors", "tower"] }
http = "0.2.9"
rand = "0.8.5"
serde = { version = "1.0.185", features = ["derive"] }
serde_json = "1.0.105"
chrono = "0.4.26"
time = { version = "0.3.0", features = ["serde"] }

13
api/lib/src/lib.rs Normal file
View file

@ -0,0 +1,13 @@
use sqlx::SqlitePool;
pub const API_VERSION: &str = "v0.0.1";
pub mod router;
mod node;
#[derive(Clone)]
pub struct AppState {
pub pool: SqlitePool,
pub domain: String,
}

60
api/lib/src/node.rs Normal file
View file

@ -0,0 +1,60 @@
use http::StatusCode;
use std::net::SocketAddr;
use axum::{Json,extract::{ConnectInfo,State},response::IntoResponse};
use crate::AppState;
use shared::models::{Node, DeleteNode};
pub async fn get_node_id(State(state):State<AppState>, ConnectInfo(addr): ConnectInfo<SocketAddr>) -> Result<Json<Node>, impl IntoResponse> {
let node_from_db_result =match sqlx::query_as::<_,Node>
("select * from nodes where ip = ?")
.bind(addr.ip().to_string())
.fetch_one(&state.pool).await {
Ok(res) => Ok(res),
Err(_err) => create_node_entry(State(&state),addr.ip().to_string()).await
};
match node_from_db_result {
Ok(node) => Ok(Json(node)),
Err(err) => Err((StatusCode::INTERNAL_SERVER_ERROR, err.to_string().into_response()))
}
}
//TODO add Json requirement to all api points for consistency
async fn create_node_entry(State(state):State<&AppState>,ip:String) -> Result<Node,sqlx::Error> {
// finds the lowest free id in the db.
match sqlx::query_as::<_,Node>("insert into nodes(id,ip) values ((SELECT (Min(id) + 1) AS NewIDToInsert
FROM nodes T2A
WHERE NOT EXISTS (SELECT id FROM nodes T2B WHERE T2A.id + 1 = T2B.id)),?) returning id, ip,created_at")
.bind(ip)
.fetch_one(&state.pool).await {
Ok(res) => Ok(res),
Err(err) => Err(err)
}
}
// requires json request with String ip of the device
pub async fn del_node_id(State(state):State<AppState>,ConnectInfo(addr): ConnectInfo<SocketAddr>, Json(del_req): Json<DeleteNode>) -> Result<Json<Node>, impl IntoResponse> {
if addr.ip().to_string() == del_req.ip {
return Err((StatusCode::FORBIDDEN, "You cannot request node other then yourself to be removed from registry".to_string().into_response()));
}
match sqlx::query_as::<_,Node>(
"delete from nodes where ip = ? returning id,ip,created_at")
.bind(del_req.ip)
.fetch_one(&state.pool).await
{
Ok(res) => Ok(Json(res)),
Err(err) => Err((StatusCode::INTERNAL_SERVER_ERROR, err.to_string().into_response()))
}
}
pub async fn apip(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("Hello {}", addr.ip().to_string())
}

25
api/lib/src/router.rs Normal file
View file

@ -0,0 +1,25 @@
use crate::AppState;
use axum::{Router, routing::{post,get}, Json};
use crate::node;
pub fn create_api_router(state:AppState) -> Router{
Router::new()
.route("/test", get(root))
.nest("/ip", create_api_ip_router(state))
}
pub fn create_api_ip_router(state:AppState) -> Router {
Router::new()
.route("/", get(node::apip))
.route("/get", get(node::get_node_id))
.route("/del", post(node::del_node_id))
.with_state(state)
}
async fn root() -> Json<String> {
Json("bozo".to_string())
}

68
api/lib/src/users.rs Normal file
View file

@ -0,0 +1,68 @@
use crate::AppState;
use axum::{
extract::State,
response::IntoResponse
};
use shared::models::{CreateUser,User};
async fn get_user(State(state): State<AppState>, user_id: &uuid::Uuid) -> Result<User, impl IntoResponse> {
sqlx::query_as::<_,User>(
r#"
SELECT id, name, email,password,created_at,ROLE
FROM users
WHERE user_id = $1
"#,)
.bind(&user_id)
.fetch_one(&state.pool)
.await
.map_err(|e| e.to_string())
}
async fn create_user(State(state): State<AppState>, create_user: &CreateUser) -> Result<User, impl IntoResponse> {
sqlx::query_as::<_, User>(
r#"
INSERT INTO users (name, email,password)
VALUES ($1, $2, $3)
RETURNING id, name, email,password,created_at,ROLE
"#,
)
.bind(&create_user.name)
.bind(&create_user.email)
.bind(&create_user.password)
.fetch_one(&state.pool)
.await
.map_err(|e| e.to_string())
}
async fn update_user(State(state): State<AppState>, user: &User) -> Result<User, impl IntoResponse> {
sqlx::query_as::<_, User>(
r#"
UPDATE users
SET name, email,password, ROLE
WHERE id = $1
RETURNING id, name, email,password,created_at,ROLE
"#,
)
.bind(&user.name)
.bind(&user.email)
.bind(&user.password)
.fetch_one(&state.pool)
.await
.map_err(|e| e.to_string())
}
async fn delete_user(State(state): State<AppState>, user_id: &uuid::Uuid) -> Result<uuid::Uuid, impl IntoResponse> {
sqlx::query_scalar::<_, uuid::Uuid>(
r#"
DELETE FROM users
WHERE id = $1
RETURNING id
"#,
)
.bind(user_id)
.fetch_one(&state.pool)
.await
.map_err(|e| e.to_string())
}

14
shared/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
backend = ['sqlx']
[dependencies]
chrono = { version = "0.4.26", features = ["serde"] }
sqlx = { version = "0.7.1", default-features = false, features = [ "runtime-async-std-native-tls", "macros", "postgres", "uuid", "chrono", "json" ], optional = true }
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.105"

1
shared/src/lib.rs Normal file
View file

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

16
shared/src/models.rs Normal file
View file

@ -0,0 +1,16 @@
use serde::{Deserialize,Serialize};
#[cfg_attr(feature = "backend", derive(sqlx::FromRow))]
#[derive(Serialize,Deserialize,Debug,Clone,PartialEq,Eq,PartialOrd,Ord)]
pub struct Node {
pub id: i32,
pub ip: String,
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
}
#[cfg_attr(feature = "backend", derive(sqlx::FromRow))]
#[derive(Serialize,Deserialize,Debug,Clone,PartialEq,Eq,PartialOrd,Ord,Default)]
pub struct DeleteNode {
pub ip: String,
}