diff --git a/ApfelBruno/Data/get data from last hour.bru b/ApfelBruno/Data/get data from last hour.bru new file mode 100644 index 0000000..29c1869 --- /dev/null +++ b/ApfelBruno/Data/get data from last hour.bru @@ -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 +} diff --git a/ApfelBruno/Node/Create node group.bru b/ApfelBruno/Group/Create node group.bru similarity index 94% rename from ApfelBruno/Node/Create node group.bru rename to ApfelBruno/Group/Create node group.bru index 4badd84..256f820 100644 --- a/ApfelBruno/Node/Create node group.bru +++ b/ApfelBruno/Group/Create node group.bru @@ -1,7 +1,7 @@ meta { name: Create node group type: http - seq: 3 + seq: 1 } post { diff --git a/ApfelBruno/Node/Create node.bru b/ApfelBruno/Node/Create node.bru index 256d087..64cccbd 100644 --- a/ApfelBruno/Node/Create node.bru +++ b/ApfelBruno/Node/Create node.bru @@ -12,9 +12,7 @@ post { body:json { { - "name":"some mac address", - "coord_la":1.123123, - "coord_lo":5.3123123, - "group":"efbd70a9-dc89-4c8d-9e6c-e7607c823df3" + "id":"04-7c-16-06-b3-53", + "group":"22da4165-582c-4df9-a911-dfd5573ae468" } } diff --git a/ApfelBruno/Node/delete node.bru b/ApfelBruno/Node/delete node.bru index 10fec80..017c973 100644 --- a/ApfelBruno/Node/delete node.bru +++ b/ApfelBruno/Node/delete node.bru @@ -1,7 +1,7 @@ meta { name: delete node type: http - seq: 4 + seq: 3 } delete { diff --git a/Cargo.lock b/Cargo.lock index f3efbe2..c8424ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,6 +543,7 @@ dependencies = [ "actix-cors", "actix-web", "argon2", + "chrono", "dotenvy", "entity", "futures", @@ -773,8 +774,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.6", ] diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index e9d36d0..acc9f14 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -18,3 +18,4 @@ sea-orm = { version = "1", features = [ dotenvy = "*" jsonwebtoken = "*" futures = "*" +chrono = "*" diff --git a/crates/backend/src/controller/node.rs b/crates/backend/src/controller/node.rs index 491ac61..975017c 100644 --- a/crates/backend/src/controller/node.rs +++ b/crates/backend/src/controller/node.rs @@ -1,28 +1,36 @@ use crate::AppState; -use actix_web::{error::ErrorInternalServerError, web, HttpResponse, Responder}; -use entity::node_group; -use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait}; +use actix_web::{ + error::{ErrorBadRequest, ErrorInternalServerError}, + 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 uuid::Uuid; +#[derive(Serialize)] +struct NodeWithSensorData { + node: node::Model, + sensor_data: Vec, +} + #[derive(Deserialize)] pub struct CreateGroupWithoutId { name: String, } #[derive(Deserialize)] -pub struct CreateLicense { - name: String, - coord_la: f64, - coord_lo: f64, +pub struct CreateNode { + id: i32, group: uuid::Uuid, } #[derive(Serialize)] struct GroupWithNode { #[serde(flatten)] - group: entity::node_group::Model, - node: Vec, + group: node_group::Model, + node: Vec, } pub async fn get_nodes(state: web::Data) -> actix_web::Result { @@ -40,6 +48,33 @@ pub async fn get_nodes(state: web::Data) -> actix_web::Result) -> actix_web::Result { + 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 = 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( state: web::Data, group: web::Json, @@ -60,7 +95,7 @@ pub async fn create_group( pub async fn create_node( state: web::Data, - node: web::Json, + node: web::Json, ) -> actix_web::Result { let db = &state.db; @@ -78,18 +113,12 @@ pub async fn create_node( 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 { - id: ActiveValue::NotSet, - 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, + id: ActiveValue::Set(node.id), group: ActiveValue::Set(node.group), }; @@ -98,6 +127,7 @@ pub async fn create_node( Ok(web::Json(result)) } +/* pub async fn delete_node( state: web::Data, path: web::Path, @@ -113,3 +143,12 @@ pub async fn delete_node( Ok(HttpResponse::Ok().finish()) } +*/ + +fn mac_to_i32(mac: &str) -> Result { + // 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) +} diff --git a/crates/backend/src/routes.rs b/crates/backend/src/routes.rs index 969c250..b9ea427 100644 --- a/crates/backend/src/routes.rs +++ b/crates/backend/src/routes.rs @@ -19,7 +19,8 @@ pub fn config(cfg: &mut web::ServiceConfig) { .get(node::get_nodes) .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)), ); } diff --git a/crates/entity/src/crates/entity/src/lib.rs b/crates/entity/src/crates/entity/src/lib.rs new file mode 100644 index 0000000..cfbbcef --- /dev/null +++ b/crates/entity/src/crates/entity/src/lib.rs @@ -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; diff --git a/crates/entity/src/crates/entity/src/node.rs b/crates/entity/src/crates/entity/src/node.rs new file mode 100644 index 0000000..d121dec --- /dev/null +++ b/crates/entity/src/crates/entity/src/node.rs @@ -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 for Entity { + fn to() -> RelationDef { + Relation::NodeGroup.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SensorData.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/entity/src/crates/entity/src/node_group.rs b/crates/entity/src/crates/entity/src/node_group.rs new file mode 100644 index 0000000..e20b6a5 --- /dev/null +++ b/crates/entity/src/crates/entity/src/node_group.rs @@ -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 for Entity { + fn to() -> RelationDef { + Relation::Node.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/entity/src/crates/entity/src/prelude.rs b/crates/entity/src/crates/entity/src/prelude.rs new file mode 100644 index 0000000..ad04007 --- /dev/null +++ b/crates/entity/src/crates/entity/src/prelude.rs @@ -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; diff --git a/crates/entity/src/crates/entity/src/sensor_data.rs b/crates/entity/src/crates/entity/src/sensor_data.rs new file mode 100644 index 0000000..613d9fb --- /dev/null +++ b/crates/entity/src/crates/entity/src/sensor_data.rs @@ -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, +} + +#[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 for Entity { + fn to() -> RelationDef { + Relation::Node.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/entity/src/crates/entity/src/user.rs b/crates/entity/src/crates/entity/src/user.rs new file mode 100644 index 0000000..0fa6320 --- /dev/null +++ b/crates/entity/src/crates/entity/src/user.rs @@ -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 {} diff --git a/crates/entity/src/lib.rs b/crates/entity/src/lib.rs index a79fe3a..cfbbcef 100644 --- a/crates/entity/src/lib.rs +++ b/crates/entity/src/lib.rs @@ -4,4 +4,5 @@ pub mod prelude; pub mod node; pub mod node_group; +pub mod sensor_data; pub mod user; diff --git a/crates/entity/src/node.rs b/crates/entity/src/node.rs index ba10999..f6e9b35 100644 --- a/crates/entity/src/node.rs +++ b/crates/entity/src/node.rs @@ -3,28 +3,11 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "node")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, - 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 id: i32, pub group: Uuid, } @@ -38,6 +21,8 @@ pub enum Relation { on_delete = "Cascade" )] NodeGroup, + #[sea_orm(has_many = "super::sensor_data::Entity")] + SensorData, } impl Related for Entity { @@ -46,4 +31,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::SensorData.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/entity/src/prelude.rs b/crates/entity/src/prelude.rs index b3fea58..ad04007 100644 --- a/crates/entity/src/prelude.rs +++ b/crates/entity/src/prelude.rs @@ -2,4 +2,5 @@ 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; diff --git a/crates/entity/src/sensor_data.rs b/crates/entity/src/sensor_data.rs new file mode 100644 index 0000000..dc49a6d --- /dev/null +++ b/crates/entity/src/sensor_data.rs @@ -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 for Entity { + fn to() -> RelationDef { + Relation::Node.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/migration/src/lib.rs b/crates/migration/src/lib.rs index 9dc29f7..8400a9d 100644 --- a/crates/migration/src/lib.rs +++ b/crates/migration/src/lib.rs @@ -2,6 +2,7 @@ pub use sea_orm_migration::prelude::*; mod m20241008_091626_create_table_user; mod m20241008_095058_create_table_node; +mod m20241013_134422_create_table_sensor_data; pub struct Migrator; @@ -11,6 +12,7 @@ impl MigratorTrait for Migrator { vec![ Box::new(m20241008_091626_create_table_user::Migration), Box::new(m20241008_095058_create_table_node::Migration), + Box::new(m20241013_134422_create_table_sensor_data::Migration), ] } } diff --git a/crates/migration/src/m20241008_095058_create_table_node.rs b/crates/migration/src/m20241008_095058_create_table_node.rs index 9a7e9aa..8f4bc19 100644 --- a/crates/migration/src/m20241008_095058_create_table_node.rs +++ b/crates/migration/src/m20241008_095058_create_table_node.rs @@ -26,21 +26,7 @@ impl MigrationTrait for Migration { Table::create() .table(Node::Table) .if_not_exists() - .col( - 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(integer(Node::Id).primary_key()) .col(uuid(Node::Group)) .foreign_key( ForeignKey::create() @@ -71,25 +57,15 @@ impl MigrationTrait for Migration { } #[derive(DeriveIden)] -enum Node { +pub enum Node { Table, - Id, - Name, - Status, - CoordLa, - CoordLo, - Temperature, // def: -127 - BatteryMinimum, // def: -127 - BatteryCurrent, // def: -127 - BatteryMaximum, // def: -127 - Voltage, // def: -127 - Uptime, // def: 0 + Id, // Mac address Group, } #[derive(DeriveIden)] enum NodeGroup { Table, - Id, - Name, + Id, // Uuid + Name, // Groupname } diff --git a/crates/migration/src/m20241013_134422_create_table_sensor_data.rs b/crates/migration/src/m20241013_134422_create_table_sensor_data.rs new file mode 100644 index 0000000..e93b57f --- /dev/null +++ b/crates/migration/src/m20241013_134422_create_table_sensor_data.rs @@ -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, +}