Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 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 @@ -132,6 +132,7 @@ prost-build = "0.14.1"
prost-types = "0.14.1"
quick_cache = "0.6.2"
quote = "1.0.35"
lazy-regex = "3"
rand = "0.10.0"
rand_chacha = "0.10.0"
rand_distr = "0.6.0"
Expand Down
1 change: 1 addition & 0 deletions modules/meteroid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ meteroid-store.workspace = true
meteroid-invoicing.workspace = true
meteroid-mailer.workspace = true
rand.workspace = true
lazy-regex.workspace = true
rusty-money.workspace = true
meteroid-seeder = { workspace = true }
hubspot-client = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions modules/meteroid/crates/diesel-models/src/entitlements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct FeatureRow {
pub tenant_id: TenantId,
pub product_id: Option<ProductId>,
pub name: String,
pub code: String,
pub description: Option<String>,
pub feature_type: FeatureTypeEnum,
pub status: FeatureStatusEnum,
Expand All @@ -49,6 +50,7 @@ pub struct FeatureRowNew {
pub tenant_id: TenantId,
pub product_id: Option<ProductId>,
pub name: String,
pub code: String,
pub description: Option<String>,
pub feature_type: FeatureTypeEnum,
pub status: FeatureStatusEnum,
Expand All @@ -59,6 +61,7 @@ pub struct FeatureRowNew {
#[diesel(table_name = crate::schema::feature)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct FeatureRowPatch {
// `code` intentionally omitted — it is an immutable addressing key.
pub name: Option<String>,
pub description: Option<Option<String>>,
pub product_id: Option<Option<ProductId>>,
Expand Down
38 changes: 37 additions & 1 deletion modules/meteroid/crates/diesel-models/src/query/entitlements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use crate::errors::IntoDbResult;
use crate::extend::pagination::{Paginate, PaginatedVec, PaginationRequest};
use crate::{DbResult, PgConn};
use common_domain::ids::{
BaseId, EntitlementEntityId, EntitlementId, FeatureId, PlanVersionId, ProductId, TenantId,
AliasOr, BaseId, EntitlementEntityId, EntitlementId, FeatureId, PlanVersionId, ProductId,
TenantId,
};
use diesel::{
BoolExpressionMethods, ExpressionMethods, Insertable, IntoSql, NullableExpressionMethods,
Expand Down Expand Up @@ -63,6 +64,41 @@ impl FeatureRow {
})
}

pub async fn find_by_id_or_code(
conn: &mut PgConn,
id_or_code: AliasOr<FeatureId>,
param_tenant_id: TenantId,
) -> DbResult<FeatureWithProductRow> {
use crate::schema::feature::dsl as f_dsl;
use crate::schema::product;
use diesel_async::RunQueryDsl;

let mut query = f_dsl::feature
.left_join(product::table)
.filter(f_dsl::tenant_id.eq(param_tenant_id))
.select((
FeatureRow::as_select(),
(product::id, product::name).nullable(),
))
.into_boxed();

match id_or_code {
AliasOr::Id(id) => query = query.filter(f_dsl::id.eq(id)),
AliasOr::Alias(code) => query = query.filter(f_dsl::code.eq(code)),
}
log::debug!("{}", debug_query::<diesel::pg::Pg, _>(&query));

let (feature, product_opt): (FeatureRow, Option<(ProductId, String)>) = query
.first(conn)
.await
.attach("Error while finding feature by id or code")
.into_db_result()?;
Ok(FeatureWithProductRow {
feature,
product: product_opt.map(|(id, name)| FeatureProductMeta { id, name }),
})
}

pub async fn list(
conn: &mut PgConn,
param_tenant_id: TenantId,
Expand Down
1 change: 1 addition & 0 deletions modules/meteroid/crates/diesel-models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ diesel::table! {
tenant_id -> Uuid,
product_id -> Nullable<Uuid>,
name -> Text,
code -> Text,
description -> Nullable<Text>,
feature_type -> FeatureTypeEnum,
status -> FeatureStatusEnum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub struct Feature {
/// Product this feature belongs to. `None` for tenant-global features.
pub product: Option<FeatureProductRef>,
pub name: String,
pub code: String,
pub description: Option<String>,
pub feature_type: FeatureType,
pub status: FeatureStatusEnum,
Expand Down Expand Up @@ -112,6 +113,7 @@ impl TryFrom<FeatureWithProductRow> for Feature {
name: p.name,
}),
name: feature.name,
code: feature.code,
description: feature.description,
feature_type,
status: feature.status.into(),
Expand All @@ -127,6 +129,7 @@ pub struct FeatureNew {
pub tenant_id: TenantId,
pub product_id: Option<ProductId>,
pub name: String,
pub code: String,
pub description: Option<String>,
pub feature_type: FeatureType,
pub entitlement: Option<EntitlementValue>,
Expand All @@ -138,6 +141,7 @@ impl From<FeatureNew> for FeatureRowNew {
tenant_id,
product_id,
name,
code,
description,
feature_type,
entitlement: _,
Expand All @@ -151,6 +155,7 @@ impl From<FeatureNew> for FeatureRowNew {
tenant_id,
product_id,
name,
code,
description,
feature_type: feature_type_enum,
status: DbFeatureStatusEnum::Active,
Expand Down Expand Up @@ -299,6 +304,7 @@ pub struct FeatureProductRef {
pub struct FeatureRef {
pub id: FeatureId,
pub name: String,
pub code: String,
pub product: Option<FeatureProductRef>,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::store::PgConn;
use crate::{Store, StoreResult};
use chrono::{DateTime, Datelike, Days, Duration, Months, NaiveDate, NaiveDateTime, NaiveTime};
use common_domain::ids::{
AddOnId, BaseId, CustomerId, EntitlementEntityId, EntitlementId, FeatureId, PlanId,
AddOnId, AliasOr, BaseId, CustomerId, EntitlementEntityId, EntitlementId, FeatureId, PlanId,
PlanVersionId, ProductId, QuoteId, SubscriptionId, TenantId,
};
use diesel_models::add_ons::AddOnRow;
Expand Down Expand Up @@ -60,6 +60,12 @@ pub trait EntitlementsInterface {

async fn get_feature(&self, id: FeatureId, tenant_id: TenantId) -> StoreResult<Feature>;

async fn get_feature_by_id_or_code(
&self,
id_or_code: AliasOr<FeatureId>,
tenant_id: TenantId,
) -> StoreResult<Feature>;

async fn list_features(
&self,
tenant_id: TenantId,
Expand Down Expand Up @@ -252,6 +258,37 @@ impl EntitlementsInterface for Store {
Ok(feature)
}

async fn get_feature_by_id_or_code(
&self,
id_or_code: AliasOr<FeatureId>,
tenant_id: TenantId,
) -> StoreResult<Feature> {
let mut conn = self.get_conn().await?;

let row = FeatureRow::find_by_id_or_code(&mut conn, id_or_code, tenant_id)
.await
.map_err(Into::<Report<StoreError>>::into)?;

let feature_id = row.feature.id;
let mut feature: Feature = row.try_into()?;

let entitlement_rows = EntitlementRow::list_by_entity(
&mut conn,
tenant_id,
EntitlementEntityId::Feature(feature_id),
)
.await
.map_err(Into::<Report<StoreError>>::into)?;

feature.entitlement = entitlement_rows
.into_iter()
.next()
.map(TryInto::try_into)
.transpose()?;

Ok(feature)
}

async fn list_features(
&self,
tenant_id: TenantId,
Expand Down Expand Up @@ -1511,6 +1548,7 @@ fn resolve(
feature: FeatureRef {
id: feature.id,
name: feature.name.clone(),
code: feature.code.clone(),
product: feature.product.clone(),
},
value: resolved_value,
Expand Down Expand Up @@ -1733,6 +1771,7 @@ mod tests {
tenant_id: TenantId::new(),
product: None,
name: id.to_string(),
code: id.to_string(),
description: None,
feature_type,
status: FeatureStatusEnum::Active,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ mod tests {
feature: FeatureRef {
id: FeatureId::new(),
name: "test".to_string(),
code: "test".to_string(),
product: None,
},
origin: ResolvedOrigin {
Expand Down Expand Up @@ -506,6 +507,7 @@ mod tests {
let feature = FeatureRef {
id: FeatureId::new(),
name: "deleted-metric".to_string(),
code: "deleted-metric".to_string(),
product: None,
};
let origin = ResolvedOrigin {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE feature
DROP CONSTRAINT IF EXISTS feature_tenant_code_key;

ALTER TABLE feature
DROP COLUMN IF EXISTS code;

ALTER TABLE feature
ADD CONSTRAINT feature_tenant_id_name_key UNIQUE (tenant_id, name);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER TABLE feature ADD COLUMN code TEXT;

UPDATE feature SET code = id::text WHERE code IS NULL;

ALTER TABLE feature ALTER COLUMN code SET NOT NULL;

ALTER TABLE feature ADD CONSTRAINT feature_tenant_code_key UNIQUE (tenant_id, code);

ALTER TABLE feature DROP CONSTRAINT IF EXISTS feature_tenant_id_name_key;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ message CreateFeatureRequest {
// Product the feature belongs to. Unset ⇒ tenant-global feature.
optional string product_id = 4;
optional EntitlementValue entitlement = 5;
string code = 6;
}

message CreateFeatureResponse {
Expand Down
2 changes: 2 additions & 0 deletions modules/meteroid/proto/api/entitlements/v1/models.proto
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ message FeatureRef {
string id = 1;
string name = 2;
optional ProductRef product = 3;
string code = 4;
}

message Feature {
Expand All @@ -79,6 +80,7 @@ message Feature {
// Product the feature belongs to. Unset ⇒ tenant-global feature.
optional ProductRef product = 6;
string created_at = 7;
string code = 8;
}

message EntitlementEntity {
Expand Down
3 changes: 3 additions & 0 deletions modules/meteroid/src/api/entitlements/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ pub fn feature_to_proto(f: Feature) -> proto::Feature {
status: feature_status_to_proto(f.status),
product,
created_at: f.created_at.as_proto(),
code: f.code,
}
}

Expand Down Expand Up @@ -280,6 +281,7 @@ pub fn effective_entitlement_to_proto(r: EffectiveEntitlement) -> proto::Effecti
feature: Some(proto::FeatureRef {
id: r.feature.id.as_proto(),
name: r.feature.name,
code: r.feature.code,
product: r.feature.product.map(|p| proto::ProductRef {
id: p.id.as_proto(),
name: p.name,
Expand Down Expand Up @@ -314,6 +316,7 @@ pub fn resolved_entitlement_to_proto(r: ResolvedEntitlement) -> proto::ResolvedE
feature: Some(proto::FeatureRef {
id: r.feature.id.as_proto(),
name: r.feature.name,
code: r.feature.code,
product: r.feature.product.map(|p| proto::ProductRef {
id: p.id.as_proto(),
name: p.name,
Expand Down
Loading
Loading