Skip to content

Commit

Permalink
added schema and crl apis for organisation
Browse files Browse the repository at this point in the history
  • Loading branch information
sauraww committed Dec 20, 2024
1 parent c44bd0c commit c27e5b4
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ HOSTNAME="<application_name>-<deployment_id>-<replicaset>-<pod>"
ACTIX_KEEP_ALIVE=120
MAX_DB_CONNECTION_POOL_SIZE=3
ENABLE_TENANT_AND_SCOPE=true
TENANTS=dev,test
TENANTS=dev,test,superposition
TENANT_MIDDLEWARE_EXCLUSION_LIST="/health,/assets/favicon.ico,/pkg/frontend.js,/pkg,/pkg/frontend_bg.wasm,/pkg/tailwind.css,/pkg/style.css,/assets,/admin,/"
SERVICE_PREFIX=""
SERVICE_NAME="CAC"
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/frontend",
"crates/cac_toml",
"crates/superposition",
"crates/superposition/organisation",
"crates/superposition_types",
"examples/experimentation_client_integration_example",
"examples/cac_client_integration_example",
Expand Down
3 changes: 2 additions & 1 deletion crates/service_utils/src/service/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl FromStr for AppEnv {
pub enum AppScope {
CAC,
EXPERIMENTATION,
SUPERPOSITION,
}
impl FromRequest for AppScope {
type Error = Error;
Expand Down Expand Up @@ -112,7 +113,7 @@ impl AppExecutionNamespace {
app_state.app_env,
tenant,
scope,
) {
) {
(false, _, _, _) => Ok(AppExecutionNamespace("cac_v1".to_string())),
(true, _, Some(t), Some(_)) => Ok(AppExecutionNamespace(t.0)),
(true, _, None, _) => {
Expand Down
2 changes: 2 additions & 0 deletions crates/superposition/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ context_aware_config = { path = "../context_aware_config" }
dotenv = "0.15.0"
env_logger = "0.8"
experimentation_platform = { path = "../experimentation_platform" }
organisation = { path = "./organisation" }
fred = { workspace = true, optional = true }
frontend = { path = "../frontend" }
leptos = { workspace = true }
Expand All @@ -24,6 +25,7 @@ serde_json = { workspace = true }
service_utils = { path = "../service_utils" }
superposition_types = { path = "../superposition_types" }
toml = { workspace = true }
idgenerator = "2.0.0"

[features]
high-performance-mode = [
Expand Down
21 changes: 21 additions & 0 deletions crates/superposition/organisation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "organisation"
version = "0.1.0"
edition = "2021"


[dependencies]
service_utils = { path = "../../service_utils" }
superposition_types = { path = "../../superposition_types", features = [
"result",
"diesel_derives",
]}
serde = { workspace = true }
serde_json = { workspace = true }
chrono = { workspace = true }
actix-web = { workspace = true }
anyhow = { workspace = true }
diesel = { workspace = true , features = ["numeric"]}
idgenerator = "2.0.0"
log = { workspace = true }
superposition_macros = { path = "../../superposition_macros" }
140 changes: 140 additions & 0 deletions crates/superposition/organisation/src/api/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use actix_web::{web::{self,Json, Query}, HttpResponse, Scope, post,get};
use diesel::prelude::*;
use chrono::Utc;
use idgenerator::IdInstance;
use service_utils::service::types:: DbConnection;
use superposition_types::{custom_query::PaginationParams, result as superposition, PaginatedResponse } ;
use super::types::{CreateOrganisationRequest, OrganisationResponse, CreateOrganisationResponse};


use superposition_types::database::{models::organisation:: Organisation, schema::organisation::dsl::organisation};



pub fn endpoints() -> Scope {
Scope::new("")
.service(create_organisation)
.service(list_organisations)
.service(get_organisation)
}

#[post("")]
pub async fn create_organisation(
req: web::Json<CreateOrganisationRequest>,
mut db_conn: DbConnection,
) -> superposition::Result<HttpResponse> {

let org_id = db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
// Generating a numeric ID from IdInstance and prefixing it with `orgid`
let numeric_id = IdInstance::next_id();
let org_id = format!("orgid{}", numeric_id);

let now = Utc::now().naive_utc();

let new_org = Organisation {
id: org_id.clone(),
country_code: req.country_code.clone(),
contact_email: req.contact_email.clone(),
contact_phone: req.contact_phone.clone(),
created_by: req.created_by.clone(),
admin_email: req.admin_email.clone(),
status: req.status,
contact_details: req.contact_details.clone(),
sector: req.sector.clone(),
industry: req.industry.clone(),
created_at: now,
updated_at: now,
updated_by: req.created_by.clone(),
};

diesel::insert_into(organisation)
.values(&new_org)
.execute(transaction_conn)
.map_err(|e| {
log::error!("Failed to insert new organisation: {:?}", e);
superposition::AppError::UnexpectedError(anyhow::anyhow!("Failed to create organisation"))
})?;
Ok(org_id)
})?;

let mut http_resp = HttpResponse::Created();
Ok(http_resp.json(CreateOrganisationResponse { org_id }))
}

#[get("/{org_id}")]
pub async fn get_organisation(
org_id: web::Path<String>,
mut db_conn: DbConnection,
) -> superposition::Result<HttpResponse> {
let org = db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
organisation
.find(org_id.as_str())
.first::<Organisation>(transaction_conn)
.map_err(|e| {
log::error!("Failed to fetch organisation {}: {:?}", org_id, e);
match e {
diesel::result::Error::NotFound => {
superposition::AppError::NotFound(format!("Organisation {} not found", org_id))
}
_ => superposition::AppError::UnexpectedError(anyhow::anyhow!(
"Failed to fetch organisation"
)),
}
})
})?;

Ok(HttpResponse::Ok().json(OrganisationResponse::from(org)))
}

#[get("/list")]
pub async fn list_organisations(
db_conn: DbConnection,
filters: Query<PaginationParams>,
) -> superposition::Result<Json<PaginatedResponse<Organisation>>> {
use superposition_types::database::schema::organisation::dsl::*;
let DbConnection(mut conn) = db_conn;
log::info!("list_organisations");
let result = conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
// If all parameter is true, return all organisations
if let Some(true) = filters.all {
let organisations: Vec<Organisation> = organisation
.order(created_at.desc())
.get_results(transaction_conn)?;
log::info!("organisations: {organisations:?}");
return Ok(PaginatedResponse {
total_pages: 1,
total_items: organisations.len() as i64,
data: organisations,
});
}

// Get total count of organisations
let total_items: i64 = organisation.count().get_result(transaction_conn)?;

// Set up pagination
let limit = filters.count.unwrap_or(10);
let mut builder = organisation
.into_boxed()
.order(created_at.desc())
.limit(limit);

// Apply offset if page is specified
if let Some(page) = filters.page {
let offset = (page - 1) * limit;
builder = builder.offset(offset);
}

// Get paginated results
let organisations: Vec<Organisation> = builder.load(transaction_conn)?;

let total_pages = (total_items as f64 / limit as f64).ceil() as i64;

Ok(PaginatedResponse {
total_pages,
total_items,
data: organisations,
})
})?;

Ok(Json(result))
}
3 changes: 3 additions & 0 deletions crates/superposition/organisation/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod handlers;
pub mod types;
pub use handlers::endpoints;
84 changes: 84 additions & 0 deletions crates/superposition/organisation/src/api/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use superposition_types::database::models::organisation::OrgStatus;
use superposition_types::database::models::organisation:: Organisation;

// Request payload for creating an organisation
#[derive(Deserialize)]
pub struct CreateOrganisationRequest {
pub country_code: Option<String>,
pub contact_email: Option<String>,
pub contact_phone: Option<String>,
pub created_by: String,
pub admin_email: String,
pub status: OrgStatus,
pub contact_details: Option<JsonValue>,
pub sector: Option<String>,
pub industry: Option<String>,
}


// Response type to include `org_id`
#[derive(Serialize)]
pub struct CreateOrganisationResponse {
pub org_id: String,
}

// Response struct for single organisation
#[derive(Serialize)]
pub struct OrganisationResponse {
id: String,
country_code: Option<String>,
contact_email: Option<String>,
contact_phone: Option<String>,
admin_email: String,
status: OrgStatus,
contact_details: Option<JsonValue>,
sector: Option<String>,
industry: Option<String>,
created_at: chrono::NaiveDateTime,
updated_at: chrono::NaiveDateTime,
created_by: String,
updated_by: String,
}

impl From<Organisation> for OrganisationResponse {
fn from(org: Organisation) -> Self {
OrganisationResponse {
id: org.id,
country_code: org.country_code,
contact_email: org.contact_email,
contact_phone: org.contact_phone,
admin_email: org.admin_email,
status: org.status,
contact_details: org.contact_details,
sector: org.sector,
industry: org.industry,
created_at: org.created_at,
updated_at: org.updated_at,
created_by: org.created_by,
updated_by: org.updated_by,
}
}
}


#[derive(Deserialize,Default)]
pub struct OrganisationFilters {
pub page: Option<i64>,
pub size: Option<i64>,
pub sort_by: Option<SortBy>,
pub sort_on: Option<OrganisationSortOn>,
}

#[derive(Deserialize)]
pub enum SortBy {
Asc,
Desc,
}

#[derive(Deserialize)]
pub enum OrganisationSortOn {
CreatedAt,
Status,
}
1 change: 1 addition & 0 deletions crates/superposition/organisation/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod api;
Loading

0 comments on commit c27e5b4

Please sign in to comment.