Skip to content

Commit

Permalink
Merge pull request #13 from Jim-Hodapp-Coaching/add_organization_crud…
Browse files Browse the repository at this point in the history
…_handlers

Add Organization CRUD handlers
  • Loading branch information
jhodapp authored Nov 16, 2023
2 parents 52c4b3e + d1fefb7 commit 3ca3c9d
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 7 deletions.
3 changes: 2 additions & 1 deletion entity/src/organization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
113 changes: 111 additions & 2 deletions web/src/controller/organization_controller.rs
Original file line number Diff line number Diff line change
@@ -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<AppState>) -> impl IntoResponse {
let organizations = organization::Entity::find()
.all(&app_state.database_connection.unwrap())
Expand All @@ -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/<id>
pub async fn read(State(app_state): State<AppState>, Path(id): Path<i32>) -> impl IntoResponse {
debug!("GET Organization by id: {}", id);

let organization: Option<organization::Model> = 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<AppState>,
Json(organization_json): Json<organization::Model>,
) -> 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/<id>\?name\=New_Organization_Name
pub async fn update(
State(app_state): State<AppState>,
Path(id): Path<i32>,
Query(organization_params): Query<organization::Model>,
) -> Result<Json<entity::organization::Model>, 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/<id>
pub async fn delete(
State(app_state): State<AppState>,
Path(id): Path<i32>,
) -> 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}))
}
}
10 changes: 8 additions & 2 deletions web/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ pub type Result<T> = core::result::Result<T, Error>;

#[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(),
}
}
}

Expand Down
10 changes: 8 additions & 2 deletions web/src/router.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
}

Expand Down

0 comments on commit 3ca3c9d

Please sign in to comment.