From 661b5004e88ac28926dc2fd3a969e8ea81683244 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Mon, 29 Apr 2024 01:11:52 +0200 Subject: [PATCH] Add macros --- ...71bf18ede519e6b950933f6751ef9cf7521c9.json | Bin 0 -> 573 bytes ...2b2bab2a9923c715295d8b1d937d33844d1a4.json | Bin 0 -> 364 bytes ...d8b11e95d56b2f166326301a0b6722fc1fd44.json | Bin 0 -> 876 bytes ...8f281279353d0b92625c46ef5cd09a672c031.json | Bin 0 -> 260 bytes ...be83d32bb0380b1fb76f90bf4e200636c7f6a.json | Bin 0 -> 253 bytes ...44f9571c88fad3316593edf0c72ddef24c73e.json | Bin 0 -> 266 bytes ...a11ec04dd0aab26139c19583af00e5baaba56.json | Bin 0 -> 596 bytes ...52a342012048741bdcd1ff9461bc46e1cf701.json | Bin 0 -> 798 bytes ...2870941545b095193612956c29d37a5a1b774.json | Bin 0 -> 362 bytes ...5a94af2e823a8d88ab2aa5ace8c2b56023004.json | Bin 0 -> 811 bytes ...bac76c8233c0feee15f4504bf74ea864716ce.json | Bin 0 -> 817 bytes ...646f825be186da276b113c0150aaad26b057c.json | Bin 0 -> 871 bytes ...6c2bc73174fb1921fe1260702f1eab87d979c.json | Bin 0 -> 254 bytes ...d5bfbfbc785fa795254120822b843ce63ff07.json | Bin 0 -> 252 bytes ...f4a2d40abdf3843d0c815399ea9d267cade5a.json | Bin 0 -> 574 bytes ...7be750a7bcf5d1185935e8386671c69dc7270.json | Bin 0 -> 560 bytes api.v1.yaml | 6 + emgauwa-core/src/handlers/v1/macros.rs | 161 +++++++++++++++++ emgauwa-core/src/handlers/v1/mod.rs | 1 + emgauwa-core/src/main.rs | 6 + emgauwa-lib/src/db/macro.rs | 166 ++++++++++++++++++ emgauwa-lib/src/db/macro_action.rs | 99 +++++++++++ emgauwa-lib/src/db/mod.rs | 4 + emgauwa-lib/src/models/macro.rs | 42 +++++ emgauwa-lib/src/models/macro_action.rs | 56 ++++++ emgauwa-lib/src/models/mod.rs | 4 + emgauwa-lib/src/types/request.rs | 37 +++- migrations/20231120000000_init.up.sql | 2 +- 28 files changed, 582 insertions(+), 2 deletions(-) create mode 100644 .sqlx/query-03bfe5bd71d673a621b6ad6af5f71bf18ede519e6b950933f6751ef9cf7521c9.json create mode 100644 .sqlx/query-1348af1c13719ffcd72c2a4c6712b2bab2a9923c715295d8b1d937d33844d1a4.json create mode 100644 .sqlx/query-2551c285e3e223311cff8e32022d8b11e95d56b2f166326301a0b6722fc1fd44.json create mode 100644 .sqlx/query-2e3528a386066c3fc11be1259298f281279353d0b92625c46ef5cd09a672c031.json create mode 100644 .sqlx/query-3adb7ab9cacd5a84d882e4c9860be83d32bb0380b1fb76f90bf4e200636c7f6a.json create mode 100644 .sqlx/query-486ed307f718754a3d4ea2c6fe944f9571c88fad3316593edf0c72ddef24c73e.json create mode 100644 .sqlx/query-49afbeaa1d32f8c2e6fea7a2d57a11ec04dd0aab26139c19583af00e5baaba56.json create mode 100644 .sqlx/query-8bdd41a11fd2ca0440b2d6c0dc752a342012048741bdcd1ff9461bc46e1cf701.json create mode 100644 .sqlx/query-96a0b9960daa8a10e04e22cba592870941545b095193612956c29d37a5a1b774.json create mode 100644 .sqlx/query-a0c9c1a108c6560b4f073c866415a94af2e823a8d88ab2aa5ace8c2b56023004.json create mode 100644 .sqlx/query-b6f6fd898dc3f1dbe2c39bf0445bac76c8233c0feee15f4504bf74ea864716ce.json create mode 100644 .sqlx/query-bcd9d4dd3641e6c84262ed5f6c5646f825be186da276b113c0150aaad26b057c.json create mode 100644 .sqlx/query-c138a9c659a7410e9935ad3f6a56c2bc73174fb1921fe1260702f1eab87d979c.json create mode 100644 .sqlx/query-cb0d76a3a1cfa439d48056c865cd5bfbfbc785fa795254120822b843ce63ff07.json create mode 100644 .sqlx/query-eee8820f59927c1560a5fd461b9f4a2d40abdf3843d0c815399ea9d267cade5a.json create mode 100644 .sqlx/query-fde0dca1aba5b490a9f5f6006677be750a7bcf5d1185935e8386671c69dc7270.json create mode 100644 emgauwa-core/src/handlers/v1/macros.rs create mode 100644 emgauwa-lib/src/db/macro.rs create mode 100644 emgauwa-lib/src/db/macro_action.rs create mode 100644 emgauwa-lib/src/models/macro.rs create mode 100644 emgauwa-lib/src/models/macro_action.rs diff --git a/.sqlx/query-03bfe5bd71d673a621b6ad6af5f71bf18ede519e6b950933f6751ef9cf7521c9.json b/.sqlx/query-03bfe5bd71d673a621b6ad6af5f71bf18ede519e6b950933f6751ef9cf7521c9.json new file mode 100644 index 0000000000000000000000000000000000000000..ce5073bfb449296706a9e1ce0edec68ad8649f48 GIT binary patch literal 573 zcma)2T}#6-6n*cn2zeAAn%1UP5J9OZOvE|xL1ZM&jSZyjY*TQI{dbeJ(G3yILm=m# zkDIfL5W<45Th;)+K=}D-p$yyf19Xy!+P7k zf!-ArzJZtOy*3sgo;!aNx1-jKYu9n8R5Cq4Nd5hoqU`N0aLtC=l^Ca7LdHc-1kF=M o6T&GIl*vrygiCS`0y0tnGRB*a+CZAlv^|W`|8A0YIVA9W=aLaZ zl{h-=HN7YF^J8x&O>ZLnH*0@RV7;%Oz6g-#=KYQIDE%;!@0Yr%$!rvPAdjm-=}~$k zV$dZ7ga|pS2xj(TGD%1 zmGp!@d}Go+7Oo3IVq<>0DFBeP%cga+wJ2=ddY#sy9rYR*?j)ogj$P~oo|o8y1YSVZ pwo(;LGR);&0ZcG~f?-uLE(_SPYNtv8s^W#>yv%b2nA2>@{sD6XSvUXy literal 0 HcmV?d00001 diff --git a/.sqlx/query-2551c285e3e223311cff8e32022d8b11e95d56b2f166326301a0b6722fc1fd44.json b/.sqlx/query-2551c285e3e223311cff8e32022d8b11e95d56b2f166326301a0b6722fc1fd44.json new file mode 100644 index 0000000000000000000000000000000000000000..f6f27240201c487019cdc883fb89aba77d3d1574 GIT binary patch literal 876 zcma)4VN1g>6#Twl5%N)FEUB$i5J9atnH$!Dh)PMDR~OAvO;dD?{dbde0R@L^p(Xd; z-6i+3>LG-Akt?F=S-YB`81HG*@fNJ z*~3=eC%_wL%kH2Yno^6x1+cOQw?{8)*)CLLfkzLZO~DsKR)TxF!z`uNTqq_jW#H25 z9&DEHkPB7SF2|8F$pPLJ*P*Mkbp1Fe`+una(k4I9U)nSHGCN7PivslD8>idEciwne zE2C>ELEC}+Z4&Kf;!}F=dht8c_I?`BZXzKsoQapYu>kSCn>?{lm~0@} z{cb?+O`lj{fo literal 0 HcmV?d00001 diff --git a/.sqlx/query-2e3528a386066c3fc11be1259298f281279353d0b92625c46ef5cd09a672c031.json b/.sqlx/query-2e3528a386066c3fc11be1259298f281279353d0b92625c46ef5cd09a672c031.json new file mode 100644 index 0000000000000000000000000000000000000000..fe474aeaf7b96d6adac0268d7b0465ba964399c9 GIT binary patch literal 260 zcmXZVy>7!G6o%p5SAm!_7zo5vs=Ab_L!zi|r4CUP!2#Du{ztxn#yQYc=F1Y&Z^l=%HD89j)%zXkEu`^eVUFW4k?(g?8xlQvCNGa8 zA0|lA<=7P?n1Qid`e8yi9CH`xY!m)Bhm`d(L#^XaJ0r0B==Q%Imaem|%fZ7DU+zv@ zJGCg32O)W7MWsrmKs3OZ3ye$6b=B~S@r@Qz_|j>vxP-L|jRaq6t2P`;!Dw+S{s02$ BL@xjU literal 0 HcmV?d00001 diff --git a/.sqlx/query-3adb7ab9cacd5a84d882e4c9860be83d32bb0380b1fb76f90bf4e200636c7f6a.json b/.sqlx/query-3adb7ab9cacd5a84d882e4c9860be83d32bb0380b1fb76f90bf4e200636c7f6a.json new file mode 100644 index 0000000000000000000000000000000000000000..59ce879ae84f4974a31a99a9cfc0db080ebfbd2d GIT binary patch literal 253 zcmXZVF;BxV5Cz~pzvASX!Ht9CAXQy3VkklcB!;T0?reudO48Oz22}OGb8cC`d+*&P z0}!4LBMw14f%yE`c5@I9iO)-jzX_l1)_z%dso&pVKo^e_e81Y-Lg!%(n|s=a$;FP+ z%7xq9cgKDi#))CKPlHS+jQnU0G3h3gI;4es`Qxw&X@YFUIr vPbE_2(D_F+g*PS-g{zE~6pZpJCz8rYS~XParjpbYA(v7s?aD@@$gbHRv&chS literal 0 HcmV?d00001 diff --git a/.sqlx/query-486ed307f718754a3d4ea2c6fe944f9571c88fad3316593edf0c72ddef24c73e.json b/.sqlx/query-486ed307f718754a3d4ea2c6fe944f9571c88fad3316593edf0c72ddef24c73e.json new file mode 100644 index 0000000000000000000000000000000000000000..a4bfa2653474694611079a41c29982b6b40fab03 GIT binary patch literal 266 zcmXZVy>7xV6o%nFS7CW(Ah8oCm8vc^>Oh4ENF5MDj?ZypDL<`~3>D(udkn0Pzw`Q< zFvg*|45ml^!uVmo^)vESX^~d~!L6;z0MKR9DuFz?CI5&0 BNIw7o literal 0 HcmV?d00001 diff --git a/.sqlx/query-49afbeaa1d32f8c2e6fea7a2d57a11ec04dd0aab26139c19583af00e5baaba56.json b/.sqlx/query-49afbeaa1d32f8c2e6fea7a2d57a11ec04dd0aab26139c19583af00e5baaba56.json new file mode 100644 index 0000000000000000000000000000000000000000..90e1cca5eef580dd7c4cb6eedc308bdf2f604a2f GIT binary patch literal 596 zcma)&Piw+J5XJ9)ieXP$JS5RX?L}IUB1lSO?4^`4o1N5!HL>{v74f@wcjLB7d*~h( z_RX8$yq#kYfC>Iq;EvcWu=UeQHpGTT{%EN>7;zb|7fA}sIDLd2W>r~3zm;MLrfUGt zcdN}}4gL8L<^xC;=_ZMn@dMm+7(sPbNpA2*jn&|+RPC;)weVsEI2UZuC6U6m8%?*A zm5>E0-Qt;S-C@mPPj9lwOPjK2qA@!cPtMhiG=ApDI{M#e-!*CT75ZM4{6Azn;R5oL zI=E1;&?$X3-)I~DwX?E4R(ib+Rc2A{8!VCU+eQn4^`5`UidHG)%Iw&!G)GmFoBI3F zjncP8ur1bGv&8WXb54l97zOz>3nJ6$RVD}%ae5OeFL|jArtM5yq>TOVCh2l>;9L)ZzW4oj z$@lg%LMY0jfm+iQp)b!H(`$Mi%3p(aV=S&W>$_KSMeesxkE90W+5%K>+NPV3s5|A1 z3<`VC^jy{2q4w<+d5Z;dY{X_VGZ^Wi<|(a1(}4997VM{w_Tx?$rYYMn$V1cTf*zZP zgqkb^o+jmK##8z{F+Jmd`c7LIi&Og9#rmD9G!E9^{KON@FShvB`V(RNJpu7I`IyA} zgrz%heu;YRLh30Kx-}nFA2N;DV`j7&tOaSq!leBu!FD>?p7S#e;m;^eE9k1QD@%ze vIB;HKrho~-WQMt7s1`RF%tgj{#>8?VxI~4#EN?~5r4qU3s$4KmXNTDjA#=WM literal 0 HcmV?d00001 diff --git a/.sqlx/query-96a0b9960daa8a10e04e22cba592870941545b095193612956c29d37a5a1b774.json b/.sqlx/query-96a0b9960daa8a10e04e22cba592870941545b095193612956c29d37a5a1b774.json new file mode 100644 index 0000000000000000000000000000000000000000..c86b729bb8f9ad8a260bf9108b4a263bb937a95c GIT binary patch literal 362 zcmXv}O>4s-7{2FMi02(NjY%Dh9Y)3m9b?&GhcU`)w2fd*r{+*f{`=IV9`xmTpD(Wk zLRgBug_FYf2!DR;bWr#%!GDu-mkhSM_UQ{z^xVC_p#VpZzF*p|O(S|hj|*KY-#aam z%{9uSK|h-5WPJpFGJw`aUfvRERwpPbMkloe6BQ&?SPl-Cv)XHW7*m;78}PcC^ZUCn z@flqFL{KgTE)Ak%r;kI30L|Qa<=V*@5GHlKRXc$3YB7v=CgK77kTQ7IGs-tLEjg?ElFLm445(&HQPTKJfL6quIrKYb$kM2Pw+;**S!TI-#dGT^`I1#$^|CmqXN~5F!nD$ z(-QXd;<$#cc3A@ J5F4|@;v0S`!DRpd literal 0 HcmV?d00001 diff --git a/.sqlx/query-b6f6fd898dc3f1dbe2c39bf0445bac76c8233c0feee15f4504bf74ea864716ce.json b/.sqlx/query-b6f6fd898dc3f1dbe2c39bf0445bac76c8233c0feee15f4504bf74ea864716ce.json new file mode 100644 index 0000000000000000000000000000000000000000..611b0dec08d389cf78b180c838c5fe2505b64953 GIT binary patch literal 817 zcmbu7O-sZu5QgvjD?*Oqq1|S?T|qoZMPWr;1rH)69}^qQrfb?FW!e93k~TLN)a@bA zcjkR&l9~MsAuL#B=oat=!cUKD)d4>D@@Efr2x7S|ubmcb2xtm~V63l1uzSr}8Y`5c+F26c(fHwR z*g>U?Y@zdyZj{os3XgXBK`16zbEw4fJ+THxf6J;lt z_yYJ4bohA!k2Ap=^`IG MWSQn1@a!=A22AqBBLDyZ literal 0 HcmV?d00001 diff --git a/.sqlx/query-bcd9d4dd3641e6c84262ed5f6c5646f825be186da276b113c0150aaad26b057c.json b/.sqlx/query-bcd9d4dd3641e6c84262ed5f6c5646f825be186da276b113c0150aaad26b057c.json new file mode 100644 index 0000000000000000000000000000000000000000..b3120648fb0c5f528493a9c8d6a7f4fabc926454 GIT binary patch literal 871 zcmbtS%WA_g5WMFrgicAx!FFWD?WOb(N+7t@NqQ+I=;c~ekyAe?Cgk5M$rkkBL)#c* zw4>eGSMXcUx2tRi*6HpE90{w+17VFUbq%I{ zDES;zLXqxZPc>T|Ao9<*3>AZ9k=>{V>TwZ)u z-_qto&+O+L=MR0QsrVni|7+v?(bfDvycln>xVpvQ@HjpC9ldkntbr$Wf#WpM^02NeA~YSy$JJlv{3kpK5iJ8sRxL`7oF>zsxIsHLM0A~s;4DsIQRNX%P&f7Tx zU{+sx>>OJI``H)G=-49g-{kx~;HN6oLBY2C{(=sTzYTEQsZznzSlGbFYi`|Oe4|5V zrgI9ITRV0AKyW&TBGE5;`e}4N=xat=HrIM2;Lp+Hz3r#AMcszr=}a$AXEhEr2{Sxh wp)`3WQ7n1vq)aokX_aG^n?xItab5eo%lN+hwiY3y8>(O?WdK9NFi literal 0 HcmV?d00001 diff --git a/.sqlx/query-eee8820f59927c1560a5fd461b9f4a2d40abdf3843d0c815399ea9d267cade5a.json b/.sqlx/query-eee8820f59927c1560a5fd461b9f4a2d40abdf3843d0c815399ea9d267cade5a.json new file mode 100644 index 0000000000000000000000000000000000000000..cb7c1bdf5ea441776a329bb9538c6ce8903023c9 GIT binary patch literal 574 zcma)&PfNrw5XJBNDMF6oq5roPL{KUUD`H*nAhJxGsSTv*nzrDw>~}Xw8{I_@<`Br6 z_kJ^({elq6%8i5;=>?(BPb)qkJvZ{lh-zoWe3jp<$r-sV9v?^xmFjx(a+eo5895`D z?_4f1KMkVMpT^`TiI*j)e2-T*rMynnT;DwM>@s01fe6InPNJ0*llscW!;&1 z?&Xj~bS@s8Yieoy)RFacf1`aD(&iKNwdl(KkZp$}$j{Wlk$Qq&~}BBp8J)p3>u s%gT=v58|3diC<=Q1ObaYC|Mn*QOLY1_2V$h5Hc1d%L*9AKo^I_7ft+$N&o-= literal 0 HcmV?d00001 diff --git a/.sqlx/query-fde0dca1aba5b490a9f5f6006677be750a7bcf5d1185935e8386671c69dc7270.json b/.sqlx/query-fde0dca1aba5b490a9f5f6006677be750a7bcf5d1185935e8386671c69dc7270.json new file mode 100644 index 0000000000000000000000000000000000000000..8d164fb24bf198888884c395539f43a1f002f90b GIT binary patch literal 560 zcma)2O-sWt7{2dUgdD{~x-CuXQJEl2#JQWu$fpgY=}c2_jQw|$w9yR_%ps8HeLmhi z#~A=nu$`r?gf{@bJg>Ev@H)aDgLH=kuGXu&Ew}>ro2N(6QtrBbqzKt_r&*L9gCvM} zXNK1H0eDRSmQ?y9(hy_XKgtpWK35vPnf&Q?UJ8e}LH+ZnVT*7I1%B(Rm@?mx! z{<1EzD3C9#Z%xPkVo~`UaHHQFA8Sb5 z>6_3F#!zNr$GK8PO)qom@5dMw?qmTQ+BdO8B_tA@7L-xUmNlZa!m325ED2$fU_=Sy a3X7tsaJ|5?T2x_F@Uj-1%n5?C)9eczsf92A literal 0 HcmV?d00001 diff --git a/api.v1.yaml b/api.v1.yaml index 67c18f5..9b362a3 100644 --- a/api.v1.yaml +++ b/api.v1.yaml @@ -554,6 +554,12 @@ paths: responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/macro' operationId: get-api-v1-macros description: Receive a list with all available macros. post: diff --git a/emgauwa-core/src/handlers/v1/macros.rs b/emgauwa-core/src/handlers/v1/macros.rs new file mode 100644 index 0000000..d4ab7eb --- /dev/null +++ b/emgauwa-core/src/handlers/v1/macros.rs @@ -0,0 +1,161 @@ +use actix::Addr; +use actix_web::{delete, get, post, put, web, HttpResponse}; +use emgauwa_lib::db::DbMacro; +use emgauwa_lib::errors::{DatabaseError, EmgauwaError}; +use emgauwa_lib::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay}; +use emgauwa_lib::types::{ + ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate, +}; +use itertools::Itertools; +use sqlx::{Pool, Sqlite}; + +use crate::app_state; +use crate::app_state::AppState; + +#[get("/macros")] +pub async fn index(pool: web::Data>) -> Result { + let mut pool_conn = pool.acquire().await?; + + let db_macros = DbMacro::get_all(&mut pool_conn).await?; + let macros: Vec = convert_db_list(&mut pool_conn, db_macros)?; + + Ok(HttpResponse::Ok().json(macros)) +} + +#[get("/macros/{macro_id}")] +pub async fn show( + pool: web::Data>, + path: web::Path<(String,)>, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let (macro_uid,) = path.into_inner(); + let uid = EmgauwaUid::try_from(macro_uid.as_str())?; + + let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid) + .await? + .ok_or(DatabaseError::NotFound)?; + + let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?; + Ok(HttpResponse::Ok().json(return_macro)) +} + +#[post("/macros")] +pub async fn add( + pool: web::Data>, + data: web::Json, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let new_macro = DbMacro::create(&mut pool_conn, EmgauwaUid::default(), &data.name).await?; + + new_macro + .set_actions(&mut pool_conn, data.actions.as_slice()) + .await?; + + let return_macro = Macro::from_db_model(&mut pool_conn, new_macro)?; + Ok(HttpResponse::Created().json(return_macro)) +} + +#[put("/macros/{macro_id}")] +pub async fn update( + pool: web::Data>, + path: web::Path<(String,)>, + data: web::Json, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let (macro_uid,) = path.into_inner(); + let uid = EmgauwaUid::try_from(macro_uid.as_str())?; + + let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid) + .await? + .ok_or(DatabaseError::NotFound)?; + + if let Some(name) = &data.name { + db_macro.update(&mut pool_conn, name).await?; + } + + if let Some(actions) = &data.actions { + db_macro + .set_actions(&mut pool_conn, actions.as_slice()) + .await?; + } + + let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?; + Ok(HttpResponse::Ok().json(return_macro)) +} + +#[delete("/macros/{macro_id}")] +pub async fn delete( + pool: web::Data>, + path: web::Path<(String,)>, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let (macro_uid,) = path.into_inner(); + let uid = EmgauwaUid::try_from(macro_uid.as_str())?; + + DbMacro::delete_by_uid(&mut pool_conn, uid).await?; + Ok(HttpResponse::Ok().json("macro got deleted")) +} + +#[put("/macros/{macro_id}/execute")] +pub async fn execute( + pool: web::Data>, + app_state: web::Data>, + path: web::Path<(String,)>, + query: web::Query, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let (macro_uid,) = path.into_inner(); + let uid = EmgauwaUid::try_from(macro_uid.as_str())?; + + let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid) + .await? + .ok_or(DatabaseError::NotFound)?; + + let actions_db = match query.weekday { + None => db_macro.get_actions(&mut pool_conn).await?, + Some(weekday) => { + db_macro + .get_actions_weekday(&mut pool_conn, weekday) + .await? + } + }; + let mut actions: Vec = convert_db_list(&mut pool_conn, actions_db)?; + + for action in &actions { + action.execute(&mut pool_conn).await?; + } + + let affected_controller_uids: Vec = actions + .iter() + .map(|action| action.relay.controller_id.clone()) + .unique() + .collect(); + + for controller_uid in affected_controller_uids { + let mut affected_relays: Vec = Vec::new(); + let mut affected_relay_ids: Vec = Vec::new(); + + for action in actions.iter_mut() { + if affected_relay_ids.contains(&action.relay.r.id) { + continue; + } + action.relay.reload(&mut pool_conn)?; + affected_relays.push(action.relay.clone()); + affected_relay_ids.push(action.relay.r.id); + } + + app_state + .send(app_state::Action { + controller_uid, + action: ControllerWsAction::Relays(affected_relays.clone()), + }) + .await??; + } + + Ok(HttpResponse::Ok().finish()) // TODO add a message? +} diff --git a/emgauwa-core/src/handlers/v1/mod.rs b/emgauwa-core/src/handlers/v1/mod.rs index dca0eae..9ce232c 100644 --- a/emgauwa-core/src/handlers/v1/mod.rs +++ b/emgauwa-core/src/handlers/v1/mod.rs @@ -1,4 +1,5 @@ pub mod controllers; +pub mod macros; pub mod relays; pub mod schedules; pub mod tags; diff --git a/emgauwa-core/src/main.rs b/emgauwa-core/src/main.rs index cc1bf87..f4e654f 100644 --- a/emgauwa-core/src/main.rs +++ b/emgauwa-core/src/main.rs @@ -108,6 +108,12 @@ async fn main() -> Result<(), std::io::Error> { .service(handlers::v1::tags::show) .service(handlers::v1::tags::delete) .service(handlers::v1::tags::add) + .service(handlers::v1::macros::index) + .service(handlers::v1::macros::show) + .service(handlers::v1::macros::add) + .service(handlers::v1::macros::update) + .service(handlers::v1::macros::delete) + .service(handlers::v1::macros::execute) .service(handlers::v1::ws::ws_controllers) .service(handlers::v1::ws::ws_relays), ) diff --git a/emgauwa-lib/src/db/macro.rs b/emgauwa-lib/src/db/macro.rs new file mode 100644 index 0000000..1ced624 --- /dev/null +++ b/emgauwa-lib/src/db/macro.rs @@ -0,0 +1,166 @@ +use std::ops::DerefMut; + +use serde_derive::{Deserialize, Serialize}; +use sqlx::pool::PoolConnection; +use sqlx::Sqlite; + +use crate::db::{DbController, DbMacroAction, DbRelay, DbSchedule}; +use crate::errors::DatabaseError; +use crate::types::{EmgauwaUid, RequestMacroAction}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DbMacro { + #[serde(skip)] + pub id: i64, + #[serde(rename = "id")] + pub uid: EmgauwaUid, + pub name: String, +} + + +impl DbMacro { + pub async fn get_all(conn: &mut PoolConnection) -> Result, DatabaseError> { + sqlx::query_as!(DbMacro, "SELECT * FROM macros") + .fetch_all(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } + + pub async fn get( + conn: &mut PoolConnection, + id: i64, + ) -> Result, DatabaseError> { + sqlx::query_as!(DbMacro, "SELECT * FROM macros WHERE id = ?", id) + .fetch_optional(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } + + pub async fn get_by_uid( + conn: &mut PoolConnection, + filter_uid: &EmgauwaUid, + ) -> Result, DatabaseError> { + sqlx::query_as!(DbMacro, "SELECT * FROM macros WHERE uid = ?", filter_uid) + .fetch_optional(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } + + pub async fn create( + conn: &mut PoolConnection, + new_uid: EmgauwaUid, + new_name: &str, + ) -> Result { + sqlx::query_as!( + DbMacro, + "INSERT INTO macros (uid, name) VALUES (?, ?) RETURNING *", + new_uid, + new_name + ) + .fetch_optional(conn.deref_mut()) + .await? + .ok_or(DatabaseError::InsertGetError) + } + + pub async fn delete(&self, conn: &mut PoolConnection) -> Result<(), DatabaseError> { + sqlx::query!("DELETE FROM macros WHERE id = ?", self.id) + .execute(conn.deref_mut()) + .await + .map(|res| match res.rows_affected() { + 0 => Err(DatabaseError::DeleteError), + _ => Ok(()), + })? + } + + pub async fn delete_by_uid( + conn: &mut PoolConnection, + filter_uid: EmgauwaUid, + ) -> Result<(), DatabaseError> { + if sqlx::query_scalar!("SELECT 1 FROM macros WHERE uid = ?", filter_uid) + .fetch_optional(conn.deref_mut()) + .await? + .is_none() + { + return Err(DatabaseError::NotFound); + } + + sqlx::query!("DELETE FROM macros WHERE uid = ?", filter_uid) + .execute(conn.deref_mut()) + .await + .map(|res| match res.rows_affected() { + 0 => Err(DatabaseError::DeleteError), + _ => Ok(()), + })? + } + + pub async fn update( + &self, + conn: &mut PoolConnection, + new_name: &str, + ) -> Result { + sqlx::query!("UPDATE relays SET name = ? WHERE id = ?", new_name, self.id,) + .execute(conn.deref_mut()) + .await?; + + DbMacro::get(conn, self.id) + .await? + .ok_or(DatabaseError::UpdateGetError) + } + + pub async fn set_actions( + &self, + conn: &mut PoolConnection, + new_actions: &[RequestMacroAction], + ) -> Result<(), DatabaseError> { + sqlx::query!("DELETE FROM macro_actions WHERE macro_id = ?", self.id) + .execute(conn.deref_mut()) + .await?; + + for new_action in new_actions { + let controller = DbController::get_by_uid(conn, &new_action.relay.controller_id) + .await? + .ok_or(DatabaseError::NotFound)?; + let relay = + DbRelay::get_by_controller_and_num(conn, &controller, new_action.relay.number) + .await? + .ok_or(DatabaseError::NotFound)?; + + let schedule = DbSchedule::get_by_uid(conn, &new_action.schedule.id) + .await? + .ok_or(DatabaseError::NotFound)?; + + DbMacroAction::create(conn, self, &relay, &schedule, new_action.weekday).await?; + } + Ok(()) + } + + pub async fn get_actions( + &self, + conn: &mut PoolConnection, + ) -> Result, DatabaseError> { + sqlx::query_as!( + DbMacroAction, + "SELECT * FROM macro_actions WHERE macro_id = ?", + self.id + ) + .fetch_all(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } + + pub async fn get_actions_weekday( + &self, + conn: &mut PoolConnection, + weekday: i64, + ) -> Result, DatabaseError> { + sqlx::query_as!( + DbMacroAction, + "SELECT * FROM macro_actions WHERE macro_id = ? AND weekday = ?", + self.id, + weekday + ) + .fetch_all(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } +} diff --git a/emgauwa-lib/src/db/macro_action.rs b/emgauwa-lib/src/db/macro_action.rs new file mode 100644 index 0000000..4a88c39 --- /dev/null +++ b/emgauwa-lib/src/db/macro_action.rs @@ -0,0 +1,99 @@ +use std::ops::DerefMut; + +use sqlx::pool::PoolConnection; +use sqlx::Sqlite; + +use crate::db::{DbMacro, DbRelay, DbSchedule}; +use crate::errors::DatabaseError; + +#[derive(Debug, Clone)] +pub struct DbMacroAction { + pub id: i64, + pub macro_id: i64, + pub relay_id: i64, + pub schedule_id: i64, + pub weekday: i64, // should be u8, but sqlite will store it as i64 +} + + +impl DbMacroAction { + pub async fn get_all( + conn: &mut PoolConnection, + ) -> Result, DatabaseError> { + sqlx::query_as!(DbMacroAction, "SELECT * FROM macro_actions") + .fetch_all(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } + + pub async fn get( + conn: &mut PoolConnection, + id: i64, + ) -> Result, DatabaseError> { + sqlx::query_as!( + DbMacroAction, + "SELECT * FROM macro_actions WHERE id = ?", + id + ) + .fetch_optional(conn.deref_mut()) + .await + .map_err(DatabaseError::from) + } + + pub async fn create( + conn: &mut PoolConnection, + new_macro: &DbMacro, + new_relay: &DbRelay, + new_schedule: &DbSchedule, + new_weekday: i64, + ) -> Result { + sqlx::query_as!( + DbMacroAction, + "INSERT INTO macro_actions (macro_id, relay_id, schedule_id, weekday) VALUES (?, ?, ?, ?) RETURNING *", + new_macro.id, + new_relay.id, + new_schedule.id, + new_weekday + ) + .fetch_optional(conn.deref_mut()) + .await? + .ok_or(DatabaseError::InsertGetError) + } + + pub async fn delete(&self, conn: &mut PoolConnection) -> Result<(), DatabaseError> { + sqlx::query!("DELETE FROM macro_actions WHERE id = ?", self.id) + .execute(conn.deref_mut()) + .await + .map(|res| match res.rows_affected() { + 0 => Err(DatabaseError::DeleteError), + _ => Ok(()), + })? + } + + pub async fn get_relay( + &self, + conn: &mut PoolConnection, + ) -> Result { + DbRelay::get(conn, self.relay_id) + .await? + .ok_or(DatabaseError::NotFound) + } + + pub async fn get_schedule( + &self, + conn: &mut PoolConnection, + ) -> Result { + DbSchedule::get(conn, self.schedule_id) + .await? + .ok_or(DatabaseError::NotFound) + } + + pub async fn get_macro( + &self, + conn: &mut PoolConnection, + ) -> Result { + DbMacro::get(conn, self.macro_id) + .await? + .ok_or(DatabaseError::NotFound) + } +} diff --git a/emgauwa-lib/src/db/mod.rs b/emgauwa-lib/src/db/mod.rs index eeefd57..c4f74b6 100644 --- a/emgauwa-lib/src/db/mod.rs +++ b/emgauwa-lib/src/db/mod.rs @@ -7,6 +7,8 @@ use sqlx::{ConnectOptions, Pool, Sqlite}; mod controllers; mod junction_relay_schedule; mod junction_tag; +mod r#macro; +mod macro_action; mod model_utils; mod relays; mod schedules; @@ -15,6 +17,8 @@ mod tag; pub use controllers::DbController; pub use junction_relay_schedule::DbJunctionRelaySchedule; pub use junction_tag::DbJunctionTag; +pub use macro_action::DbMacroAction; +pub use r#macro::DbMacro; pub use relays::DbRelay; pub use schedules::{DbPeriods, DbSchedule}; pub use tag::DbTag; diff --git a/emgauwa-lib/src/models/macro.rs b/emgauwa-lib/src/models/macro.rs new file mode 100644 index 0000000..fbe9dbb --- /dev/null +++ b/emgauwa-lib/src/models/macro.rs @@ -0,0 +1,42 @@ +use futures::executor::block_on; +use serde_derive::{Deserialize, Serialize}; +use sqlx::pool::PoolConnection; +use sqlx::Sqlite; + +use crate::db::DbMacro; +use crate::errors::DatabaseError; +use crate::models::{convert_db_list, FromDbModel, MacroAction}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Macro { + #[serde(flatten)] + pub m: DbMacro, + pub actions: Vec, +} + + +impl FromDbModel for Macro { + type DbModel = DbMacro; + type DbModelCache = (); + + fn from_db_model( + conn: &mut PoolConnection, + db_model: Self::DbModel, + ) -> Result { + Self::from_db_model_cache(conn, db_model, ()) + } + + fn from_db_model_cache( + conn: &mut PoolConnection, + db_model: Self::DbModel, + _cache: Self::DbModelCache, + ) -> Result { + let actions_db = block_on(db_model.get_actions(conn))?; + let actions: Vec = convert_db_list(conn, actions_db)?; + + Ok(Macro { + m: db_model, + actions, + }) + } +} diff --git a/emgauwa-lib/src/models/macro_action.rs b/emgauwa-lib/src/models/macro_action.rs new file mode 100644 index 0000000..d7b5aca --- /dev/null +++ b/emgauwa-lib/src/models/macro_action.rs @@ -0,0 +1,56 @@ +use futures::executor::block_on; +use serde_derive::{Deserialize, Serialize}; +use sqlx::pool::PoolConnection; +use sqlx::Sqlite; + +use crate::db::{DbJunctionRelaySchedule, DbMacroAction}; +use crate::errors::{DatabaseError, EmgauwaError}; +use crate::models::{FromDbModel, Relay, Schedule}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MacroAction { + pub schedule: Schedule, + pub relay: Relay, + pub weekday: i64, +} + + +impl FromDbModel for MacroAction { + type DbModel = DbMacroAction; + type DbModelCache = (); + + fn from_db_model( + conn: &mut PoolConnection, + db_model: Self::DbModel, + ) -> Result { + Self::from_db_model_cache(conn, db_model, ()) + } + + fn from_db_model_cache( + conn: &mut PoolConnection, + db_model: Self::DbModel, + _cache: Self::DbModelCache, + ) -> Result { + let schedule_db = block_on(db_model.get_schedule(conn))?; + let schedule = Schedule::from_db_model(conn, schedule_db)?; + + let relay_db = block_on(db_model.get_relay(conn))?; + let relay = Relay::from_db_model(conn, relay_db)?; + + let weekday = db_model.weekday; + + Ok(MacroAction { + schedule, + relay, + weekday, + }) + } +} + +impl MacroAction { + pub async fn execute(&self, conn: &mut PoolConnection) -> Result<(), EmgauwaError> { + DbJunctionRelaySchedule::set_schedule(conn, &self.relay.r, &self.schedule.s, self.weekday) + .await?; + Ok(()) + } +} diff --git a/emgauwa-lib/src/models/mod.rs b/emgauwa-lib/src/models/mod.rs index 136de53..570bf48 100644 --- a/emgauwa-lib/src/models/mod.rs +++ b/emgauwa-lib/src/models/mod.rs @@ -1,9 +1,13 @@ mod controller; +mod r#macro; +mod macro_action; mod relay; mod schedule; mod tag; pub use controller::Controller; +pub use macro_action::MacroAction; +pub use r#macro::Macro; pub use relay::Relay; pub use schedule::Schedule; use sqlx::pool::PoolConnection; diff --git a/emgauwa-lib/src/types/request.rs b/emgauwa-lib/src/types/request.rs index e1cfced..5c9a927 100644 --- a/emgauwa-lib/src/types/request.rs +++ b/emgauwa-lib/src/types/request.rs @@ -4,7 +4,7 @@ use sqlx::Sqlite; use crate::db::{DbPeriods, DbSchedule}; use crate::errors::DatabaseError; -use crate::types::ScheduleUid; +use crate::types::{EmgauwaUid, ScheduleUid}; #[derive(Debug, Serialize, Deserialize)] pub struct RequestScheduleCreate { @@ -48,6 +48,41 @@ pub struct RequestTagCreate { pub tag: String, } +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestMacroActionRelay { + pub number: i64, + pub controller_id: EmgauwaUid, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestMacroActionSchedule { + pub id: ScheduleUid, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestMacroAction { + pub weekday: i64, + pub relay: RequestMacroActionRelay, + pub schedule: RequestMacroActionSchedule, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestMacroCreate { + pub name: String, + pub actions: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestMacroUpdate { + pub name: Option, + pub actions: Option>, +} + +#[derive(Debug, Deserialize)] +pub struct RequestMacroExecute { + pub weekday: Option, +} + impl RequestScheduleId { pub async fn get_schedule( &self, diff --git a/migrations/20231120000000_init.up.sql b/migrations/20231120000000_init.up.sql index 1e5f205..6c588eb 100644 --- a/migrations/20231120000000_init.up.sql +++ b/migrations/20231120000000_init.up.sql @@ -125,7 +125,7 @@ CREATE TABLE macros AUTOINCREMENT NOT NULL, uid - VARCHAR(36) + BLOB NOT NULL UNIQUE, name