Skip to content

Commit

Permalink
chore: migrate tenant and provider_config to diesel (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
azhur authored Apr 22, 2024
1 parent fd21bd5 commit b4ad915
Show file tree
Hide file tree
Showing 45 changed files with 693 additions and 561 deletions.
1 change: 1 addition & 0 deletions modules/meteroid/crates/diesel-models/src/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct ProviderConfig {
#[derive(Insertable, Debug)]
#[diesel(table_name = crate::schema::provider_config)]
pub struct ProviderConfigNew {
pub id: Uuid,
pub tenant_id: Uuid,
pub invoicing_provider: InvoicingProviderEnum,
pub enabled: bool,
Expand Down
6 changes: 4 additions & 2 deletions modules/meteroid/crates/diesel-models/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ impl<T> IntoDbResult for Result<T, DieselError> {
Ok(value) => Ok(value),
Err(err) => {
let db_err = DatabaseError::from(&err);
Err(Report::from(err).change_context(db_err)).map_err(DatabaseErrorContainer::from)
Err(DatabaseErrorContainer::from(
Report::from(err).change_context(db_err),
))
}
}
}
Expand All @@ -77,7 +79,7 @@ impl<T> IntoDbResult for error_stack::Result<T, DieselError> {
Ok(value) => Ok(value),
Err(err) => {
let db_err = DatabaseError::from(err.current_context());
Err(Report::from(err).change_context(db_err)).map_err(DatabaseErrorContainer::from)
Err(DatabaseErrorContainer::from(err.change_context(db_err)))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions modules/meteroid/crates/diesel-models/src/organizations.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use chrono::NaiveDateTime;
use uuid::Uuid;

use diesel::{Identifiable, Insertable, Queryable};
use diesel::{Identifiable, Insertable, Queryable, Selectable};

#[derive(Queryable, Debug, Identifiable)]
#[derive(Queryable, Debug, Identifiable, Selectable)]
#[diesel(table_name = crate::schema::organization)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Organization {
Expand Down
39 changes: 37 additions & 2 deletions modules/meteroid/crates/diesel-models/src/query/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ use crate::configs::{InvoicingConfig, ProviderConfig, ProviderConfigNew};
use crate::errors::IntoDbResult;
use crate::{DbResult, PgConn};

use diesel::debug_query;
use crate::enums::InvoicingProviderEnum;
use diesel::prelude::{ExpressionMethods, QueryDsl};
use diesel::{debug_query, DecoratableTarget};
use error_stack::ResultExt;

impl ProviderConfigNew {
pub async fn insert(&self, conn: &mut PgConn) -> DbResult<ProviderConfig> {
use crate::schema::provider_config::dsl::*;
use diesel_async::RunQueryDsl;

let query = diesel::insert_into(provider_config).values(self);
let query = diesel::insert_into(provider_config)
.values(self)
.on_conflict((tenant_id, invoicing_provider))
.filter_target(enabled.eq(true))
.do_update()
.set((
enabled.eq(self.enabled),
webhook_security.eq(&self.webhook_security),
api_security.eq(&self.api_security),
));

log::debug!("{}", debug_query::<diesel::pg::Pg, _>(&query).to_string());

Expand Down Expand Up @@ -38,3 +49,27 @@ impl InvoicingConfig {
.into_db_result()
}
}

impl ProviderConfig {
pub async fn find_provider_config(
conn: &mut PgConn,
tenant_uid: uuid::Uuid,
provider: InvoicingProviderEnum,
) -> DbResult<ProviderConfig> {
use crate::schema::provider_config::dsl::*;
use diesel_async::RunQueryDsl;

let query = provider_config
.filter(tenant_id.eq(tenant_uid))
.filter(invoicing_provider.eq(provider))
.filter(enabled.eq(true));

log::debug!("{}", debug_query::<diesel::pg::Pg, _>(&query).to_string());

query
.first(conn)
.await
.attach_printable("Error while finding provider config")
.into_db_result()
}
}
26 changes: 25 additions & 1 deletion modules/meteroid/crates/diesel-models/src/query/organizations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use crate::organizations::{Organization, OrganizationNew};

use crate::{DbResult, PgConn};

use diesel::debug_query;
use diesel::prelude::{ExpressionMethods, QueryDsl};
use diesel::{debug_query, JoinOnDsl, SelectableHelper};
use error_stack::ResultExt;

impl OrganizationNew {
Expand All @@ -22,3 +23,26 @@ impl OrganizationNew {
.into_db_result()
}
}

impl Organization {
pub async fn by_user_id(conn: &mut PgConn, user_id: uuid::Uuid) -> DbResult<Organization> {
use crate::schema::organization::dsl as o_dsl;
use crate::schema::organization_member::dsl as om_dsl;
use crate::schema::user::dsl as u_dsl;
use diesel_async::RunQueryDsl;

let query = o_dsl::organization
.inner_join(om_dsl::organization_member.on(o_dsl::id.eq(om_dsl::organization_id)))
.inner_join(u_dsl::user.on(om_dsl::user_id.eq(u_dsl::id)))
.filter(u_dsl::id.eq(user_id))
.select(Organization::as_select());

log::debug!("{}", debug_query::<diesel::pg::Pg, _>(&query).to_string());

query
.first(conn)
.await
.attach_printable("Error while finding organization by user_id")
.into_db_result()
}
}
25 changes: 24 additions & 1 deletion modules/meteroid/crates/diesel-models/src/query/tenants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::errors::IntoDbResult;
use crate::tenants::{Tenant, TenantNew};
use crate::{DbResult, PgConn};

use diesel::debug_query;
use diesel::prelude::{ExpressionMethods, QueryDsl};
use diesel::{debug_query, JoinOnDsl, SelectableHelper};
use error_stack::ResultExt;

impl TenantNew {
Expand Down Expand Up @@ -36,4 +36,27 @@ impl Tenant {
.attach_printable("Error while finding tenant by id")
.into_db_result()
}

pub async fn list_by_user_id(conn: &mut PgConn, user_id: uuid::Uuid) -> DbResult<Vec<Tenant>> {
use crate::schema::organization::dsl as o_dsl;
use crate::schema::organization_member::dsl as om_dsl;
use crate::schema::tenant::dsl as t_dsl;
use crate::schema::user::dsl as u_dsl;
use diesel_async::RunQueryDsl;

let query = t_dsl::tenant
.inner_join(o_dsl::organization.on(t_dsl::organization_id.eq(o_dsl::id)))
.inner_join(om_dsl::organization_member.on(om_dsl::organization_id.eq(o_dsl::id)))
.inner_join(u_dsl::user.on(u_dsl::id.eq(om_dsl::user_id)))
.filter(u_dsl::id.eq(user_id))
.select(Tenant::as_select());

log::debug!("{}", debug_query::<diesel::pg::Pg, _>(&query).to_string());

query
.get_results(conn)
.await
.attach_printable("Error while fetching tenants by user_id")
.into_db_result()
}
}
4 changes: 2 additions & 2 deletions modules/meteroid/crates/diesel-models/src/tenants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use uuid::Uuid;

use crate::enums::TenantEnvironmentEnum;

use diesel::{Identifiable, Insertable, Queryable};
use diesel::{Identifiable, Insertable, Queryable, Selectable};

#[derive(Clone, Queryable, Debug, Identifiable)]
#[derive(Clone, Queryable, Debug, Identifiable, Selectable)]
#[diesel(table_name = crate::schema::tenant)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Tenant {
Expand Down
4 changes: 3 additions & 1 deletion modules/meteroid/crates/meteroid-store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ rust_decimal.workspace = true
rust_decimal_macros.workspace = true
common-utils = { workspace = true, features = ["decimal"] }
itertools.workspace = true

secrecy = { workspace = true, features = ["default", "serde"] }
chacha20poly1305 = { workspace = true }
hex = { workspace = true }

[dev-dependencies]
rstest = { workspace = true }
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use chacha20poly1305::{
};
use error_stack::{Result, ResultExt};
use secrecy::{ExposeSecret, SecretString};
use tonic::Status;

const NONCE_SIZE: usize = 12;

Expand All @@ -20,13 +19,7 @@ pub enum EncryptionError {
DecryptError,
}

impl From<EncryptionError> for Status {
fn from(error: EncryptionError) -> Self {
Status::new(tonic::Code::Internal, error.to_string())
}
}

pub fn encrypt(crypt_key: &SecretString, value: &str) -> Result<SecretString, EncryptionError> {
pub fn encrypt(crypt_key: &SecretString, value: &str) -> Result<String, EncryptionError> {
let cipher = ChaCha20Poly1305::new_from_slice(crypt_key.expose_secret().as_bytes())
.change_context(EncryptionError::InvalidKey)?;

Expand All @@ -36,7 +29,7 @@ pub fn encrypt(crypt_key: &SecretString, value: &str) -> Result<SecretString, En
.encrypt(nonce, value.as_bytes())
.map_err(|_| EncryptionError::EncryptError)?;

Ok(SecretString::new(hex::encode(ciphertext)))
Ok(hex::encode(ciphertext))
}

pub fn decrypt(key: &SecretString, value: &str) -> Result<SecretString, EncryptionError> {
Expand Down Expand Up @@ -90,9 +83,9 @@ mod tests {

let encrypted = super::encrypt(&key, raw_str).unwrap();

assert_eq!(encrypted.expose_secret().as_str(), encrypted_str);
assert_eq!(encrypted.as_str(), encrypted_str);

let decrypted = super::decrypt(&key, encrypted.expose_secret().as_str()).unwrap();
let decrypted = super::decrypt(&key, encrypted.as_str()).unwrap();

assert_eq!(decrypted.expose_secret().as_str(), raw_str);
}
Expand Down
120 changes: 120 additions & 0 deletions modules/meteroid/crates/meteroid-store/src/domain/configs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::domain::enums::InvoicingProviderEnum;
use crate::errors::StoreError;
use crate::StoreResult;
use chrono::NaiveDateTime;
use error_stack::ResultExt;
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WebhookSecurity {
pub secret: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ApiSecurity {
pub api_key: String,
}

#[derive(Clone, Debug)]
pub struct ProviderConfig {
pub id: Uuid,
pub created_at: NaiveDateTime,
pub tenant_id: Uuid,
pub invoicing_provider: InvoicingProviderEnum,
pub enabled: bool,
pub webhook_security: WebhookSecurity,
pub api_security: ApiSecurity,
}

impl ProviderConfig {
pub fn from_row(
key: &SecretString,
row: diesel_models::configs::ProviderConfig,
) -> StoreResult<ProviderConfig> {
let enc_wh_sec: WebhookSecurity =
serde_json::from_value(row.webhook_security).map_err(|e| {
StoreError::SerdeError("Failed to deserialize webhook_security".to_string(), e)
})?;

let enc_api_sec: ApiSecurity = serde_json::from_value(row.api_security).map_err(|e| {
StoreError::SerdeError("Failed to deserialize api_security".to_string(), e)
})?;

let wh_sec = WebhookSecurity {
secret: crate::crypt::decrypt(key, enc_wh_sec.secret.as_str())
.change_context(StoreError::CryptError(
"webhook_security decryption error".into(),
))?
.expose_secret()
.clone(),
};

let api_sec = ApiSecurity {
api_key: crate::crypt::decrypt(key, enc_api_sec.api_key.as_str())
.change_context(StoreError::CryptError(
"api_security decryption error".into(),
))?
.expose_secret()
.clone(),
};

Ok(ProviderConfig {
id: row.id,
created_at: row.created_at,
tenant_id: row.tenant_id,
invoicing_provider: row.invoicing_provider.into(),
enabled: row.enabled,
webhook_security: wh_sec,
api_security: api_sec,
})
}
}

#[derive(Clone, Debug)]
pub struct ProviderConfigNew {
pub tenant_id: Uuid,
pub invoicing_provider: InvoicingProviderEnum,
pub enabled: bool,
pub webhook_security: WebhookSecurity,
pub api_security: ApiSecurity,
}

impl ProviderConfigNew {
pub fn domain_to_row(
key: &SecretString,
domain: &ProviderConfigNew,
) -> StoreResult<diesel_models::configs::ProviderConfigNew> {
let wh_sec_enc = WebhookSecurity {
secret: crate::crypt::encrypt(key, domain.webhook_security.secret.as_str())
.change_context(StoreError::CryptError(
"webhook_security encryption error".into(),
))?,
};

let api_sec_enc = ApiSecurity {
api_key: crate::crypt::encrypt(key, domain.api_security.api_key.as_str())
.change_context(StoreError::CryptError(
"api_security encryption error".into(),
))?,
};

let wh_sec = serde_json::to_value(&wh_sec_enc).map_err(|e| {
StoreError::SerdeError("Failed to serialize webhook_security".to_string(), e)
})?;

let api_sec = serde_json::to_value(&api_sec_enc).map_err(|e| {
StoreError::SerdeError("Failed to serialize api_security".to_string(), e)
})?;

Ok(diesel_models::configs::ProviderConfigNew {
id: Uuid::now_v7(),
tenant_id: domain.tenant_id,
invoicing_provider: domain.invoicing_provider.clone().into(),
enabled: domain.enabled,
webhook_security: wh_sec,
api_security: api_sec,
})
}
}
1 change: 1 addition & 0 deletions modules/meteroid/crates/meteroid-store/src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod tenants;

pub mod api_tokens;
pub mod billable_metrics;
pub mod configs;
pub mod enums;
pub mod misc;
pub mod product_families;
Expand Down
17 changes: 16 additions & 1 deletion modules/meteroid/crates/meteroid-store/src/domain/tenants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,26 @@ pub struct Tenant {
#[derive(Clone, Debug, o2o)]
#[owned_into(DieselTenantNew)]
#[ghosts(id: {uuid::Uuid::now_v7()})]
pub struct TenantNew {
pub struct OrgTenantNew {
pub name: String,
pub slug: String,
pub organization_id: Uuid,
pub currency: String,
#[into(~.map(|x| x.into()))]
pub environment: Option<TenantEnvironmentEnum>,
}

#[derive(Clone, Debug)]
pub struct UserTenantNew {
pub name: String,
pub slug: String,
pub user_id: Uuid,
pub currency: String,
pub environment: Option<TenantEnvironmentEnum>,
}

#[derive(Clone, Debug)]
pub enum TenantNew {
ForOrg(OrgTenantNew),
ForUser(UserTenantNew),
}
Loading

0 comments on commit b4ad915

Please sign in to comment.