diff --git a/src/db.rs b/src/db.rs index 284a639..1ecd389 100644 --- a/src/db.rs +++ b/src/db.rs @@ -7,7 +7,7 @@ use dotenv::dotenv; pub mod errors; pub mod models; pub mod schema; -pub mod schedule; +pub mod schedules; pub mod tag; mod model_utils; diff --git a/src/db/errors.rs b/src/db/errors.rs index 2b560f0..e8e2f20 100644 --- a/src/db/errors.rs +++ b/src/db/errors.rs @@ -10,6 +10,7 @@ pub enum DatabaseError { InsertGetError, NotFound, Protected, + UpdateError(diesel::result::Error), } impl DatabaseError { @@ -45,6 +46,7 @@ impl From<&DatabaseError> for String { DatabaseError::NotFound => String::from("model was not found in database"), DatabaseError::DeleteError => String::from("error on deleting from database"), DatabaseError::Protected => String::from("model is protected"), + DatabaseError::UpdateError(_) => String::from("error on updating the model"), } } } diff --git a/src/db/schedule.rs b/src/db/schedules.rs similarity index 78% rename from src/db/schedule.rs rename to src/db/schedules.rs index 4c73657..37a13f3 100644 --- a/src/db/schedule.rs +++ b/src/db/schedules.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use diesel::dsl::sql; use diesel::prelude::*; @@ -37,6 +38,15 @@ pub fn get_schedule_by_uid(filter_uid: EmgauwaUid) -> Result Vec { + let connection = get_connection(); + JunctionTag::belonging_to(tag) + .inner_join(schedules) + .select(schema::schedules::all_columns) + .load::(&connection) + .expect("Error loading tags") +} + pub fn delete_schedule_by_uid(filter_uid: EmgauwaUid) -> Result<(), DatabaseError> { let filter_uid = match filter_uid { EmgauwaUid::Off => Err(DatabaseError::Protected), @@ -79,6 +89,25 @@ pub fn create_schedule(new_name: &str, new_periods: &Periods) -> Result Result { + let connection = get_connection(); + + let new_periods = match schedule.uid { + EmgauwaUid::Off | EmgauwaUid::On => schedule.periods.borrow(), + EmgauwaUid::Any(_) => new_periods, + }; + + diesel::update(schedule) + .set(( + schema::schedules::name.eq(new_name), + schema::schedules::periods.eq(new_periods), + )) + .execute(&connection) + .map_err(DatabaseError::UpdateError)?; + + get_schedule_by_uid(schedule.uid.clone()) +} + pub fn set_schedule_tags(schedule: &Schedule, new_tags: &[String]) -> Result<(), DatabaseError> { let connection = get_connection(); diesel::delete(junction_tag.filter(schema::junction_tag::schedule_id.eq(schedule.id))) diff --git a/src/db/tag.rs b/src/db/tag.rs index a002ebc..8eb3728 100644 --- a/src/db/tag.rs +++ b/src/db/tag.rs @@ -3,7 +3,7 @@ use diesel::prelude::*; use crate::db::errors::DatabaseError; -use crate::db::{get_connection}; +use crate::db::{get_connection, schema}; use crate::db::models::*; use crate::db::schema::tags::dsl::tags; use crate::db::schema::junction_tag::dsl::junction_tag; @@ -29,6 +29,17 @@ pub fn create_tag(new_tag: &str) -> Result { Ok(result) } +pub fn get_tag(target_tag: &str) -> Result { + let connection = get_connection(); + + let result = tags + .filter(schema::tags::tag.eq(target_tag)) + .first::(&connection) + .or(Err(DatabaseError::NotFound))?; + + Ok(result) +} + pub fn create_junction_tag(target_tag: Tag, target_relay: Option<&Relay>, target_schedule: Option<&Schedule>) -> Result { let connection = get_connection(); diff --git a/src/handlers/errors.rs b/src/handlers/errors.rs index 132531b..3655cfa 100644 --- a/src/handlers/errors.rs +++ b/src/handlers/errors.rs @@ -3,6 +3,7 @@ use actix_web::HttpResponse; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; +#[derive(Debug)] pub enum HandlerError { BadUid, ProtectedSchedule diff --git a/src/handlers/v1/schedules.rs b/src/handlers/v1/schedules.rs index 32ea007..8e7f111 100644 --- a/src/handlers/v1/schedules.rs +++ b/src/handlers/v1/schedules.rs @@ -1,9 +1,11 @@ +use std::borrow::Borrow; use std::convert::TryFrom; -use actix_web::{HttpResponse, Responder, web, get, delete}; +use actix_web::{HttpResponse, Responder, web, get, post, put, delete}; use serde::{Serialize, Deserialize}; use crate::db::models::Periods; -use crate::db::schedule::*; +use crate::db::schedules::*; +use crate::db::tag::get_tag; use crate::handlers::errors::HandlerError; use crate::return_models::ReturnSchedule; use crate::types::EmgauwaUid; @@ -15,12 +17,27 @@ pub struct RequestSchedule { tags: Vec, } +#[get("/api/v1/schedules")] pub async fn index() -> impl Responder { let schedules = get_schedules(); let return_schedules: Vec = schedules.into_iter().map(ReturnSchedule::from).collect(); HttpResponse::Ok().json(return_schedules) } +#[get("/api/v1/schedules/tag/{tag}")] +pub async fn tagged(web::Path((tag,)): web::Path<(String,)>) -> impl Responder { + + let tag_db = get_tag(&tag); + if tag_db.is_err() { + return HttpResponse::from(tag_db.unwrap_err()) + } + let tag_db = tag_db.unwrap(); + + let schedules = get_schedules_by_tag(&tag_db); + let return_schedules: Vec = schedules.into_iter().map(ReturnSchedule::from).collect(); + HttpResponse::Ok().json(return_schedules) +} + #[get("/api/v1/schedules/{schedule_id}")] pub async fn show(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Responder { @@ -38,15 +55,16 @@ pub async fn show(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Resp } } -pub async fn add(post: web::Json) -> impl Responder { - let new_schedule = create_schedule(&post.name, &post.periods); +#[post("/api/v1/schedules")] +pub async fn add(data: web::Json) -> impl Responder { + let new_schedule = create_schedule(&data.name, &data.periods); if new_schedule.is_err() { return HttpResponse::from(new_schedule.unwrap_err()) } let new_schedule = new_schedule.unwrap(); - let result = set_schedule_tags(&new_schedule, post.tags.as_slice()); + let result = set_schedule_tags(&new_schedule, data.tags.as_slice()); if result.is_err() { return HttpResponse::from(result.unwrap_err()); } @@ -54,6 +72,35 @@ pub async fn add(post: web::Json) -> impl Responder { HttpResponse::Created().json(ReturnSchedule::from(new_schedule)) } +#[put("/api/v1/schedules/{schedule_id}")] +pub async fn update(web::Path((schedule_uid,)): web::Path<(String,)>, data: web::Json) -> impl Responder { + + let emgauwa_uid = EmgauwaUid::try_from(schedule_uid.as_str()).or(Err(HandlerError::BadUid)); + if emgauwa_uid.is_err() { + return HttpResponse::from(emgauwa_uid.unwrap_err()); + } + let emgauwa_uid = emgauwa_uid.unwrap(); + + let schedule = get_schedule_by_uid(emgauwa_uid); + if schedule.is_err() { + return HttpResponse::from(schedule.unwrap_err()); + } + let schedule = schedule.unwrap(); + + let schedule = update_schedule(&schedule, data.name.as_str(), data.periods.borrow()); + if schedule.is_err() { + return HttpResponse::from(schedule.unwrap_err()); + } + let schedule = schedule.unwrap(); + + let result = set_schedule_tags(&schedule, data.tags.as_slice()); + if result.is_err() { + return HttpResponse::from(result.unwrap_err()); + } + + HttpResponse::Ok().json(ReturnSchedule::from(schedule)) +} + #[delete("/api/v1/schedules/{schedule_id}")] pub async fn delete(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Responder { let emgauwa_uid = EmgauwaUid::try_from(schedule_uid.as_str()).or(Err(HandlerError::BadUid)); diff --git a/src/main.rs b/src/main.rs index e3faf9a..623c982 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,15 +30,11 @@ async fn main() -> std::io::Result<()> { .wrap(middleware::Logger::default()) .wrap(middleware::NormalizePath::new(TrailingSlash::Trim)) .app_data(web::JsonConfig::default().error_handler(handlers::json_error_handler)) - .route( - "/api/v1/schedules", - web::get().to(handlers::v1::schedules::index), - ) - .route( - "/api/v1/schedules", - web::post().to(handlers::v1::schedules::add), - ) + .service(handlers::v1::schedules::index) + .service(handlers::v1::schedules::tagged) .service(handlers::v1::schedules::show) + .service(handlers::v1::schedules::add) + .service(handlers::v1::schedules::update) .service(handlers::v1::schedules::delete) }) .bind("127.0.0.1:5000")? diff --git a/src/return_models.rs b/src/return_models.rs index 0f77a24..253a9f7 100644 --- a/src/return_models.rs +++ b/src/return_models.rs @@ -1,7 +1,7 @@ use serde::{Serialize}; use crate::db::models::Schedule; -use crate::db::schedule::get_schedule_tags; +use crate::db::schedules::get_schedule_tags; #[derive(Debug, Serialize)] pub struct ReturnSchedule { diff --git a/tests/tavern_tests/2.1.schedules_protected.tavern.yaml b/tests/tavern_tests/2.1.schedules_protected.tavern.yaml index dd62064..c29c0a6 100644 --- a/tests/tavern_tests/2.1.schedules_protected.tavern.yaml +++ b/tests/tavern_tests/2.1.schedules_protected.tavern.yaml @@ -27,6 +27,7 @@ stages: periods: - start: "00:10" end: "00:20" + tags: [] response: status_code: 200 verify_response_with: @@ -62,6 +63,7 @@ stages: periods: - start: "16:10" end: "17:20" + tags: [] response: status_code: 200 verify_response_with: diff --git a/tests/tavern_utils/validate_schedule.py b/tests/tavern_utils/validate_schedule.py index 79d1ca6..eb92b9e 100644 --- a/tests/tavern_utils/validate_schedule.py +++ b/tests/tavern_utils/validate_schedule.py @@ -23,7 +23,8 @@ def multiple(response): _verify_single(schedule) def check_name(response, name): - assert response.json().get("name") == name, "schedule name check failed" + response_name = response.json().get("name") + assert response_name == name, f"schedule name check failed (expected: '{name}'; actual: '{response_name}')" def check_id(response, id): assert response.json().get("id") == id, "schedule id check failed" @@ -57,7 +58,7 @@ def compare_on(response): assert response.json().get("id") == "on", "schedule on did not return id on" assert len(response.json().get("periods")) == 1, "schedule on has unexpected amount of periods" assert response.json().get("periods")[0].get("start") == "00:00", "Schedule on has unexpected start" - assert response.json().get("periods")[0].get("end") == "23:59", "Schedule on has unexpected start" + assert response.json().get("periods")[0].get("end") == "00:00", "Schedule on has unexpected start" def find(response, id=None, name=None, periods=None, tag=None): if periods != None: