diff --git a/entity/src/organization.rs b/entity/src/organization.rs index a9164cd..3ca749b 100644 --- a/entity/src/organization.rs +++ b/entity/src/organization.rs @@ -3,11 +3,12 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel, Eq, Deserialize, Serialize)] #[sea_orm(schema_name = "refactor_platform_rs", table_name = "organizations")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] + // TODO: consider changing this to a u64 pub id: i32, pub name: String, } diff --git a/web/src/controller/organization_controller.rs b/web/src/controller/organization_controller.rs index a1b8b9b..e73c70a 100644 --- a/web/src/controller/organization_controller.rs +++ b/web/src/controller/organization_controller.rs @@ -1,13 +1,25 @@ -use crate::AppState; -use axum::extract::State; +use crate::{AppState, Error}; +use axum::extract::{Path, Query, State}; use axum::response::IntoResponse; use axum::Json; use entity::organization; +use entity::organization::Entity as Organization; use sea_orm::entity::EntityTrait; +use sea_orm::ActiveModelTrait; +use sea_orm::ActiveValue::{NotSet, Set}; +use sea_orm::DeleteResult; +use serde_json::json; + +extern crate log; +use log::*; pub struct OrganizationController {} impl OrganizationController { + /// GET all Organizations + /// Test this with curl: curl --header "Content-Type: application/json" \ in zsh at 12:03:06 + /// --request GET \ + /// http://localhost:3000/organizations pub async fn index(State(app_state): State) -> impl IntoResponse { let organizations = organization::Entity::find() .all(&app_state.database_connection.unwrap()) @@ -16,4 +28,101 @@ impl OrganizationController { Json(organizations) } + + /// GET a particular Organization entity specified by its primary key + /// Test this with curl: curl --header "Content-Type: application/json" \ in zsh at 12:03:06 + /// --request GET \ + /// http://localhost:3000/organizations/ + pub async fn read(State(app_state): State, Path(id): Path) -> impl IntoResponse { + debug!("GET Organization by id: {}", id); + + let organization: Option = organization::Entity::find_by_id(id) + .one(&app_state.database_connection.unwrap()) + .await + .unwrap_or_default(); + + Json(organization) + } + + /// CREATE a new Organization entity + /// Test this with curl: curl --header "Content-Type: application/json" \ + /// --request POST \ + /// --data '{"name":"My New Organization"}' \ + /// http://localhost:3000/organizations + pub async fn create( + State(app_state): State, + Json(organization_json): Json, + ) -> impl IntoResponse { + debug!("CREATE new Organization: {}", organization_json.name); + + let organization_active_model = organization::ActiveModel { + id: NotSet, + name: Set(organization_json.name), + }; + + let organization: organization::Model = organization_active_model + .insert(&app_state.database_connection.unwrap()) + .await + .unwrap_or_default(); + + Json(organization) + } + + /// UPDATE a particular Organization entity specified by its primary key + /// Test this with curl: curl --header "Content-Type: application/json" \ in zsh at 12:03:06 + /// --request PUT \ + /// http://localhost:3000/organizations/\?name\=New_Organization_Name + pub async fn update( + State(app_state): State, + Path(id): Path, + Query(organization_params): Query, + ) -> Result, Error> { + debug!( + "UPDATE the entire Organization by id: {}, new name: {}", + id, organization_params.name + ); + + let db = app_state.database_connection.as_ref().unwrap(); + + let organization_to_update = organization::Entity::find_by_id(id) + .one(db) + .await + .unwrap_or_default(); + + let updated_organization = match organization_to_update { + Some(org) => { + let mut organization_am: organization::ActiveModel = org.into(); + organization_am.name = Set(organization_params.name); + + organization::Entity::update(organization_am) + .exec(db) + .await + .unwrap() + } + None => return Err(Error::EntityNotFound), + }; + + Ok(Json(updated_organization)) + } + + /// DELETE an Organization entity specified by its primary key + /// Test this with curl: curl --header "Content-Type: application/json" \ in zsh at 12:03:06 + /// --request DELETE \ + /// http://localhost:3000/organizations/ + pub async fn delete( + State(app_state): State, + Path(id): Path, + ) -> impl IntoResponse { + debug!("DELETE Organization by id: {}", id); + + let res: DeleteResult = Organization::delete_by_id(id) + .exec(&app_state.database_connection.unwrap()) + .await + .unwrap(); + + // TODO: temporary check while learning, return a DBErr instead + assert_eq!(res.rows_affected, 1); + + Json(json!({"id": id})) + } } diff --git a/web/src/error.rs b/web/src/error.rs index 7a6dca2..09a8436 100644 --- a/web/src/error.rs +++ b/web/src/error.rs @@ -5,12 +5,18 @@ pub type Result = core::result::Result; #[derive(Debug)] pub enum Error { - PlaceholderError, + InternalServer, + EntityNotFound, } impl IntoResponse for Error { fn into_response(self) -> Response { - (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response() + match self { + Error::InternalServer => { + (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response() + } + Error::EntityNotFound => (StatusCode::NOT_FOUND, "ENTITY NOT FOUND").into_response(), + } } } diff --git a/web/src/router.rs b/web/src/router.rs index 5b0c180..cef44f7 100644 --- a/web/src/router.rs +++ b/web/src/router.rs @@ -1,6 +1,6 @@ use crate::AppState; use axum::{ - routing::{get, get_service}, + routing::{delete, get, get_service, post, put}, Router, }; use tower_http::services::ServeDir; @@ -15,7 +15,13 @@ pub fn define_routes(app_state: AppState) -> Router { pub fn organization_routes(app_state: AppState) -> Router { Router::new() - .route("/organization", get(OrganizationController::index)) + // TODO: Add an API versioning scheme and prefix all routes with it + // See Router::nest() - https://docs.rs/axum/latest/axum/struct.Router.html#method.nest + .route("/organizations", get(OrganizationController::index)) + .route("/organizations/:id", get(OrganizationController::read)) + .route("/organizations", post(OrganizationController::create)) + .route("/organizations/:id", put(OrganizationController::update)) + .route("/organizations/:id", delete(OrganizationController::delete)) .with_state(app_state) }