Compare commits
7 commits
d76bf0f711
...
0b74f358d5
Author | SHA1 | Date | |
---|---|---|---|
0b74f358d5 | |||
59a8152855 | |||
69414af9d5 | |||
e3f3f85ef5 | |||
45409168d6 | |||
470b9c905b | |||
b28db015b4 |
11 changed files with 108 additions and 73 deletions
BIN
Cargo.lock
generated
BIN
Cargo.lock
generated
Binary file not shown.
28
api.v1.json
28
api.v1.json
|
@ -6,7 +6,7 @@
|
|||
"url": "https://git.serguzim.me/emgauwa/"
|
||||
},
|
||||
"title": "Emgauwa API v1",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"description": "Server API to manage an Emgauwa system."
|
||||
},
|
||||
"tags": [
|
||||
|
@ -524,8 +524,10 @@
|
|||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"active_schedule": {
|
||||
"override_schedule": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"description": "NULL will remove the override schedule, missing field will not change the override schedule",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/schedule_id"
|
||||
|
@ -1074,11 +1076,6 @@
|
|||
"type": "string",
|
||||
"example": "Garden Controller"
|
||||
},
|
||||
"ip": {
|
||||
"type": "string",
|
||||
"format": "ipv4",
|
||||
"example": "224.73.153.12"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -1116,7 +1113,22 @@
|
|||
"$ref": "#/components/schemas/controller_id"
|
||||
},
|
||||
"active_schedule": {
|
||||
"$ref": "#/components/schemas/schedule_simple"
|
||||
"nullable": true,
|
||||
"description": "NULL when unknown (usually because controller is not connected)",
|
||||
"allOf": [
|
||||
{
|
||||
"$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": {
|
||||
"type": "array",
|
||||
|
|
2
core.pkl
2
core.pkl
|
@ -1,4 +1,4 @@
|
|||
amends "package://emgauwa.app/pkl/emgauwa@0.1.1#/core.pkl"
|
||||
amends "package://emgauwa.app/pkl/emgauwa@0.2.1#/core.pkl"
|
||||
|
||||
logging {
|
||||
level = "DEBUG"
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use actix::Addr;
|
||||
use actix_web::{delete, get, HttpResponse, post, put, web};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use sqlx::pool::PoolConnection;
|
||||
|
||||
use actix_web::{delete, get, post, put, web, HttpResponse};
|
||||
use emgauwa_common::db::{DbController, DbMacro};
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay};
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate};
|
||||
use crate::app_state;
|
||||
use emgauwa_common::types::{
|
||||
ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
|
||||
};
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::app_state;
|
||||
use crate::app_state::AppState;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
|
||||
|
@ -133,8 +134,8 @@ pub async fn execute(
|
|||
let affected_controllers = collect_affected_controllers(&mut pool_conn, &actions).await?;
|
||||
|
||||
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
|
||||
.send(app_state::Action {
|
||||
|
@ -159,10 +160,11 @@ async fn collect_affected_controllers(
|
|||
.iter()
|
||||
.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)?;
|
||||
affected_controllers.push(controller);
|
||||
}
|
||||
|
@ -177,7 +179,9 @@ async fn collect_affected_relays(
|
|||
let mut affected_relays: Vec<Relay> = Vec::new();
|
||||
|
||||
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
|
||||
{
|
||||
continue;
|
||||
|
|
|
@ -3,23 +3,40 @@ use actix_web::{get, post, put, web, HttpResponse};
|
|||
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbTag};
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, FromDbModel, Relay};
|
||||
use emgauwa_common::types::{
|
||||
ControllerWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate,
|
||||
};
|
||||
use emgauwa_common::utils;
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::app_state;
|
||||
use crate::app_state::AppState;
|
||||
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")]
|
||||
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 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))
|
||||
}
|
||||
|
@ -27,6 +44,7 @@ pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, Emgauw
|
|||
#[get("/relays/tag/{tag}")]
|
||||
pub async fn tagged(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
path: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
@ -37,7 +55,9 @@ pub async fn tagged(
|
|||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -45,6 +65,7 @@ pub async fn tagged(
|
|||
#[get("/controllers/{controller_id}/relays")]
|
||||
pub async fn index_for_controller(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
path: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
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 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))
|
||||
}
|
||||
|
||||
#[get("/controllers/{controller_id}/relays/{relay_num}")]
|
||||
pub async fn show_for_controller(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
path: web::Path<(String, i64)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
@ -76,12 +100,14 @@ pub async fn show_for_controller(
|
|||
.await?
|
||||
.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?
|
||||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let return_relay = Relay::from_db_model(&mut pool_conn, relay)?;
|
||||
Ok(HttpResponse::Ok().json(return_relay))
|
||||
let mut relay = Relay::from_db_model(&mut pool_conn, db_relay)?;
|
||||
load_state_for_relay(&mut relay, &app_state).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(relay))
|
||||
}
|
||||
|
||||
#[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 {
|
||||
relay.set_tags(&mut pool_conn, tags.as_slice()).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
|
||||
.send(app_state::Action {
|
||||
|
|
|
@ -3,7 +3,10 @@ use actix_web::{delete, get, post, put, web, HttpResponse};
|
|||
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbSchedule, DbTag};
|
||||
use emgauwa_common::errors::{ApiError, DatabaseError, EmgauwaError};
|
||||
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 sqlx::pool::PoolConnection;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
|
|
@ -4,6 +4,7 @@ use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
|||
use emgauwa_common::models::{FromDbModel, Tag};
|
||||
use emgauwa_common::types::RequestTagCreate;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
|
||||
#[get("/tags")]
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
mod handlers;
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, StreamHandler};
|
||||
use actix_web_actors::ws;
|
||||
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::errors::EmgauwaError;
|
||||
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::utils::flatten_result;
|
||||
|
||||
mod handlers;
|
||||
|
||||
pub struct ControllersWs {
|
||||
pub pool: Pool<Sqlite>,
|
||||
|
@ -32,13 +32,9 @@ impl Actor for ControllersWs {
|
|||
|
||||
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
||||
if let Some(controller_uid) = &self.controller_uid {
|
||||
let flat_res = flatten_result(
|
||||
block_on(self.app_state.send(DisconnectController {
|
||||
controller_uid: controller_uid.clone(),
|
||||
}))
|
||||
.map_err(EmgauwaError::from),
|
||||
);
|
||||
if let Err(err) = flat_res {
|
||||
if let Err(err) = block_on(self.app_state.send(DisconnectController {
|
||||
controller_uid: controller_uid.clone(),
|
||||
})).unwrap_or_else(|err| Err(EmgauwaError::from(err))) {
|
||||
log::error!("Error disconnecting controller: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,17 @@ use actix::{Actor, Arbiter};
|
|||
use actix_cors::Cors;
|
||||
use actix_web::middleware::TrailingSlash;
|
||||
use actix_web::{middleware, web, App, HttpServer};
|
||||
use serde_json::Value;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use emgauwa_common::db::DbController;
|
||||
use emgauwa_common::errors::EmgauwaError;
|
||||
use emgauwa_common::utils::{drop_privileges, init_logging};
|
||||
use serde_json::Value;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::app_state::AppState;
|
||||
|
||||
mod app_state;
|
||||
mod handlers;
|
||||
mod settings;
|
||||
mod utils;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
|
@ -25,7 +23,7 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
let listener = TcpListener::bind(format!("{}:{}", settings.server.host, settings.server.port))?;
|
||||
drop_privileges(&settings.permissions)?;
|
||||
|
||||
init_logging(&settings.logging.level)?;
|
||||
init_logging(&settings.logging)?;
|
||||
|
||||
let pool_size = 10;
|
||||
let pool = emgauwa_common::db::init(&settings.database, pool_size).await?;
|
||||
|
@ -79,6 +77,7 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
.external_urls_from_iter_unchecked([("/api/v1.json", api_v1_json.clone())]),
|
||||
)
|
||||
.service(web::redirect("/api/docs", "/api/docs/"))
|
||||
.service(web::redirect("/", "/api/docs/"))
|
||||
.service(
|
||||
web::scope("/api/v1")
|
||||
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue