Initial commit
This commit is contained in:
commit
1d7df1f152
15 changed files with 2906 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
target/*
|
||||||
|
db.sqlite
|
2558
Cargo.lock
generated
Normal file
2558
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"api/lib",
|
||||||
|
"api/axum",
|
||||||
|
"shared",
|
||||||
|
]
|
||||||
|
resolver = "2"
|
1
api/axum/.gitignore
vendored
Normal file
1
api/axum/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
17
api/axum/Cargo.toml
Normal file
17
api/axum/Cargo.toml
Normal 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"
|
6
api/axum/migrations/20230910195018_schema.sql
Normal file
6
api/axum/migrations/20230910195018_schema.sql
Normal 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
95
api/axum/src/main.rs
Normal 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
23
api/lib/Cargo.toml
Normal 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
13
api/lib/src/lib.rs
Normal 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
60
api/lib/src/node.rs
Normal 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
25
api/lib/src/router.rs
Normal 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
68
api/lib/src/users.rs
Normal 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
14
shared/Cargo.toml
Normal 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
1
shared/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod models;
|
16
shared/src/models.rs
Normal file
16
shared/src/models.rs
Normal 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,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue