Merge branch 'master' of http://mygit.internal/pierre/SolarEnergy
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
//! # Devices entities definition
 | 
					//! # Devices entities definition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::constants::StaticConstraints;
 | 
					use crate::constants::StaticConstraints;
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::{HashMap, HashSet};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Device information provided directly by the device during syncrhonisation.
 | 
					/// Device information provided directly by the device during syncrhonisation.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@@ -13,7 +13,7 @@ pub struct DeviceInfo {
 | 
				
			|||||||
    /// Device firmware / software version
 | 
					    /// Device firmware / software version
 | 
				
			||||||
    version: semver::Version,
 | 
					    version: semver::Version,
 | 
				
			||||||
    /// Maximum number of relay that the device can support
 | 
					    /// Maximum number of relay that the device can support
 | 
				
			||||||
    max_relays: usize,
 | 
					    pub max_relays: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl DeviceInfo {
 | 
					impl DeviceInfo {
 | 
				
			||||||
@@ -90,7 +90,7 @@ impl Default for DeviceRelayID {
 | 
				
			|||||||
pub struct DeviceRelay {
 | 
					pub struct DeviceRelay {
 | 
				
			||||||
    /// Device relay id. Should be unique across the whole application
 | 
					    /// Device relay id. Should be unique across the whole application
 | 
				
			||||||
    #[serde(default)]
 | 
					    #[serde(default)]
 | 
				
			||||||
    id: DeviceRelayID,
 | 
					    pub id: DeviceRelayID,
 | 
				
			||||||
    /// Human-readable name for the relay
 | 
					    /// Human-readable name for the relay
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
    /// Whether this relay can be turned on or not
 | 
					    /// Whether this relay can be turned on or not
 | 
				
			||||||
@@ -179,10 +179,73 @@ impl DeviceRelay {
 | 
				
			|||||||
            return Some("A specified conflicting relay does not exists!");
 | 
					            return Some("A specified conflicting relay does not exists!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO : check for loops
 | 
					        // Check for loops in dependencies
 | 
				
			||||||
 | 
					        if self.check_for_loop_in_dependencies(&HashSet::new(), &relays_map) {
 | 
				
			||||||
 | 
					            return Some("A loop was detected in relay dependencies!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if relay is in conflicts with one of its dependencies
 | 
				
			||||||
 | 
					        let mut all_dependencies = HashSet::new();
 | 
				
			||||||
 | 
					        let mut all_conflicts = HashSet::new();
 | 
				
			||||||
 | 
					        self.get_list_of_dependencies_and_conflicts(
 | 
				
			||||||
 | 
					            &mut all_dependencies,
 | 
				
			||||||
 | 
					            &mut all_conflicts,
 | 
				
			||||||
 | 
					            &relays_map,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        for conf_id in all_conflicts {
 | 
				
			||||||
 | 
					            if all_dependencies.contains(&conf_id) {
 | 
				
			||||||
 | 
					                return Some(
 | 
				
			||||||
 | 
					                    "The relay or one of its dependencies is in conflict with a dependency!",
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn check_for_loop_in_dependencies(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        visited: &HashSet<DeviceRelayID>,
 | 
				
			||||||
 | 
					        list: &HashMap<DeviceRelayID, &Self>,
 | 
				
			||||||
 | 
					    ) -> bool {
 | 
				
			||||||
 | 
					        let mut clone = visited.clone();
 | 
				
			||||||
 | 
					        clone.insert(self.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for d in &self.depends_on {
 | 
				
			||||||
 | 
					            if visited.contains(d) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if list
 | 
				
			||||||
 | 
					                .get(d)
 | 
				
			||||||
 | 
					                .expect("Missing a relay!")
 | 
				
			||||||
 | 
					                .check_for_loop_in_dependencies(&clone, list)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_list_of_dependencies_and_conflicts(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        deps_out: &mut HashSet<DeviceRelayID>,
 | 
				
			||||||
 | 
					        conflicts_out: &mut HashSet<DeviceRelayID>,
 | 
				
			||||||
 | 
					        list: &HashMap<DeviceRelayID, &Self>,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        for d in &self.depends_on {
 | 
				
			||||||
 | 
					            let dependency = list.get(d).expect("Missing a relay!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            deps_out.insert(dependency.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dependency.get_list_of_dependencies_and_conflicts(deps_out, conflicts_out, list);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for d in &self.conflicts_with {
 | 
				
			||||||
 | 
					            conflicts_out.insert(*d);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Device general information
 | 
					/// Device general information
 | 
				
			||||||
@@ -213,7 +276,7 @@ impl DeviceGeneralInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use crate::devices::device::DeviceRelay;
 | 
					    use crate::devices::device::{DeviceRelay, DeviceRelayID};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn check_device_relay_error() {
 | 
					    fn check_device_relay_error() {
 | 
				
			||||||
@@ -238,5 +301,66 @@ mod tests {
 | 
				
			|||||||
        assert!(bad_name.error(&[]).is_some());
 | 
					        assert!(bad_name.error(&[]).is_some());
 | 
				
			||||||
        assert_eq!(dep_on_unitary.error(&[unitary.clone()]), None);
 | 
					        assert_eq!(dep_on_unitary.error(&[unitary.clone()]), None);
 | 
				
			||||||
        assert!(dep_on_unitary.error(&[]).is_some());
 | 
					        assert!(dep_on_unitary.error(&[]).is_some());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Dependency loop
 | 
				
			||||||
 | 
					        let mut dep_cycle_1 = DeviceRelay {
 | 
				
			||||||
 | 
					            id: DeviceRelayID::default(),
 | 
				
			||||||
 | 
					            name: "dep_cycle_1".to_string(),
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let dep_cycle_2 = DeviceRelay {
 | 
				
			||||||
 | 
					            id: DeviceRelayID::default(),
 | 
				
			||||||
 | 
					            name: "dep_cycle_2".to_string(),
 | 
				
			||||||
 | 
					            depends_on: vec![dep_cycle_1.id],
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let dep_cycle_3 = DeviceRelay {
 | 
				
			||||||
 | 
					            id: DeviceRelayID::default(),
 | 
				
			||||||
 | 
					            name: "dep_cycle_3".to_string(),
 | 
				
			||||||
 | 
					            depends_on: vec![dep_cycle_2.id],
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        dep_cycle_1.depends_on = vec![dep_cycle_3.id];
 | 
				
			||||||
 | 
					        assert!(dep_cycle_1
 | 
				
			||||||
 | 
					            .error(&[dep_cycle_2.clone(), dep_cycle_3.clone()])
 | 
				
			||||||
 | 
					            .is_some());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dep_cycle_1.depends_on = vec![];
 | 
				
			||||||
 | 
					        assert!(dep_cycle_1.error(&[dep_cycle_2, dep_cycle_3]).is_none());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Impossible conflict
 | 
				
			||||||
 | 
					        let other_dep = DeviceRelay {
 | 
				
			||||||
 | 
					            id: DeviceRelayID::default(),
 | 
				
			||||||
 | 
					            name: "other_dep".to_string(),
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let mut second_dep = DeviceRelay {
 | 
				
			||||||
 | 
					            id: DeviceRelayID::default(),
 | 
				
			||||||
 | 
					            name: "second_dep".to_string(),
 | 
				
			||||||
 | 
					            conflicts_with: vec![other_dep.id],
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let target_relay = DeviceRelay {
 | 
				
			||||||
 | 
					            id: DeviceRelayID::default(),
 | 
				
			||||||
 | 
					            name: "target_relay".to_string(),
 | 
				
			||||||
 | 
					            depends_on: vec![other_dep.id, second_dep.id],
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert!(target_relay
 | 
				
			||||||
 | 
					            .error(&[other_dep.clone(), second_dep.clone()])
 | 
				
			||||||
 | 
					            .is_some());
 | 
				
			||||||
 | 
					        assert!(target_relay
 | 
				
			||||||
 | 
					            .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()])
 | 
				
			||||||
 | 
					            .is_some());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        second_dep.conflicts_with = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert!(target_relay
 | 
				
			||||||
 | 
					            .error(&[other_dep.clone(), second_dep.clone()])
 | 
				
			||||||
 | 
					            .is_none());
 | 
				
			||||||
 | 
					        assert!(target_relay
 | 
				
			||||||
 | 
					            .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()])
 | 
				
			||||||
 | 
					            .is_none());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
use crate::crypto::pki;
 | 
					use crate::crypto::pki;
 | 
				
			||||||
use crate::devices::device::{Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay};
 | 
					use crate::devices::device::{
 | 
				
			||||||
 | 
					    Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use crate::utils::time_utils::time_secs;
 | 
					use crate::utils::time_utils::time_secs;
 | 
				
			||||||
use openssl::x509::{X509Req, X509};
 | 
					use openssl::x509::{X509Req, X509};
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
@@ -194,10 +196,29 @@ impl DevicesList {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get the full list of relays
 | 
					    /// Get the full list of relays
 | 
				
			||||||
    pub fn relays_list(&mut self) -> Vec<DeviceRelay> {
 | 
					    pub fn relays_list(&self) -> Vec<DeviceRelay> {
 | 
				
			||||||
        self.0
 | 
					        self.0
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .flat_map(|(_id, d)| d.relays.clone())
 | 
					            .flat_map(|(_id, d)| d.relays.clone())
 | 
				
			||||||
            .collect()
 | 
					            .collect()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Create a new relay
 | 
				
			||||||
 | 
					    pub fn relay_create(&mut self, dev_id: &DeviceId, relay: DeviceRelay) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        let dev = self
 | 
				
			||||||
 | 
					            .0
 | 
				
			||||||
 | 
					            .get_mut(dev_id)
 | 
				
			||||||
 | 
					            .ok_or(DevicesListError::DeviceNotFound)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dev.relays.push(relay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.persist_dev_config(dev_id)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get a single relay
 | 
				
			||||||
 | 
					    pub fn relay_get_single(&self, relay_id: DeviceRelayID) -> Option<DeviceRelay> {
 | 
				
			||||||
 | 
					        self.relays_list().into_iter().find(|i| i.id == relay_id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::devices::device::{Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay};
 | 
					use crate::devices::device::{
 | 
				
			||||||
 | 
					    Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use crate::devices::devices_list::DevicesList;
 | 
					use crate::devices::devices_list::DevicesList;
 | 
				
			||||||
use crate::energy::consumption;
 | 
					use crate::energy::consumption;
 | 
				
			||||||
use crate::energy::consumption::EnergyConsumption;
 | 
					use crate::energy::consumption::EnergyConsumption;
 | 
				
			||||||
@@ -184,3 +186,29 @@ impl Handler<GetRelaysList> for EnergyActor {
 | 
				
			|||||||
        self.devices.relays_list()
 | 
					        self.devices.relays_list()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a new device relay
 | 
				
			||||||
 | 
					#[derive(Message)]
 | 
				
			||||||
 | 
					#[rtype(result = "anyhow::Result<()>")]
 | 
				
			||||||
 | 
					pub struct CreateDeviceRelay(pub DeviceId, pub DeviceRelay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler<CreateDeviceRelay> for EnergyActor {
 | 
				
			||||||
 | 
					    type Result = anyhow::Result<()>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle(&mut self, msg: CreateDeviceRelay, _ctx: &mut Context<Self>) -> Self::Result {
 | 
				
			||||||
 | 
					        self.devices.relay_create(&msg.0, msg.1)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get the information about a single relay
 | 
				
			||||||
 | 
					#[derive(Message)]
 | 
				
			||||||
 | 
					#[rtype(result = "Option<DeviceRelay>")]
 | 
				
			||||||
 | 
					pub struct GetSingleRelay(pub DeviceRelayID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler<GetSingleRelay> for EnergyActor {
 | 
				
			||||||
 | 
					    type Result = Option<DeviceRelay>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle(&mut self, msg: GetSingleRelay, _ctx: &mut Context<Self>) -> Self::Result {
 | 
				
			||||||
 | 
					        self.devices.relay_get_single(msg.0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -165,6 +165,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
				
			|||||||
                "/web_api/relays/list",
 | 
					                "/web_api/relays/list",
 | 
				
			||||||
                web::get().to(relays_controller::get_list),
 | 
					                web::get().to(relays_controller::get_list),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/web_api/relay/create",
 | 
				
			||||||
 | 
					                web::post().to(relays_controller::create),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // Devices API
 | 
					            // Devices API
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/devices_api/utils/time",
 | 
					                "/devices_api/utils/time",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,60 @@
 | 
				
			|||||||
 | 
					use crate::devices::device::{DeviceId, DeviceRelay};
 | 
				
			||||||
use crate::energy::energy_actor;
 | 
					use crate::energy::energy_actor;
 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
use actix_web::HttpResponse;
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get the full list of relays
 | 
					/// Get the full list of relays
 | 
				
			||||||
pub async fn get_list(actor: WebEnergyActor) -> HttpResult {
 | 
					pub async fn get_list(actor: WebEnergyActor) -> HttpResult {
 | 
				
			||||||
    let list = actor.send(energy_actor::GetRelaysList).await?;
 | 
					    let list = actor.send(energy_actor::GetRelaysList).await?;
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(list))
 | 
					    Ok(HttpResponse::Ok().json(list))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct CreateDeviceRelayRequest {
 | 
				
			||||||
 | 
					    device_id: DeviceId,
 | 
				
			||||||
 | 
					    #[serde(flatten)]
 | 
				
			||||||
 | 
					    relay: DeviceRelay,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a new relay
 | 
				
			||||||
 | 
					pub async fn create(actor: WebEnergyActor, req: web::Json<CreateDeviceRelayRequest>) -> HttpResult {
 | 
				
			||||||
 | 
					    let list = actor.send(energy_actor::GetRelaysList).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(e) = req.relay.error(&list) {
 | 
				
			||||||
 | 
					        log::error!("Invalid relay create query: {e}");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::BadRequest().json(e));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let Some(device) = actor
 | 
				
			||||||
 | 
					        .send(energy_actor::GetSingleDevice(req.device_id.clone()))
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        log::error!("Invalid relay create query: specified device does not exists!");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::NotFound().json("Linked device not found!"));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if device.relays.len() >= device.info.max_relays {
 | 
				
			||||||
 | 
					        log::error!("Invalid relay create query: too many relay for the target device!");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::BadRequest().json("Too many relays for the target device!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if actor
 | 
				
			||||||
 | 
					        .send(energy_actor::GetSingleRelay(req.relay.id))
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .is_some()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        log::error!("Invalid relay create query: A relay with the same ID already exists!");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::BadRequest().json("A relay with the same ID already exists!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create the device
 | 
				
			||||||
 | 
					    actor
 | 
				
			||||||
 | 
					        .send(energy_actor::CreateDeviceRelay(
 | 
				
			||||||
 | 
					            req.device_id.clone(),
 | 
				
			||||||
 | 
					            req.relay.clone(),
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					        .await??;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Accepted().finish())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ export class RelayApi {
 | 
				
			|||||||
      uri: "/relay/create",
 | 
					      uri: "/relay/create",
 | 
				
			||||||
      jsonData: {
 | 
					      jsonData: {
 | 
				
			||||||
        ...relay,
 | 
					        ...relay,
 | 
				
			||||||
 | 
					        id: undefined,
 | 
				
			||||||
        device_id: device.id,
 | 
					        device_id: device.id,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user