use tracing::instrument; use crate::api::api_prelude::*; use crate::error::{Error, Result}; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct ApiApplication { pub id: i32, pub app_name: String, pub url: String, pub description: Option, pub active: bool, pub glyph: Option, pub application_category_id: Option, pub enable_healthcheck: bool, pub healthcheck_status: Option, pub favicon: bool, } impl From for ApiApplication { fn from(model: application::Model) -> Self { Self { id: model.id, app_name: model.app_name, url: model.url, description: model.description, active: model.active, glyph: model.glyph, application_category_id: model.application_category_id, enable_healthcheck: model.enable_healthcheck, healthcheck_status: None, favicon: model.favicon, } } } #[instrument] #[get("")] pub async fn list_applications(state: web::Data) -> Result { let apps: Vec = Application::find().all(&state.db).await.unwrap(); let count = apps.len(); let mut apps = apps .into_iter() .map(|app| app.into()) .collect::>(); for app in apps.iter_mut() { app.healthcheck_status = state.healthcheck_status.lock().await.get(&app.id).cloned(); } Ok(HttpResponse::Ok().json(ListObjects::new(apps, count))) } #[instrument] #[post("")] pub async fn new_application( state: web::Data, data: web::Json, ) -> Result { let model = application::ActiveModel { id: NotSet, app_name: Set(data.0.app_name), description: Set(data.0.description), url: Set(data.0.url), active: Set(data.0.active), glyph: Set(data.0.glyph), application_category_id: Set(data.0.application_category_id), enable_healthcheck: Set(data.0.enable_healthcheck), favicon: Set(data.0.favicon), }; let app = model.insert(&state.db).await?; Ok(HttpResponse::Ok().json(app)) } #[instrument] #[get("{id}")] pub async fn get_applications( state: web::Data, id: web::Path, ) -> Result { let id = id.into_inner(); let res: Option = Application::find_by_id(id).one(&state.db).await?; match res { Some(app) => Ok(HttpResponse::Ok().json(app)), None => Err(Error::not_found()), } } #[instrument] #[put("{id}")] pub async fn update_applications( state: web::Data, data: web::Json, id: web::Path, ) -> Result { let id = id.into_inner(); let res: Option = Application::find_by_id(id).one(&state.db).await?; match res { Some(_app) => { let data = data.into_inner(); let ret = application::ActiveModel { id: Set(id), active: Set(data.active), app_name: Set(data.app_name), description: Set(data.description), url: Set(data.url), application_category_id: Set(data.application_category_id), glyph: Set(data.glyph), enable_healthcheck: Set(data.enable_healthcheck), favicon: Set(data.favicon), }; let model = ret.update(&state.db).await?; Ok(HttpResponse::Ok().json(model)) } None => Err(Error::not_found()), } } #[instrument] #[delete("{id}")] pub async fn delete_application( state: web::Data, id: web::Path, ) -> Result { Application::delete_many() .filter(application::Column::Id.eq(id.into_inner())) .exec(&state.db) .await?; Ok(HttpResponse::Ok().body("")) } /// Routes for the application endpoints. This binds up a scope with all endpoints for applications, to make it easier to add them to the server. pub fn routes() -> Scope { web::scope("/applications") .service(list_applications) .service(update_applications) .service(delete_application) .service(new_application) .service(get_applications) } #[cfg(test)] mod tests { use crate::api::test_prelude::*; use actix_web::http::Method; #[actix_rt::test] async fn test_list_applications() -> Result<()> { let state = setup_state().await?; application::ActiveModel { app_name: Set("Application 1".into()), url: Set("http://somewhere/".into()), active: Set(true), ..Default::default() } .insert(&state.db) .await?; application::ActiveModel { app_name: Set("Application 2".into()), url: Set("http://somewhere/".into()), active: Set(true), ..Default::default() } .insert(&state.db) .await?; let mut req = actix_web::test::TestRequest::with_uri("/api/applications") .method(Method::GET) .to_request(); let resp = call_endpoint!(req, state); assert_eq!(resp.status(), 200); let data = get_response!(resp, ListObjects); assert_eq!(2_usize, data.items.len()); Ok(()) } #[actix_rt::test] async fn test_get_applications() -> Result<()> { let state = setup_state().await?; let model = application::ActiveModel { app_name: Set("Application 1".into()), url: Set("http://somewhere/".into()), active: Set(true), ..Default::default() } .insert(&state.db) .await?; let mut req = actix_web::test::TestRequest::with_uri(&format!("/api/applications/{}", model.id)) .method(Method::GET) .to_request(); let resp = call_endpoint!(req, state); let status = resp.status(); let mut data = get_response!(resp, crate::entity::application::Model); data.id = model.id; assert_eq!(model, data); assert_eq!(status, 200); Ok(()) } #[actix_rt::test] async fn test_new_application() -> Result<()> { let model = application::Model { id: 0, app_name: "Application 1".into(), glyph: Some("web".into()), url: "http://example.com".into(), description: Some("Some Application".into()), active: true, application_category_id: None, enable_healthcheck: false, ..Default::default() }; let state = setup_state().await?; let mut req = actix_web::test::TestRequest::with_uri("/api/applications") .method(Method::POST) .set_json(model.clone()) .to_request(); let resp = call_endpoint!(req, state); assert_eq!(resp.status(), 200); let data = get_response!(resp, crate::entity::application::Model); assert_eq!(model, data); assert_eq!(application::Entity::find().count(&state.db).await?, 1); Ok(()) } #[actix_rt::test] async fn test_update_application() -> Result<()> { let state = setup_state().await?; application::ActiveModel { app_name: Set("Application 1".into()), url: Set("http://somewhere/".into()), active: Set(true), ..Default::default() } .insert(&state.db) .await?; let mut model = application::ActiveModel { app_name: Set("Application 2".into()), url: Set("http://somewhere/".into()), active: Set(true), ..Default::default() } .insert(&state.db) .await?; model.url = "http://updated.com".into(); let mut req = actix_web::test::TestRequest::with_uri(&format!("/api/applications/{}", model.id)) .method(Method::PUT) .set_json(model.clone()) .to_request(); let resp = call_endpoint!(req, state); assert_eq!(resp.status(), 200); let mut data = get_response!(resp, crate::entity::application::Model); data.id = model.id; assert_eq!(model, data, "Check API"); let db_model = application::Entity::find_by_id(model.id) .one(&state.db) .await? .unwrap(); assert_eq!(db_model, model, "Check DB"); Ok(()) } #[actix_rt::test] async fn test_delete_application() -> Result<()> { let state = setup_state().await?; let model = application::ActiveModel { app_name: Set("Application 1".into()), url: Set("http://somewhere/".into()), active: Set(true), ..Default::default() } .insert(&state.db) .await?; let mut req = actix_web::test::TestRequest::with_uri(&format!("/api/applications/{}", model.id)) .method(Method::DELETE) .to_request(); let resp = call_endpoint!(req, state); assert_eq!(resp.status(), 200); assert_eq!(application::Entity::find().count(&state.db).await?, 0); Ok(()) } }