Skip to content

Commit

Permalink
feat(organization): add organization table (#2669)
Browse files Browse the repository at this point in the history
  • Loading branch information
Narayanbhat166 authored Oct 30, 2023
1 parent 23bd364 commit d682471
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 4 deletions.
1 change: 1 addition & 0 deletions crates/api_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod ephemeral_key;
pub mod errors;
pub mod files;
pub mod mandates;
pub mod organization;
pub mod payment_methods;
pub mod payments;
#[cfg(feature = "payouts")]
Expand Down
13 changes: 13 additions & 0 deletions crates/api_models/src/organization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pub struct OrganizationNew {
pub org_id: String,
pub org_name: Option<String>,
}

impl OrganizationNew {
pub fn new(org_name: Option<String>) -> Self {
Self {
org_id: common_utils::generate_id_with_default_len("org"),
org_name,
}
}
}
1 change: 1 addition & 0 deletions crates/diesel_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod mandate;
pub mod merchant_account;
pub mod merchant_connector_account;
pub mod merchant_key_store;
pub mod organization;
pub mod payment_attempt;
pub mod payment_intent;
pub mod payment_link;
Expand Down
35 changes: 35 additions & 0 deletions crates/diesel_models/src/organization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use diesel::{AsChangeset, Identifiable, Insertable, Queryable};

use crate::schema::organization;

#[derive(Clone, Debug, Identifiable, Queryable)]
#[diesel(table_name = organization, primary_key(org_id))]
pub struct Organization {
pub org_id: String,
pub org_name: Option<String>,
}

#[derive(Clone, Debug, Insertable)]
#[diesel(table_name = organization, primary_key(org_id))]
pub struct OrganizationNew {
pub org_id: String,
pub org_name: Option<String>,
}

#[derive(Clone, Debug, AsChangeset)]
#[diesel(table_name = organization)]
pub struct OrganizationUpdateInternal {
org_name: Option<String>,
}

pub enum OrganizationUpdate {
Update { org_name: Option<String> },
}

impl From<OrganizationUpdate> for OrganizationUpdateInternal {
fn from(value: OrganizationUpdate) -> Self {
match value {
OrganizationUpdate::Update { org_name } => Self { org_name },
}
}
}
1 change: 1 addition & 0 deletions crates/diesel_models/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod mandate;
pub mod merchant_account;
pub mod merchant_connector_account;
pub mod merchant_key_store;
pub mod organization;
pub mod payment_attempt;
pub mod payment_intent;
pub mod payment_link;
Expand Down
38 changes: 38 additions & 0 deletions crates/diesel_models/src/query/organization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use diesel::{associations::HasTable, ExpressionMethods};
use router_env::tracing::{self, instrument};

use crate::{
organization::*, query::generics, schema::organization::dsl, PgPooledConn, StorageResult,
};

impl OrganizationNew {
#[instrument(skip(conn))]
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Organization> {
generics::generic_insert(conn, self).await
}
}

impl Organization {
pub async fn find_by_org_id(conn: &PgPooledConn, org_id: String) -> StorageResult<Self> {
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(conn, dsl::org_id.eq(org_id))
.await
}

pub async fn update_by_org_id(
conn: &PgPooledConn,
org_id: String,
update: OrganizationUpdate,
) -> StorageResult<Self> {
generics::generic_update_with_unique_predicate_get_result::<
<Self as HasTable>::Table,
_,
_,
_,
>(
conn,
dsl::org_id.eq(org_id),
OrganizationUpdateInternal::from(update),
)
.await
}
}
12 changes: 12 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,17 @@ diesel::table! {
}
}

diesel::table! {
use diesel::sql_types::*;
use crate::enums::diesel_exports::*;

organization (org_id) {
#[max_length = 32]
org_id -> Varchar,
org_name -> Nullable<Text>,
}
}

diesel::table! {
use diesel::sql_types::*;
use crate::enums::diesel_exports::*;
Expand Down Expand Up @@ -879,6 +890,7 @@ diesel::allow_tables_to_appear_in_same_query!(
merchant_account,
merchant_connector_account,
merchant_key_store,
organization,
payment_attempt,
payment_intent,
payment_link,
Expand Down
24 changes: 20 additions & 4 deletions crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@ use crate::{
types::{self as domain_types, AsyncLift},
},
storage::{self, enums::MerchantStorageScheme},
transformers::ForeignTryFrom,
transformers::{ForeignFrom, ForeignTryFrom},
},
utils::{self, OptionExt},
};

const DEFAULT_ORG_ID: &str = "org_abcdefghijklmn";

#[inline]
pub fn create_merchant_publishable_key() -> String {
format!(
Expand Down Expand Up @@ -146,6 +144,24 @@ pub async fn create_merchant_account(
})
.transpose()?;

let organization_id = if let Some(organization_id) = req.organization_id.as_ref() {
db.find_organization_by_org_id(organization_id)
.await
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
message: "organization with the given id does not exist".to_string(),
})?;
organization_id.to_string()
} else {
let new_organization = api_models::organization::OrganizationNew::new(None);
let db_organization = ForeignFrom::foreign_from(new_organization);
let organization = db
.insert_organization(db_organization)
.await
.to_duplicate_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error when creating organization")?;
organization.org_id
};

let mut merchant_account = async {
Ok(domain::MerchantAccount {
merchant_id: req.merchant_id,
Expand Down Expand Up @@ -177,7 +193,7 @@ pub async fn create_merchant_account(
intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from),
payout_routing_algorithm: req.payout_routing_algorithm,
id: None,
organization_id: req.organization_id.unwrap_or(DEFAULT_ORG_ID.to_string()),
organization_id,
is_recon_enabled: false,
default_profile: None,
recon_status: diesel_models::enums::ReconStatus::NotRequested,
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod mandate;
pub mod merchant_account;
pub mod merchant_connector_account;
pub mod merchant_key_store;
pub mod organization;
pub mod payment_link;
pub mod payment_method;
pub mod payout_attempt;
Expand Down Expand Up @@ -75,6 +76,7 @@ pub trait StorageInterface:
+ payment_link::PaymentLinkInterface
+ RedisConnInterface
+ business_profile::BusinessProfileInterface
+ organization::OrganizationInterface
+ 'static
{
fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
Expand Down
131 changes: 131 additions & 0 deletions crates/router/src/db/organization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use common_utils::errors::CustomResult;
use diesel_models::organization as storage;
use error_stack::IntoReport;

use crate::{connection, core::errors, services::Store};

#[async_trait::async_trait]
pub trait OrganizationInterface {
async fn insert_organization(
&self,
organization: storage::OrganizationNew,
) -> CustomResult<storage::Organization, errors::StorageError>;

async fn find_organization_by_org_id(
&self,
org_id: &str,
) -> CustomResult<storage::Organization, errors::StorageError>;

async fn update_organization_by_org_id(
&self,
user_id: &str,
update: storage::OrganizationUpdate,
) -> CustomResult<storage::Organization, errors::StorageError>;
}

#[async_trait::async_trait]
impl OrganizationInterface for Store {
async fn insert_organization(
&self,
organization: storage::OrganizationNew,
) -> CustomResult<storage::Organization, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
organization
.insert(&conn)
.await
.map_err(Into::into)
.into_report()
}

async fn find_organization_by_org_id(
&self,
org_id: &str,
) -> CustomResult<storage::Organization, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Organization::find_by_org_id(&conn, org_id.to_string())
.await
.map_err(Into::into)
.into_report()
}

async fn update_organization_by_org_id(
&self,
org_id: &str,
update: storage::OrganizationUpdate,
) -> CustomResult<storage::Organization, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;

storage::Organization::update_by_org_id(&conn, org_id.to_string(), update)
.await
.map_err(Into::into)
.into_report()
}
}

#[async_trait::async_trait]
impl OrganizationInterface for super::MockDb {
async fn insert_organization(
&self,
organization: storage::OrganizationNew,
) -> CustomResult<storage::Organization, errors::StorageError> {
let mut organizations = self.organizations.lock().await;

if organizations
.iter()
.any(|org| org.org_id == organization.org_id)
{
Err(errors::StorageError::DuplicateValue {
entity: "org_id",
key: None,
})?
}
let org = storage::Organization {
org_id: organization.org_id.clone(),
org_name: organization.org_name,
};
organizations.push(org.clone());
Ok(org)
}

async fn find_organization_by_org_id(
&self,
org_id: &str,
) -> CustomResult<storage::Organization, errors::StorageError> {
let organizations = self.organizations.lock().await;

organizations
.iter()
.find(|org| org.org_id == org_id)
.cloned()
.ok_or(
errors::StorageError::ValueNotFound(format!(
"No organization available for org_id = {org_id}"
))
.into(),
)
}

async fn update_organization_by_org_id(
&self,
org_id: &str,
update: storage::OrganizationUpdate,
) -> CustomResult<storage::Organization, errors::StorageError> {
let mut organizations = self.organizations.lock().await;

organizations
.iter_mut()
.find(|org| org.org_id == org_id)
.map(|org| match &update {
storage::OrganizationUpdate::Update { org_name } => storage::Organization {
org_name: org_name.clone(),
..org.to_owned()
},
})
.ok_or(
errors::StorageError::ValueNotFound(format!(
"No organization available for org_id = {org_id}"
))
.into(),
)
}
}
11 changes: 11 additions & 0 deletions crates/router/src/types/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,3 +861,14 @@ impl From<domain::Address> for payments::AddressDetails {
}
}
}

impl ForeignFrom<api_models::organization::OrganizationNew>
for diesel_models::organization::OrganizationNew
{
fn foreign_from(item: api_models::organization::OrganizationNew) -> Self {
Self {
org_id: item.org_id,
org_name: item.org_name,
}
}
}
2 changes: 2 additions & 0 deletions crates/storage_impl/src/mock_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct MockDb {
pub business_profiles: Arc<Mutex<Vec<crate::store::business_profile::BusinessProfile>>>,
pub reverse_lookups: Arc<Mutex<Vec<store::ReverseLookup>>>,
pub payment_link: Arc<Mutex<Vec<store::payment_link::PaymentLink>>>,
pub organizations: Arc<Mutex<Vec<store::organization::Organization>>>,
}

impl MockDb {
Expand Down Expand Up @@ -74,6 +75,7 @@ impl MockDb {
business_profiles: Default::default(),
reverse_lookups: Default::default(),
payment_link: Default::default(),
organizations: Default::default(),
})
}
}
2 changes: 2 additions & 0 deletions migrations/2023-10-23-101023_add_organization_table/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE IF EXISTS ORGANIZATION;
5 changes: 5 additions & 0 deletions migrations/2023-10-23-101023_add_organization_table/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Your SQL goes here
CREATE TABLE IF NOT EXISTS ORGANIZATION (
org_id VARCHAR(32) PRIMARY KEY NOT NULL,
org_name TEXT
);
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,11 @@ if (jsonData?.merchant_id) {
"INFO - Unable to assign variable {{organization_id}}, as jsonData.organization_id is undefined.",
);
}

// Response body should have "mandate_id"
pm.test(
"[POST]::/accounts - Organization id is generated",
function () {
pm.expect(typeof jsonData.organization_id !== "undefined").to.be.true;
},
);

0 comments on commit d682471

Please sign in to comment.