From bb76e3db4d890ef091627a16a3e4c9cafe99de0c Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Fri, 19 Apr 2024 18:21:14 +0200 Subject: [PATCH] Improve tag endpoint --- api.v1.yaml | 29 ++++++++++------ emgauwa-core/src/handlers/v1/tags.rs | 50 ++++++++++++++++++++++++++-- emgauwa-core/src/main.rs | 3 ++ emgauwa-lib/src/db/tag.rs | 21 ++++++++++++ emgauwa-lib/src/models/mod.rs | 2 ++ emgauwa-lib/src/models/tag.rs | 49 +++++++++++++++++++++++++++ emgauwa-lib/src/types/request.rs | 5 +++ 7 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 emgauwa-lib/src/models/tag.rs diff --git a/api.v1.yaml b/api.v1.yaml index 445d38e..67c18f5 100644 --- a/api.v1.yaml +++ b/api.v1.yaml @@ -443,6 +443,10 @@ paths: responses: '201': description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/tag_full' '400': description: Bad Request requestBody: @@ -474,16 +478,7 @@ paths: content: application/json: schema: - type: object - properties: - relays: - type: array - items: - $ref: '#/components/schemas/relay' - schedules: - type: array - items: - $ref: '#/components/schemas/schedule' + $ref: '#/components/schemas/tag_full' '404': description: Not Found operationId: get-tags-tag @@ -822,6 +817,20 @@ components: type: string title: tag example: sprinkler + tag_full: + title: tag (full) + type: object + properties: + tag: + $ref: '#/components/schemas/tag' + relays: + type: array + items: + $ref: '#/components/schemas/relay' + schedules: + type: array + items: + $ref: '#/components/schemas/schedule' schedule_id: type: string title: schedule_id diff --git a/emgauwa-core/src/handlers/v1/tags.rs b/emgauwa-core/src/handlers/v1/tags.rs index 5b03038..851bf98 100644 --- a/emgauwa-core/src/handlers/v1/tags.rs +++ b/emgauwa-core/src/handlers/v1/tags.rs @@ -1,6 +1,8 @@ -use actix_web::{get, web, HttpResponse}; +use actix_web::{delete, get, post, web, HttpResponse}; use emgauwa_lib::db::DbTag; -use emgauwa_lib::errors::EmgauwaError; +use emgauwa_lib::errors::{DatabaseError, EmgauwaError}; +use emgauwa_lib::models::{FromDbModel, Tag}; +use emgauwa_lib::types::RequestCreateTag; use sqlx::{Pool, Sqlite}; #[get("/api/v1/tags")] @@ -13,3 +15,47 @@ pub async fn index(pool: web::Data>) -> Result>, + path: web::Path<(String,)>, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let (tag_name,) = path.into_inner(); + + let tag = DbTag::get_by_tag(&mut pool_conn, &tag_name) + .await? + .ok_or(DatabaseError::NotFound)?; + + let return_tag = Tag::from_db_model(&mut pool_conn, tag)?; + Ok(HttpResponse::Ok().json(return_tag)) +} + +#[delete("/api/v1/tags/{tag_name}")] +pub async fn delete( + pool: web::Data>, + path: web::Path<(String,)>, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let (tag_name,) = path.into_inner(); + + DbTag::delete_by_tag(&mut pool_conn, &tag_name).await?; + Ok(HttpResponse::Ok().json("tag got deleted")) +} + +#[post("/api/v1/tags")] +pub async fn add( + pool: web::Data>, + data: web::Json, +) -> Result { + let mut pool_conn = pool.acquire().await?; + + let new_tag = DbTag::create(&mut pool_conn, &data.tag).await?; + + let cache = (Vec::new(), Vec::new()); // a new tag can't have any relays or schedules + let return_tag = Tag::from_db_model_cache(&mut pool_conn, new_tag, cache)?; + Ok(HttpResponse::Created().json(return_tag)) +} diff --git a/emgauwa-core/src/main.rs b/emgauwa-core/src/main.rs index 1f3e1dd..1191da6 100644 --- a/emgauwa-core/src/main.rs +++ b/emgauwa-core/src/main.rs @@ -85,6 +85,9 @@ async fn main() -> Result<(), std::io::Error> { .service(handlers::v1::schedules::update) .service(handlers::v1::schedules::delete) .service(handlers::v1::tags::index) + .service(handlers::v1::tags::show) + .service(handlers::v1::tags::delete) + .service(handlers::v1::tags::add) .service(handlers::v1::ws::ws_controllers) }) .listen(listener)? diff --git a/emgauwa-lib/src/db/tag.rs b/emgauwa-lib/src/db/tag.rs index abd964d..c6a374b 100644 --- a/emgauwa-lib/src/db/tag.rs +++ b/emgauwa-lib/src/db/tag.rs @@ -63,4 +63,25 @@ impl DbTag { .await .map_err(DatabaseError::from) } + + pub async fn delete_by_tag( + conn: &mut PoolConnection, + filter_tag: &str, + ) -> Result<(), DatabaseError> { + if sqlx::query_scalar!("SELECT 1 FROM tags WHERE tag = ?", filter_tag) + .fetch_optional(conn.deref_mut()) + .await? + .is_none() + { + return Err(DatabaseError::NotFound); + } + + sqlx::query!("DELETE FROM tags WHERE tag = ?", filter_tag) + .execute(conn.deref_mut()) + .await + .map(|res| match res.rows_affected() { + 0 => Err(DatabaseError::DeleteError), + _ => Ok(()), + })? + } } diff --git a/emgauwa-lib/src/models/mod.rs b/emgauwa-lib/src/models/mod.rs index 7333c28..136de53 100644 --- a/emgauwa-lib/src/models/mod.rs +++ b/emgauwa-lib/src/models/mod.rs @@ -1,12 +1,14 @@ mod controller; mod relay; mod schedule; +mod tag; pub use controller::Controller; pub use relay::Relay; pub use schedule::Schedule; use sqlx::pool::PoolConnection; use sqlx::Sqlite; +pub use tag::Tag; use crate::errors::DatabaseError; diff --git a/emgauwa-lib/src/models/tag.rs b/emgauwa-lib/src/models/tag.rs new file mode 100644 index 0000000..7e23fb4 --- /dev/null +++ b/emgauwa-lib/src/models/tag.rs @@ -0,0 +1,49 @@ +use actix::MessageResponse; +use futures::executor::block_on; +use serde_derive::{Deserialize, Serialize}; +use sqlx::pool::PoolConnection; +use sqlx::Sqlite; + +use crate::db::{DbRelay, DbSchedule, DbTag}; +use crate::errors::DatabaseError; +use crate::models::{convert_db_list, FromDbModel, Relay, Schedule}; + +#[derive(Serialize, Deserialize, Debug, Clone, MessageResponse)] +pub struct Tag { + pub tag: String, + pub relays: Vec, + pub schedules: Vec, +} + +impl FromDbModel for Tag { + type DbModel = DbTag; + type DbModelCache = (Vec, Vec); + + fn from_db_model( + conn: &mut PoolConnection, + db_model: Self::DbModel, + ) -> Result { + let db_schedules = block_on(DbSchedule::get_by_tag(conn, &db_model))?; + let schedules: Vec = convert_db_list(conn, db_schedules)?; + + let db_relays = block_on(DbRelay::get_by_tag(conn, &db_model))?; + let relays: Vec = convert_db_list(conn, db_relays)?; + + let cache = (relays, schedules); + Self::from_db_model_cache(conn, db_model, cache) + } + + fn from_db_model_cache( + _conn: &mut PoolConnection, + db_model: Self::DbModel, + cache: Self::DbModelCache, + ) -> Result { + let tag = db_model.tag.clone(); + let (relays, schedules) = cache; + Ok(Tag { + tag, + relays, + schedules, + }) + } +} diff --git a/emgauwa-lib/src/types/request.rs b/emgauwa-lib/src/types/request.rs index 646b49a..036cf9d 100644 --- a/emgauwa-lib/src/types/request.rs +++ b/emgauwa-lib/src/types/request.rs @@ -38,6 +38,11 @@ pub struct RequestUpdateController { pub name: String, } +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestCreateTag { + pub tag: String, +} + impl RequestScheduleId { pub async fn get_schedule( &self,