restructure backend and database

This commit is contained in:
Mika 2024-10-13 18:44:37 +02:00
parent c8a70b5a32
commit a65657aea1
21 changed files with 373 additions and 76 deletions

View file

@ -0,0 +1,11 @@
meta {
name: get data from last hour
type: http
seq: 1
}
get {
url: http://localhost:8080/api/v1/data
body: none
auth: none
}

View file

@ -1,7 +1,7 @@
meta { meta {
name: Create node group name: Create node group
type: http type: http
seq: 3 seq: 1
} }
post { post {

View file

@ -12,9 +12,7 @@ post {
body:json { body:json {
{ {
"name":"some mac address", "id":"04-7c-16-06-b3-53",
"coord_la":1.123123, "group":"22da4165-582c-4df9-a911-dfd5573ae468"
"coord_lo":5.3123123,
"group":"efbd70a9-dc89-4c8d-9e6c-e7607c823df3"
} }
} }

View file

@ -1,7 +1,7 @@
meta { meta {
name: delete node name: delete node
type: http type: http
seq: 4 seq: 3
} }
delete { delete {

3
Cargo.lock generated
View file

@ -543,6 +543,7 @@ dependencies = [
"actix-cors", "actix-cors",
"actix-web", "actix-web",
"argon2", "argon2",
"chrono",
"dotenvy", "dotenvy",
"entity", "entity",
"futures", "futures",
@ -773,8 +774,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]

View file

@ -18,3 +18,4 @@ sea-orm = { version = "1", features = [
dotenvy = "*" dotenvy = "*"
jsonwebtoken = "*" jsonwebtoken = "*"
futures = "*" futures = "*"
chrono = "*"

View file

@ -1,28 +1,36 @@
use crate::AppState; use crate::AppState;
use actix_web::{error::ErrorInternalServerError, web, HttpResponse, Responder}; use actix_web::{
use entity::node_group; error::{ErrorBadRequest, ErrorInternalServerError},
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait}; web, HttpResponse, Responder,
};
use chrono::Utc;
use entity::{node, node_group, sensor_data};
use sea_orm::{entity::*, query::*, ActiveModelTrait, ActiveValue, DbBackend, EntityTrait};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
#[derive(Serialize)]
struct NodeWithSensorData {
node: node::Model,
sensor_data: Vec<sensor_data::Model>,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CreateGroupWithoutId { pub struct CreateGroupWithoutId {
name: String, name: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CreateLicense { pub struct CreateNode {
name: String, id: i32,
coord_la: f64,
coord_lo: f64,
group: uuid::Uuid, group: uuid::Uuid,
} }
#[derive(Serialize)] #[derive(Serialize)]
struct GroupWithNode { struct GroupWithNode {
#[serde(flatten)] #[serde(flatten)]
group: entity::node_group::Model, group: node_group::Model,
node: Vec<entity::node::Model>, node: Vec<node::Model>,
} }
pub async fn get_nodes(state: web::Data<AppState>) -> actix_web::Result<impl Responder> { pub async fn get_nodes(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
@ -40,6 +48,33 @@ pub async fn get_nodes(state: web::Data<AppState>) -> actix_web::Result<impl Res
Ok(web::Json(result)) Ok(web::Json(result))
} }
pub async fn get_data(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
let db = &state.db;
let nodes = node::Entity::find()
.all(db)
.await
.map_err(ErrorInternalServerError)?;
let now = Utc::now();
let one_hour_ago = now - chrono::Duration::hours(1);
let mut result: Vec<NodeWithSensorData> = Vec::new();
for node in nodes {
let node_id = node.id.clone();
let sensor_data = sensor_data::Entity::find()
.filter(sensor_data::Column::NodeId.eq(node_id))
.filter(sensor_data::Column::Timestamp.gt(one_hour_ago))
.all(db)
.await
.map_err(ErrorInternalServerError)?;
result.push(NodeWithSensorData { node, sensor_data });
}
Ok(web::Json(result))
}
pub async fn create_group( pub async fn create_group(
state: web::Data<AppState>, state: web::Data<AppState>,
group: web::Json<CreateGroupWithoutId>, group: web::Json<CreateGroupWithoutId>,
@ -60,7 +95,7 @@ pub async fn create_group(
pub async fn create_node( pub async fn create_node(
state: web::Data<AppState>, state: web::Data<AppState>,
node: web::Json<CreateLicense>, node: web::Json<CreateNode>,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let db = &state.db; let db = &state.db;
@ -78,18 +113,12 @@ pub async fn create_node(
return Err(ErrorInternalServerError("Group ID does not exist")); return Err(ErrorInternalServerError("Group ID does not exist"));
} }
let mac_int =
mac_to_i32(&node.id).map_err(|_| ErrorInternalServerError("Invalid MAC address"))?;
node.id = mac_int;
let node = entity::node::ActiveModel { let node = entity::node::ActiveModel {
id: ActiveValue::NotSet, id: ActiveValue::Set(node.id),
name: ActiveValue::Set(node.name),
status: ActiveValue::NotSet,
coord_la: ActiveValue::Set(node.coord_la),
coord_lo: ActiveValue::Set(node.coord_lo),
temperature: ActiveValue::NotSet,
battery_minimum: ActiveValue::NotSet,
battery_current: ActiveValue::NotSet,
battery_maximum: ActiveValue::NotSet,
voltage: ActiveValue::NotSet,
uptime: ActiveValue::NotSet,
group: ActiveValue::Set(node.group), group: ActiveValue::Set(node.group),
}; };
@ -98,6 +127,7 @@ pub async fn create_node(
Ok(web::Json(result)) Ok(web::Json(result))
} }
/*
pub async fn delete_node( pub async fn delete_node(
state: web::Data<AppState>, state: web::Data<AppState>,
path: web::Path<Uuid>, path: web::Path<Uuid>,
@ -113,3 +143,12 @@ pub async fn delete_node(
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
*/
fn mac_to_i32(mac: &str) -> Result<i32, std::num::ParseIntError> {
// Remove non-hexadecimal characters
let sanitized_mac: String = mac.chars().filter(|c| c.is_digit(16)).collect();
// Parse the sanitized string as a hexadecimal number
i32::from_str_radix(&sanitized_mac, 16)
}

View file

@ -19,7 +19,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
.get(node::get_nodes) .get(node::get_nodes)
.post(node::create_node), .post(node::create_node),
) )
.service(web::resource("/nodes/{id}").delete(node::delete_node)) .service(web::resource("/data").get(node::get_data))
//.service(web::resource("/nodes/{id}").delete(node::delete_node))
.service(web::resource("/groups").post(node::create_group)), .service(web::resource("/groups").post(node::create_group)),
); );
} }

View file

@ -0,0 +1,8 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
pub mod prelude;
pub mod node;
pub mod node_group;
pub mod sensor_data;
pub mod user;

View file

@ -0,0 +1,44 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "node")]
pub struct Model {
#[sea_orm(
primary_key,
auto_increment = false,
column_type = "custom(\"macaddr\")"
)]
pub id: String,
pub group: Uuid,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::node_group::Entity",
from = "Column::Group",
to = "super::node_group::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
NodeGroup,
#[sea_orm(has_many = "super::sensor_data::Entity")]
SensorData,
}
impl Related<super::node_group::Entity> for Entity {
fn to() -> RelationDef {
Relation::NodeGroup.def()
}
}
impl Related<super::sensor_data::Entity> for Entity {
fn to() -> RelationDef {
Relation::SensorData.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,26 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "node_group")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub name: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::node::Entity")]
Node,
}
impl Related<super::node::Entity> for Entity {
fn to() -> RelationDef {
Relation::Node.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,6 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
pub use super::node::Entity as Node;
pub use super::node_group::Entity as NodeGroup;
pub use super::sensor_data::Entity as SensorData;
pub use super::user::Entity as User;

View file

@ -0,0 +1,54 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "sensor_data")]
pub struct Model {
#[sea_orm(
primary_key,
auto_increment = false,
column_type = "custom(\"macaddr\")"
)]
pub id: String,
#[sea_orm(primary_key, auto_increment = false)]
pub timestamp: DateTime,
#[sea_orm(column_type = "Double")]
pub coord_la: f64,
#[sea_orm(column_type = "Double")]
pub coord_lo: f64,
#[sea_orm(column_type = "Float")]
pub temperature: f32,
#[sea_orm(column_type = "Double")]
pub battery_minimum: f64,
#[sea_orm(column_type = "Double")]
pub battery_current: f64,
#[sea_orm(column_type = "Double")]
pub battery_maximum: f64,
#[sea_orm(column_type = "Double")]
pub voltage: f64,
pub uptime: i64,
#[sea_orm(column_type = "custom(\"macaddr\")", nullable)]
pub node_id: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::node::Entity",
from = "Column::NodeId",
to = "super::node::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Node,
}
impl Related<super::node::Entity> for Entity {
fn to() -> RelationDef {
Relation::Node.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,19 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub name: String,
pub email: String,
pub hash: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -4,4 +4,5 @@ pub mod prelude;
pub mod node; pub mod node;
pub mod node_group; pub mod node_group;
pub mod sensor_data;
pub mod user; pub mod user;

View file

@ -3,28 +3,11 @@
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "node")] #[sea_orm(table_name = "node")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid, pub id: i32,
pub name: String,
pub status: bool,
#[sea_orm(column_type = "Double")]
pub coord_la: f64,
#[sea_orm(column_type = "Double")]
pub coord_lo: f64,
#[sea_orm(column_type = "Float")]
pub temperature: f32,
#[sea_orm(column_type = "Double")]
pub battery_minimum: f64,
#[sea_orm(column_type = "Double")]
pub battery_current: f64,
#[sea_orm(column_type = "Double")]
pub battery_maximum: f64,
#[sea_orm(column_type = "Double")]
pub voltage: f64,
pub uptime: i64,
pub group: Uuid, pub group: Uuid,
} }
@ -38,6 +21,8 @@ pub enum Relation {
on_delete = "Cascade" on_delete = "Cascade"
)] )]
NodeGroup, NodeGroup,
#[sea_orm(has_many = "super::sensor_data::Entity")]
SensorData,
} }
impl Related<super::node_group::Entity> for Entity { impl Related<super::node_group::Entity> for Entity {
@ -46,4 +31,10 @@ impl Related<super::node_group::Entity> for Entity {
} }
} }
impl Related<super::sensor_data::Entity> for Entity {
fn to() -> RelationDef {
Relation::SensorData.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View file

@ -2,4 +2,5 @@
pub use super::node::Entity as Node; pub use super::node::Entity as Node;
pub use super::node_group::Entity as NodeGroup; pub use super::node_group::Entity as NodeGroup;
pub use super::sensor_data::Entity as SensorData;
pub use super::user::Entity as User; pub use super::user::Entity as User;

View file

@ -0,0 +1,49 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "sensor_data")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: i32,
#[sea_orm(primary_key, auto_increment = false)]
pub timestamp: DateTime,
#[sea_orm(column_type = "Double")]
pub coord_la: f64,
#[sea_orm(column_type = "Double")]
pub coord_lo: f64,
#[sea_orm(column_type = "Float")]
pub temperature: f32,
#[sea_orm(column_type = "Double")]
pub battery_minimum: f64,
#[sea_orm(column_type = "Double")]
pub battery_current: f64,
#[sea_orm(column_type = "Double")]
pub battery_maximum: f64,
#[sea_orm(column_type = "Double")]
pub voltage: f64,
pub uptime: i64,
pub node_id: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::node::Entity",
from = "Column::NodeId",
to = "super::node::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Node,
}
impl Related<super::node::Entity> for Entity {
fn to() -> RelationDef {
Relation::Node.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -2,6 +2,7 @@ pub use sea_orm_migration::prelude::*;
mod m20241008_091626_create_table_user; mod m20241008_091626_create_table_user;
mod m20241008_095058_create_table_node; mod m20241008_095058_create_table_node;
mod m20241013_134422_create_table_sensor_data;
pub struct Migrator; pub struct Migrator;
@ -11,6 +12,7 @@ impl MigratorTrait for Migrator {
vec![ vec![
Box::new(m20241008_091626_create_table_user::Migration), Box::new(m20241008_091626_create_table_user::Migration),
Box::new(m20241008_095058_create_table_node::Migration), Box::new(m20241008_095058_create_table_node::Migration),
Box::new(m20241013_134422_create_table_sensor_data::Migration),
] ]
} }
} }

View file

@ -26,21 +26,7 @@ impl MigrationTrait for Migration {
Table::create() Table::create()
.table(Node::Table) .table(Node::Table)
.if_not_exists() .if_not_exists()
.col( .col(integer(Node::Id).primary_key())
uuid(Node::Id)
.extra("DEFAULT gen_random_uuid()")
.primary_key(),
)
.col(string(Node::Name))
.col(boolean(Node::Status).default(false))
.col(double(Node::CoordLa))
.col(double(Node::CoordLo))
.col(float(Node::Temperature).default(-127))
.col(double(Node::BatteryMinimum).default(-127))
.col(double(Node::BatteryCurrent).default(-127))
.col(double(Node::BatteryMaximum).default(-127))
.col(double(Node::Voltage).default(-127))
.col(big_unsigned(Node::Uptime).default(0))
.col(uuid(Node::Group)) .col(uuid(Node::Group))
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
@ -71,25 +57,15 @@ impl MigrationTrait for Migration {
} }
#[derive(DeriveIden)] #[derive(DeriveIden)]
enum Node { pub enum Node {
Table, Table,
Id, Id, // Mac address
Name,
Status,
CoordLa,
CoordLo,
Temperature, // def: -127
BatteryMinimum, // def: -127
BatteryCurrent, // def: -127
BatteryMaximum, // def: -127
Voltage, // def: -127
Uptime, // def: 0
Group, Group,
} }
#[derive(DeriveIden)] #[derive(DeriveIden)]
enum NodeGroup { enum NodeGroup {
Table, Table,
Id, Id, // Uuid
Name, Name, // Groupname
} }

View file

@ -0,0 +1,67 @@
use crate::m20241008_095058_create_table_node::Node;
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(SensorData::Table)
.if_not_exists()
.col(integer(Node::Id))
.col(timestamp(SensorData::Timestamp))
.primary_key(
Index::create()
.col(SensorData::Id)
.col(SensorData::Timestamp),
)
.col(double(SensorData::CoordLa))
.col(double(SensorData::CoordLo))
.col(float(SensorData::Temperature).default(-127))
.col(double(SensorData::BatteryMinimum).default(-127))
.col(double(SensorData::BatteryCurrent).default(-127))
.col(double(SensorData::BatteryMaximum).default(-127))
.col(double(SensorData::Voltage).default(-127))
.col(big_unsigned(SensorData::Uptime).default(0))
.col(integer(SensorData::NodeId))
.foreign_key(
ForeignKey::create()
.name("fk-data-node_id")
.from(SensorData::Table, SensorData::NodeId)
.to(Node::Table, Node::Id)
.on_update(ForeignKeyAction::Cascade)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
manager
.drop_table(Table::drop().table(SensorData::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum SensorData {
Table,
Id, // Mac address
Timestamp,
CoordLa,
CoordLo,
Temperature, // def: -127
BatteryMinimum, // def: -127
BatteryCurrent, // def: -127
BatteryMaximum, // def: -127
Voltage, // def: -127
Uptime, // def: 0
NodeId,
}