Skip to content

Commit

Permalink
feat: Add customers rest api (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
azhur authored Dec 25, 2024
1 parent 19fcf85 commit 038957d
Show file tree
Hide file tree
Showing 38 changed files with 1,694 additions and 106 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci-rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ jobs:
run: |
cargo run -p meteroid --bin openapi-generate
if [[ -n "$(git status --porcelain spec/api/v1/openapi.json)" ]]; then
echo "openapi.json is not up to date. Please run `cargo run -p meteroid --bin openapi-generate` and commit changes."
echo "openapi.json is not up to date. Please run 'cargo run -p meteroid --bin openapi-generate' and commit changes."
git --no-pager diff spec/api/v1/openapi.json
exit 1
fi
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ typetag = "0.2.13"
unic-langid = "0.9"
url = "2.4.1"
uuid = "1.4.1"
utoipa = { version = "5.2.0", features = ["axum_extras", "uuid", "debug", "chrono"] }
utoipa-axum = { version = "0.1.2" }
utoipa = { version = "5.3.0", features = ["axum_extras", "uuid", "debug", "chrono"] }
utoipa-axum = { version = "0.1.3" }
utoipa-swagger-ui = { version = "8.0.3", features = ["axum"] }
utoipa-redoc = { version = "5.0.0", features = ["axum"] }
utoipa-rapidoc = { version = "5.0.0", features = ["axum"] }
Expand Down
1 change: 1 addition & 0 deletions modules/meteroid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ utoipa-rapidoc.workspace = true
utoipa-scalar.workspace = true

svix = { workspace = true, features = ["http2", "rustls-tls"] }
strum = { workspace = true, features = ["derive"] }

rdkafka = { workspace = true }
kafka = { workspace = true }
Expand Down
28 changes: 28 additions & 0 deletions modules/meteroid/crates/diesel-models/src/customers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ pub struct CustomerRow {
pub local_id: String,
}

#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
#[diesel(table_name = crate::schema::customer)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct CustomerForDisplayRow {
pub id: Uuid,
pub name: String,
pub created_at: NaiveDateTime,
pub created_by: Uuid,
pub updated_at: Option<NaiveDateTime>,
pub updated_by: Option<Uuid>,
pub archived_at: Option<NaiveDateTime>,
pub tenant_id: Uuid,
pub billing_config: serde_json::Value,
pub alias: Option<String>,
pub email: Option<String>,
pub invoicing_email: Option<String>,
pub phone: Option<String>,
pub balance_value_cents: i32,
pub currency: String,
pub billing_address: Option<serde_json::Value>,
pub shipping_address: Option<serde_json::Value>,
pub invoicing_entity_id: Uuid,
pub local_id: String,
#[diesel(select_expression = crate::schema::invoicing_entity::local_id)]
#[diesel(select_expression_type = crate::schema::invoicing_entity::local_id)]
pub invoicing_entity_local_id: String,
}

#[derive(Clone, Debug, Queryable, Selectable)]
#[diesel(table_name = crate::schema::customer)]
#[diesel(check_for_backend(diesel::pg::Pg))]
Expand Down
98 changes: 92 additions & 6 deletions modules/meteroid/crates/diesel-models/src/query/customers.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::customers::{CustomerBriefRow, CustomerRow, CustomerRowNew, CustomerRowPatch};
use crate::customers::{
CustomerBriefRow, CustomerForDisplayRow, CustomerRow, CustomerRowNew, CustomerRowPatch,
};
use crate::errors::IntoDbResult;
use crate::extend::order::OrderByRequest;
use crate::extend::pagination::{Paginate, PaginatedVec, PaginationRequest};
use crate::query::IdentityDb;
use crate::{DbResult, PgConn};
use diesel::{
debug_query, BoolExpressionMethods, ExpressionMethods, OptionalExtension,
debug_query, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, OptionalExtension,
PgTextExpressionMethods, QueryDsl, SelectableHelper,
};
use error_stack::ResultExt;
Expand All @@ -31,15 +34,23 @@ impl CustomerRowNew {
impl CustomerRow {
pub async fn find_by_id(
conn: &mut PgConn,
customer_id: Uuid,
customer_id: IdentityDb,
tenant_id_param: Uuid,
) -> DbResult<CustomerRow> {
use crate::schema::customer::dsl::*;
use diesel_async::RunQueryDsl;

let query = customer
.filter(id.eq(customer_id))
.filter(tenant_id.eq(tenant_id_param));
let mut query = customer.filter(tenant_id.eq(tenant_id_param)).into_boxed();

match customer_id {
IdentityDb::UUID(id_param) => {
query = query.filter(id.eq(id_param));
}
IdentityDb::LOCAL(local_id_param) => {
query = query.filter(local_id.eq(local_id_param));
}
}

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

query
Expand Down Expand Up @@ -230,3 +241,78 @@ impl CustomerRowPatch {
.into_db_result()
}
}

impl CustomerForDisplayRow {
pub async fn find_by_local_id_or_alias(
conn: &mut PgConn,
tenant_id: Uuid,
local_id_or_alias: String,
) -> DbResult<CustomerForDisplayRow> {
use crate::schema::customer::dsl as c_dsl;
use crate::schema::invoicing_entity::dsl as ie_dsl;
use diesel_async::RunQueryDsl;

let query = c_dsl::customer
.filter(c_dsl::tenant_id.eq(tenant_id))
.filter(
c_dsl::local_id
.eq(local_id_or_alias.as_str())
.or(c_dsl::alias.eq(local_id_or_alias.as_str())),
)
.inner_join(ie_dsl::invoicing_entity.on(c_dsl::invoicing_entity_id.eq(ie_dsl::id)))
.select(CustomerForDisplayRow::as_select());
log::debug!("{}", debug_query::<diesel::pg::Pg, _>(&query).to_string());

query
.first(conn)
.await
.attach_printable("Error while finding customer by local_id or alias")
.into_db_result()
}

pub async fn list(
conn: &mut PgConn,
param_tenant_id: Uuid,
pagination: PaginationRequest,
order_by: OrderByRequest,
param_query: Option<String>,
) -> DbResult<PaginatedVec<CustomerForDisplayRow>> {
use crate::schema::customer::dsl as c_dsl;
use crate::schema::invoicing_entity::dsl as ie_dsl;

let mut query = c_dsl::customer
.filter(c_dsl::tenant_id.eq(param_tenant_id))
.inner_join(ie_dsl::invoicing_entity.on(c_dsl::invoicing_entity_id.eq(ie_dsl::id)))
.select(CustomerForDisplayRow::as_select())
.into_boxed();

if let Some(param_query) = param_query {
query = query.filter(
c_dsl::name
.ilike(format!("%{}%", param_query))
.or(c_dsl::alias.ilike(format!("%{}%", param_query))),
);
}

match order_by {
OrderByRequest::IdAsc => query = query.order(c_dsl::id.asc()),
OrderByRequest::IdDesc => query = query.order(c_dsl::id.desc()),
OrderByRequest::DateAsc => query = query.order(c_dsl::created_at.asc()),
OrderByRequest::DateDesc => query = query.order(c_dsl::created_at.desc()),
_ => query = query.order(c_dsl::id.asc()),
}

let paginated_query = query.paginate(pagination);

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

paginated_query
.load_and_count_pages(conn)
.await
.attach_printable("Error while fetching customers")
.into_db_result()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::errors::IntoDbResult;
use crate::invoicing_entities::{InvoicingEntityRow, InvoicingEntityRowPatch};
use crate::query::IdentityDb;

use crate::{DbResult, PgConn};

Expand Down Expand Up @@ -129,16 +130,25 @@ impl InvoicingEntityRow {

pub async fn get_invoicing_entity_by_id_and_tenant(
conn: &mut PgConn,
id: &uuid::Uuid,
id: &IdentityDb,
tenant_id: &uuid::Uuid,
) -> DbResult<InvoicingEntityRow> {
use crate::schema::invoicing_entity::dsl;
use diesel_async::RunQueryDsl;

let query = dsl::invoicing_entity
.filter(dsl::id.eq(id))
let mut query = dsl::invoicing_entity
.filter(dsl::tenant_id.eq(tenant_id))
.select(InvoicingEntityRow::as_select());
.select(InvoicingEntityRow::as_select())
.into_boxed();

match id {
IdentityDb::UUID(id_param) => {
query = query.filter(dsl::id.eq(id_param));
}
IdentityDb::LOCAL(local_id_param) => {
query = query.filter(dsl::local_id.eq(local_id_param));
}
}

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

Expand Down
71 changes: 65 additions & 6 deletions modules/meteroid/crates/meteroid-store/src/domain/customers.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::domain::Identity;
use crate::errors::StoreError;
use crate::utils::local_id::{IdType, LocalId};
use chrono::NaiveDateTime;
use diesel_models::customers::CustomerRow;
use diesel_models::customers::{CustomerBriefRow, CustomerRowNew, CustomerRowPatch};
use diesel_models::customers::{CustomerForDisplayRow, CustomerRow};
use error_stack::Report;
use o2o::o2o;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;

use crate::errors::StoreError;
use crate::utils::local_id::{IdType, LocalId};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Customer {
pub id: Uuid,
Expand Down Expand Up @@ -113,7 +113,7 @@ pub struct CustomerNew {
pub shipping_address: Option<ShippingAddress>,
//
pub created_by: Uuid,
pub invoicing_entity_id: Option<Uuid>,
pub invoicing_entity_id: Option<Identity>,
// for seeding
pub force_created_date: Option<chrono::NaiveDateTime>,
}
Expand Down Expand Up @@ -260,7 +260,13 @@ pub enum BillingConfig {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Stripe {
pub customer_id: String,
pub collection_method: i32, // todo fix: models.proto : CollectionMethod
pub collection_method: StripeCollectionMethod,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum StripeCollectionMethod {
ChargeAutomatically,
SendInvoice,
}

impl TryFrom<serde_json::Value> for BillingConfig {
Expand Down Expand Up @@ -307,3 +313,56 @@ pub struct CustomerBuyCredits {
pub cents: i32,
pub notes: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomerForDisplay {
pub id: Uuid,
pub local_id: String,
pub name: String,
pub created_at: NaiveDateTime,
pub created_by: Uuid,
pub updated_at: Option<NaiveDateTime>,
pub updated_by: Option<Uuid>,
pub archived_at: Option<NaiveDateTime>,
pub tenant_id: Uuid,
pub invoicing_entity_id: Uuid,
pub invoicing_entity_local_id: String,
pub billing_config: BillingConfig,
pub alias: Option<String>,
pub email: Option<String>,
pub invoicing_email: Option<String>,
pub phone: Option<String>,
pub balance_value_cents: i32,
pub currency: String,
pub billing_address: Option<Address>,
pub shipping_address: Option<ShippingAddress>,
}

impl TryFrom<CustomerForDisplayRow> for CustomerForDisplay {
type Error = Report<StoreError>;

fn try_from(value: CustomerForDisplayRow) -> Result<Self, Self::Error> {
Ok(CustomerForDisplay {
id: value.id,
local_id: value.local_id,
name: value.name,
created_at: value.created_at,
created_by: value.created_by,
updated_at: value.updated_at,
updated_by: value.updated_by,
archived_at: value.archived_at,
tenant_id: value.tenant_id,
billing_config: value.billing_config.try_into()?,
alias: value.alias,
email: value.email,
invoicing_email: value.invoicing_email,
phone: value.phone,
balance_value_cents: value.balance_value_cents,
currency: value.currency,
billing_address: value.billing_address.map(|v| v.try_into()).transpose()?,
shipping_address: value.shipping_address.map(|v| v.try_into()).transpose()?,
invoicing_entity_id: value.invoicing_entity_id,
invoicing_entity_local_id: value.invoicing_entity_local_id,
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::StoreResult;
use diesel_models::customer_balance_txs::CustomerBalanceTxRowNew;
use diesel_models::customers::CustomerRow;
use diesel_models::errors::DatabaseError;
use diesel_models::query::IdentityDb;
use error_stack::Report;
use uuid::Uuid;

Expand Down Expand Up @@ -36,9 +37,10 @@ impl CustomerBalance {
_ => Into::<Report<StoreError>>::into(err),
})?;

let customer_row_updated = CustomerRow::find_by_id(conn, customer_id, tenant_id)
.await
.map_err(Into::<Report<StoreError>>::into)?;
let customer_row_updated =
CustomerRow::find_by_id(conn, IdentityDb::UUID(customer_id), tenant_id)
.await
.map_err(Into::<Report<StoreError>>::into)?;

let tx = CustomerBalanceTxRowNew {
id: Uuid::now_v7(),
Expand Down
Loading

0 comments on commit 038957d

Please sign in to comment.