Compare commits

..

No commits in common. "0b74f358d578c10b2c8da670293da37f0ffe30e4" and "d76bf0f711dc5e620ddc489ae762cb88a3d8e349" have entirely different histories.

11 changed files with 73 additions and 108 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.6.0", "version": "0.5.1",
"description": "Server API to manage an Emgauwa system." "description": "Server API to manage an Emgauwa system."
}, },
"tags": [ "tags": [
@ -524,10 +524,8 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"override_schedule": { "active_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"
@ -1076,6 +1074,11 @@
"type": "string", "type": "string",
"example": "Garden Controller" "example": "Garden Controller"
}, },
"ip": {
"type": "string",
"format": "ipv4",
"example": "224.73.153.12"
},
"active": { "active": {
"type": "boolean" "type": "boolean"
}, },
@ -1113,22 +1116,7 @@
"$ref": "#/components/schemas/controller_id" "$ref": "#/components/schemas/controller_id"
}, },
"active_schedule": { "active_schedule": {
"nullable": true,
"description": "NULL when unknown (usually because controller is not connected)",
"allOf": [
{
"$ref": "#/components/schemas/schedule_simple" "$ref": "#/components/schemas/schedule_simple"
}
]
},
"override_schedule": {
"nullable": true,
"description": "NULL when unknown (usually because controller is not connected)",
"allOf": [
{
"$ref": "#/components/schemas/schedule_simple"
}
]
}, },
"schedules": { "schedules": {
"type": "array", "type": "array",

View file

@ -1,4 +1,4 @@
amends "package://emgauwa.app/pkl/emgauwa@0.2.1#/core.pkl" amends "package://emgauwa.app/pkl/emgauwa@0.1.1#/core.pkl"
logging { logging {
level = "DEBUG" level = "DEBUG"

View file

@ -1,15 +1,14 @@
use actix::Addr; use actix::Addr;
use actix_web::{delete, get, post, put, web, HttpResponse}; use actix_web::{delete, get, HttpResponse, post, put, web};
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::{ use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate};
ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
};
use sqlx::pool::PoolConnection;
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;
@ -134,8 +133,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 =
collect_affected_relays(&mut pool_conn, &mut actions, &controller).await?; let affected_relays = collect_affected_relays(&mut pool_conn, &mut actions, &controller).await?;
app_state app_state
.send(app_state::Action { .send(app_state::Action {
@ -160,11 +159,10 @@ 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) let controller = DbController::get(pool_conn, controller_id).await?
.await?
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
affected_controllers.push(controller); affected_controllers.push(controller);
} }
@ -179,9 +177,7 @@ 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 if affected_relays.iter().any(|relay| relay.r.id == action.relay.r.id)
.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,40 +3,23 @@ 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::{ControllerWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate}; use emgauwa_common::types::{
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( pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> {
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 mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?; let 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))
} }
@ -44,7 +27,6 @@ pub async fn index(
#[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?;
@ -55,9 +37,7 @@ 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))
} }
@ -65,7 +45,6 @@ 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?;
@ -79,16 +58,13 @@ 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 mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?; let 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?;
@ -100,14 +76,12 @@ pub async fn show_for_controller(
.await? .await?
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
let db_relay = DbRelay::get_by_controller_and_num(&mut pool_conn, &controller, relay_num) let relay = DbRelay::get_by_controller_and_num(&mut pool_conn, &controller, relay_num)
.await? .await?
.ok_or(DatabaseError::NotFound)?; .ok_or(DatabaseError::NotFound)?;
let mut relay = Relay::from_db_model(&mut pool_conn, db_relay)?; let return_relay = Relay::from_db_model(&mut pool_conn, relay)?;
load_state_for_relay(&mut relay, &app_state).await?; Ok(HttpResponse::Ok().json(return_relay))
Ok(HttpResponse::Ok().json(relay))
} }
#[put("/controllers/{controller_id}/relays/{relay_num}")] #[put("/controllers/{controller_id}/relays/{relay_num}")]
@ -150,25 +124,24 @@ 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 mut return_relay = Relay::from_db_model(&mut pool_conn, relay)?; let 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,10 +3,7 @@ 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::{ use emgauwa_common::types::{ControllerWsAction, RequestScheduleCreate, RequestScheduleGetTagged, RequestScheduleUpdate, ScheduleUid};
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,7 +4,6 @@ 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,9 +32,13 @@ 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 {
if let Err(err) = block_on(self.app_state.send(DisconnectController { let flat_res = flatten_result(
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,17 +4,19 @@ 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> {
@ -23,7 +25,7 @@ async fn main() -> Result<(), std::io::Error> {
let listener = TcpListener::bind(format!("{}:{}", settings.server.host, settings.server.port))?; let listener = TcpListener::bind(format!("{}:{}", settings.server.host, settings.server.port))?;
drop_privileges(&settings.permissions)?; drop_privileges(&settings.permissions)?;
init_logging(&settings.logging)?; init_logging(&settings.logging.level)?;
let pool_size = 10; let pool_size = 10;
let pool = emgauwa_common::db::init(&settings.database, pool_size).await?; let pool = emgauwa_common::db::init(&settings.database, pool_size).await?;
@ -77,7 +79,6 @@ async fn main() -> Result<(), std::io::Error> {
.external_urls_from_iter_unchecked([("/api/v1.json", api_v1_json.clone())]), .external_urls_from_iter_unchecked([("/api/v1.json", api_v1_json.clone())]),
) )
.service(web::redirect("/api/docs", "/api/docs/")) .service(web::redirect("/api/docs", "/api/docs/"))
.service(web::redirect("/", "/api/docs/"))
.service( .service(
web::scope("/api/v1") web::scope("/api/v1")
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim)) .wrap(middleware::NormalizePath::new(TrailingSlash::Trim))

7
src/utils.rs Normal file
View file

@ -0,0 +1,7 @@
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),
}
}