Compare commits

...

4 commits

10 changed files with 96 additions and 64 deletions

BIN
Cargo.lock generated

Binary file not shown.

View file

@ -6,7 +6,7 @@
"url": "https://git.serguzim.me/emgauwa/" "url": "https://git.serguzim.me/emgauwa/"
}, },
"title": "Emgauwa API v1", "title": "Emgauwa API v1",
"version": "0.5.1", "version": "0.6.0",
"description": "Server API to manage an Emgauwa system." "description": "Server API to manage an Emgauwa system."
}, },
"tags": [ "tags": [
@ -524,8 +524,10 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"active_schedule": { "override_schedule": {
"type": "object", "type": "object",
"nullable": true,
"description": "NULL will remove the override schedule, missing field will not change the override schedule",
"properties": { "properties": {
"id": { "id": {
"$ref": "#/components/schemas/schedule_id" "$ref": "#/components/schemas/schedule_id"
@ -1118,6 +1120,14 @@
"active_schedule": { "active_schedule": {
"$ref": "#/components/schemas/schedule_simple" "$ref": "#/components/schemas/schedule_simple"
}, },
"override_schedule": {
"nullable": true,
"allOf": [
{
"$ref": "#/components/schemas/schedule_simple"
}
]
},
"schedules": { "schedules": {
"type": "array", "type": "array",
"maxItems": 7, "maxItems": 7,

View file

@ -1,14 +1,15 @@
use actix::Addr; use actix::Addr;
use actix_web::{delete, get, HttpResponse, post, put, web}; use actix_web::{delete, get, post, put, web, HttpResponse};
use sqlx::{Pool, Sqlite};
use sqlx::pool::PoolConnection;
use emgauwa_common::db::{DbController, DbMacro}; use emgauwa_common::db::{DbController, DbMacro};
use emgauwa_common::errors::{DatabaseError, EmgauwaError}; use emgauwa_common::errors::{DatabaseError, EmgauwaError};
use emgauwa_common::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay}; use emgauwa_common::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay};
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate}; use emgauwa_common::types::{
use crate::app_state; ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
};
use sqlx::pool::PoolConnection;
use sqlx::{Pool, Sqlite};
use crate::app_state;
use crate::app_state::AppState; use crate::app_state::AppState;
use crate::handlers::EmgauwaMessage; use crate::handlers::EmgauwaMessage;
@ -133,8 +134,8 @@ pub async fn execute(
let affected_controllers = collect_affected_controllers(&mut pool_conn, &actions).await?; let affected_controllers = collect_affected_controllers(&mut pool_conn, &actions).await?;
for controller in affected_controllers { for controller in affected_controllers {
let affected_relays =
let affected_relays = collect_affected_relays(&mut pool_conn, &mut actions, &controller).await?; collect_affected_relays(&mut pool_conn, &mut actions, &controller).await?;
app_state app_state
.send(app_state::Action { .send(app_state::Action {
@ -159,10 +160,11 @@ async fn collect_affected_controllers(
.iter() .iter()
.any(|controller| controller.id == controller_id) .any(|controller| controller.id == controller_id)
{ {
continue continue;
} }
let controller = DbController::get(pool_conn, controller_id).await? let controller = DbController::get(pool_conn, controller_id)
.await?
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
affected_controllers.push(controller); affected_controllers.push(controller);
} }
@ -177,7 +179,9 @@ async fn collect_affected_relays(
let mut affected_relays: Vec<Relay> = Vec::new(); let mut affected_relays: Vec<Relay> = Vec::new();
for action in actions { for action in actions {
if affected_relays.iter().any(|relay| relay.r.id == action.relay.r.id) if affected_relays
.iter()
.any(|relay| relay.r.id == action.relay.r.id)
|| action.relay.r.controller_id != controller.id || action.relay.r.controller_id != controller.id
{ {
continue; continue;

View file

@ -3,23 +3,40 @@ use actix_web::{get, post, put, web, HttpResponse};
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbTag}; use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbTag};
use emgauwa_common::errors::{DatabaseError, EmgauwaError}; use emgauwa_common::errors::{DatabaseError, EmgauwaError};
use emgauwa_common::models::{convert_db_list, FromDbModel, Relay}; use emgauwa_common::models::{convert_db_list, FromDbModel, Relay};
use emgauwa_common::types::{ use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate};
ControllerWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate,
};
use emgauwa_common::utils;
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use crate::app_state; use crate::app_state;
use crate::app_state::AppState; use crate::app_state::AppState;
use crate::handlers::EmgauwaMessage; use crate::handlers::EmgauwaMessage;
pub async fn get_stated_relays(app_state: &Addr<AppState>) -> Result<Vec<Relay>, EmgauwaError> {
app_state.send(app_state::GetRelays {}).await?
}
pub async fn load_state_for_relay(relay: &mut Relay, app_state: &Addr<AppState>) -> Result<(), EmgauwaError>{
let stated_relays = get_stated_relays(app_state).await?;
relay.find_and_apply_state(&stated_relays);
Ok(())
}
pub async fn load_state_for_relays(relays: &mut [Relay], app_state: &Addr<AppState>) -> Result<(), EmgauwaError>{
let stated_relays = get_stated_relays(app_state).await?;
relays.iter_mut().for_each(|r| r.find_and_apply_state(&stated_relays));
Ok(())
}
#[get("/relays")] #[get("/relays")]
pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> { pub async fn index(
pool: web::Data<Pool<Sqlite>>,
app_state: web::Data<Addr<AppState>>,
) -> Result<HttpResponse, EmgauwaError> {
let mut pool_conn = pool.acquire().await?; let mut pool_conn = pool.acquire().await?;
let db_relays = DbRelay::get_all(&mut pool_conn).await?; let db_relays = DbRelay::get_all(&mut pool_conn).await?;
let relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?; let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
load_state_for_relays(&mut relays, &app_state).await?;
Ok(HttpResponse::Ok().json(relays)) Ok(HttpResponse::Ok().json(relays))
} }
@ -27,6 +44,7 @@ pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, Emgauw
#[get("/relays/tag/{tag}")] #[get("/relays/tag/{tag}")]
pub async fn tagged( pub async fn tagged(
pool: web::Data<Pool<Sqlite>>, pool: web::Data<Pool<Sqlite>>,
app_state: web::Data<Addr<AppState>>,
path: web::Path<(String,)>, path: web::Path<(String,)>,
) -> Result<HttpResponse, EmgauwaError> { ) -> Result<HttpResponse, EmgauwaError> {
let mut pool_conn = pool.acquire().await?; let mut pool_conn = pool.acquire().await?;
@ -37,7 +55,9 @@ pub async fn tagged(
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
let db_relays = DbRelay::get_by_tag(&mut pool_conn, &tag_db).await?; let db_relays = DbRelay::get_by_tag(&mut pool_conn, &tag_db).await?;
let relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
load_state_for_relays(&mut relays, &app_state).await?;
Ok(HttpResponse::Ok().json(relays)) Ok(HttpResponse::Ok().json(relays))
} }
@ -45,6 +65,7 @@ pub async fn tagged(
#[get("/controllers/{controller_id}/relays")] #[get("/controllers/{controller_id}/relays")]
pub async fn index_for_controller( pub async fn index_for_controller(
pool: web::Data<Pool<Sqlite>>, pool: web::Data<Pool<Sqlite>>,
app_state: web::Data<Addr<AppState>>,
path: web::Path<(String,)>, path: web::Path<(String,)>,
) -> Result<HttpResponse, EmgauwaError> { ) -> Result<HttpResponse, EmgauwaError> {
let mut pool_conn = pool.acquire().await?; let mut pool_conn = pool.acquire().await?;
@ -58,13 +79,16 @@ pub async fn index_for_controller(
let db_relays = controller.get_relays(&mut pool_conn).await?; let db_relays = controller.get_relays(&mut pool_conn).await?;
let relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?; let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
load_state_for_relays(&mut relays, &app_state).await?;
Ok(HttpResponse::Ok().json(relays)) Ok(HttpResponse::Ok().json(relays))
} }
#[get("/controllers/{controller_id}/relays/{relay_num}")] #[get("/controllers/{controller_id}/relays/{relay_num}")]
pub async fn show_for_controller( pub async fn show_for_controller(
pool: web::Data<Pool<Sqlite>>, pool: web::Data<Pool<Sqlite>>,
app_state: web::Data<Addr<AppState>>,
path: web::Path<(String, i64)>, path: web::Path<(String, i64)>,
) -> Result<HttpResponse, EmgauwaError> { ) -> Result<HttpResponse, EmgauwaError> {
let mut pool_conn = pool.acquire().await?; let mut pool_conn = pool.acquire().await?;
@ -76,12 +100,14 @@ pub async fn show_for_controller(
.await? .await?
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
let relay = DbRelay::get_by_controller_and_num(&mut pool_conn, &controller, relay_num) let db_relay = DbRelay::get_by_controller_and_num(&mut pool_conn, &controller, relay_num)
.await? .await?
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
let return_relay = Relay::from_db_model(&mut pool_conn, relay)?; let mut relay = Relay::from_db_model(&mut pool_conn, db_relay)?;
Ok(HttpResponse::Ok().json(return_relay)) load_state_for_relay(&mut relay, &app_state).await?;
Ok(HttpResponse::Ok().json(relay))
} }
#[put("/controllers/{controller_id}/relays/{relay_num}")] #[put("/controllers/{controller_id}/relays/{relay_num}")]
@ -124,24 +150,25 @@ pub async fn update_for_controller(
} }
} }
if let Some(s_uid) = &data.active_schedule {
let schedule = s_uid.get_schedule(&mut pool_conn).await?;
DbJunctionRelaySchedule::set_schedule(
&mut pool_conn,
&relay,
&schedule,
utils::get_weekday(),
)
.await?;
}
if let Some(tags) = &data.tags { if let Some(tags) = &data.tags {
relay.set_tags(&mut pool_conn, tags.as_slice()).await?; relay.set_tags(&mut pool_conn, tags.as_slice()).await?;
} }
let relay = relay.reload(&mut pool_conn).await?; let relay = relay.reload(&mut pool_conn).await?;
let return_relay = Relay::from_db_model(&mut pool_conn, relay)?; let mut return_relay = Relay::from_db_model(&mut pool_conn, relay)?;
load_state_for_relay(&mut return_relay, &app_state).await?;
match &data.override_schedule {
Some(Some(s_uid)) => { // We want to set an override schedule
let schedule = s_uid.get_schedule(&mut pool_conn).await?;
return_relay.override_schedule = Some(schedule);
}
Some(None) => { // We want to unset the override schedule
return_relay.override_schedule = None;
}
None => {} // We want to keep the override schedule as is
}
app_state app_state
.send(app_state::Action { .send(app_state::Action {

View file

@ -3,7 +3,10 @@ use actix_web::{delete, get, post, put, web, HttpResponse};
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbSchedule, DbTag}; use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbSchedule, DbTag};
use emgauwa_common::errors::{ApiError, DatabaseError, EmgauwaError}; use emgauwa_common::errors::{ApiError, DatabaseError, EmgauwaError};
use emgauwa_common::models::{convert_db_list, FromDbModel, Schedule}; use emgauwa_common::models::{convert_db_list, FromDbModel, Schedule};
use emgauwa_common::types::{ControllerWsAction, RequestScheduleCreate, RequestScheduleGetTagged, RequestScheduleUpdate, ScheduleUid}; use emgauwa_common::types::{
ControllerWsAction, RequestScheduleCreate, RequestScheduleGetTagged, RequestScheduleUpdate,
ScheduleUid,
};
use itertools::Itertools; use itertools::Itertools;
use sqlx::pool::PoolConnection; use sqlx::pool::PoolConnection;
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};

View file

@ -4,6 +4,7 @@ use emgauwa_common::errors::{DatabaseError, EmgauwaError};
use emgauwa_common::models::{FromDbModel, Tag}; use emgauwa_common::models::{FromDbModel, Tag};
use emgauwa_common::types::RequestTagCreate; use emgauwa_common::types::RequestTagCreate;
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use crate::handlers::EmgauwaMessage; use crate::handlers::EmgauwaMessage;
#[get("/tags")] #[get("/tags")]

View file

@ -1,20 +1,20 @@
mod handlers;
use std::time::Instant; use std::time::Instant;
use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, StreamHandler}; use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, StreamHandler};
use actix_web_actors::ws; use actix_web_actors::ws;
use actix_web_actors::ws::ProtocolError; use actix_web_actors::ws::ProtocolError;
use futures::executor::block_on;
use sqlx::{Pool, Sqlite};
use sqlx::pool::PoolConnection;
use ws::Message;
use emgauwa_common::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT}; use emgauwa_common::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT};
use emgauwa_common::errors::EmgauwaError; use emgauwa_common::errors::EmgauwaError;
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid}; use emgauwa_common::types::{ControllerWsAction, EmgauwaUid};
use futures::executor::block_on;
use sqlx::pool::PoolConnection;
use sqlx::{Pool, Sqlite};
use ws::Message;
use crate::app_state::{AppState, DisconnectController}; use crate::app_state::{AppState, DisconnectController};
use crate::utils::flatten_result;
mod handlers;
pub struct ControllersWs { pub struct ControllersWs {
pub pool: Pool<Sqlite>, pub pool: Pool<Sqlite>,
@ -32,13 +32,9 @@ impl Actor for ControllersWs {
fn stopped(&mut self, _ctx: &mut Self::Context) { fn stopped(&mut self, _ctx: &mut Self::Context) {
if let Some(controller_uid) = &self.controller_uid { if let Some(controller_uid) = &self.controller_uid {
let flat_res = flatten_result( if let Err(err) = block_on(self.app_state.send(DisconnectController {
block_on(self.app_state.send(DisconnectController {
controller_uid: controller_uid.clone(), controller_uid: controller_uid.clone(),
})) })).unwrap_or_else(|err| Err(EmgauwaError::from(err))) {
.map_err(EmgauwaError::from),
);
if let Err(err) = flat_res {
log::error!("Error disconnecting controller: {:?}", err); log::error!("Error disconnecting controller: {:?}", err);
} }
} }

View file

@ -4,19 +4,17 @@ use actix::{Actor, Arbiter};
use actix_cors::Cors; use actix_cors::Cors;
use actix_web::middleware::TrailingSlash; use actix_web::middleware::TrailingSlash;
use actix_web::{middleware, web, App, HttpServer}; use actix_web::{middleware, web, App, HttpServer};
use serde_json::Value;
use utoipa_swagger_ui::SwaggerUi;
use emgauwa_common::db::DbController; use emgauwa_common::db::DbController;
use emgauwa_common::errors::EmgauwaError; use emgauwa_common::errors::EmgauwaError;
use emgauwa_common::utils::{drop_privileges, init_logging}; use emgauwa_common::utils::{drop_privileges, init_logging};
use serde_json::Value;
use utoipa_swagger_ui::SwaggerUi;
use crate::app_state::AppState; use crate::app_state::AppState;
mod app_state; mod app_state;
mod handlers; mod handlers;
mod settings; mod settings;
mod utils;
#[actix_web::main] #[actix_web::main]
async fn main() -> Result<(), std::io::Error> { async fn main() -> Result<(), std::io::Error> {

View file

@ -1,7 +0,0 @@
pub fn flatten_result<T, E>(res: Result<Result<T, E>, E>) -> Result<T, E> {
match res {
Ok(Ok(t)) => Ok(t),
Ok(Err(e)) => Err(e),
Err(e) => Err(e),
}
}