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/"
},
"title": "Emgauwa API v1",
"version": "0.6.0",
"version": "0.5.1",
"description": "Server API to manage an Emgauwa system."
},
"tags": [
@ -524,10 +524,8 @@
"name": {
"type": "string"
},
"override_schedule": {
"active_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"
@ -1076,6 +1074,11 @@
"type": "string",
"example": "Garden Controller"
},
"ip": {
"type": "string",
"format": "ipv4",
"example": "224.73.153.12"
},
"active": {
"type": "boolean"
},
@ -1113,22 +1116,7 @@
"$ref": "#/components/schemas/controller_id"
},
"active_schedule": {
"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"
}
]
"$ref": "#/components/schemas/schedule_simple"
},
"schedules": {
"type": "array",
@ -1300,4 +1288,4 @@
}
}
}
}
}

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 {
level = "DEBUG"

View file

@ -44,4 +44,4 @@ impl EmgauwaMessage for HttpResponseBuilder {
fn emgauwa_message(mut self, message: &str) -> HttpResponse {
self.json(json!({ "message": message }))
}
}
}

View file

@ -1,15 +1,14 @@
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::errors::{DatabaseError, EmgauwaError};
use emgauwa_common::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay};
use emgauwa_common::types::{
ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
};
use sqlx::pool::PoolConnection;
use sqlx::{Pool, Sqlite};
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate};
use crate::app_state;
use crate::app_state::AppState;
use crate::handlers::EmgauwaMessage;
@ -134,8 +133,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 {
@ -160,11 +159,10 @@ 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);
}
@ -179,9 +177,7 @@ 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;
@ -190,4 +186,4 @@ async fn collect_affected_relays(
affected_relays.push(action.relay.clone());
}
Ok(affected_relays)
}
}

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::errors::{DatabaseError, EmgauwaError};
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 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>>,
app_state: web::Data<Addr<AppState>>,
) -> Result<HttpResponse, EmgauwaError> {
pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> {
let mut pool_conn = pool.acquire().await?;
let db_relays = DbRelay::get_all(&mut pool_conn).await?;
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
load_state_for_relays(&mut relays, &app_state).await?;
let relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
Ok(HttpResponse::Ok().json(relays))
}
@ -44,7 +27,6 @@ pub async fn index(
#[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?;
@ -55,9 +37,7 @@ pub async fn tagged(
.ok_or(DatabaseError::NotFound)?;
let db_relays = DbRelay::get_by_tag(&mut pool_conn, &tag_db).await?;
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
load_state_for_relays(&mut relays, &app_state).await?;
let relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
Ok(HttpResponse::Ok().json(relays))
}
@ -65,7 +45,6 @@ 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?;
@ -79,16 +58,13 @@ pub async fn index_for_controller(
let db_relays = controller.get_relays(&mut pool_conn).await?;
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
load_state_for_relays(&mut relays, &app_state).await?;
let relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
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?;
@ -100,14 +76,12 @@ pub async fn show_for_controller(
.await?
.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?
.ok_or(DatabaseError::NotFound)?;
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))
let return_relay = Relay::from_db_model(&mut pool_conn, relay)?;
Ok(HttpResponse::Ok().json(return_relay))
}
#[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 {
relay.set_tags(&mut pool_conn, tags.as_slice()).await?;
}
let relay = relay.reload(&mut pool_conn).await?;
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
}
let return_relay = Relay::from_db_model(&mut pool_conn, relay)?;
app_state
.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::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};

View file

@ -4,7 +4,6 @@ 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")]

View file

@ -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};
mod handlers;
use crate::utils::flatten_result;
pub struct ControllersWs {
pub pool: Pool<Sqlite>,
@ -32,9 +32,13 @@ impl Actor for ControllersWs {
fn stopped(&mut self, _ctx: &mut Self::Context) {
if let Some(controller_uid) = &self.controller_uid {
if let Err(err) = block_on(self.app_state.send(DisconnectController {
controller_uid: controller_uid.clone(),
})).unwrap_or_else(|err| Err(EmgauwaError::from(err))) {
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 {
log::error!("Error disconnecting controller: {:?}", err);
}
}

View file

@ -4,17 +4,19 @@ 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> {
@ -23,7 +25,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)?;
init_logging(&settings.logging.level)?;
let pool_size = 10;
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())]),
)
.service(web::redirect("/api/docs", "/api/docs/"))
.service(web::redirect("/", "/api/docs/"))
.service(
web::scope("/api/v1")
.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),
}
}