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
53 changes: 28 additions & 25 deletions internal/store/postgres/billing_plan_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,22 @@ type PlanProductRow struct {
PlanUpdatedAt time.Time `db:"plan_updated_at"`
PlanDeletedAt *time.Time `db:"plan_deleted_at"`

ProductID string `db:"product_id"`
ProductProviderID string `db:"product_provider_id"`
// product columns are pointers because a left join leaves them null for a
// plan that has no products
ProductID *string `db:"product_id"`
ProductProviderID *string `db:"product_provider_id"`
ProductPlanIDs pq.StringArray `db:"product_plan_ids"`
ProductName string `db:"product_name"`
ProductName *string `db:"product_name"`
ProductTitle *string `db:"product_title"`
ProductDescription *string `db:"product_description"`

ProductBehavior string `db:"product_behavior"`
ProductBehavior *string `db:"product_behavior"`
ProductConfig BehaviorConfig `db:"product_config"`
ProductState string `db:"product_state"`
ProductState *string `db:"product_state"`
ProductMetadata types.NullJSONText `db:"product_metadata"`

ProductCreatedAt time.Time `db:"product_created_at"`
ProductUpdatedAt time.Time `db:"product_updated_at"`
ProductCreatedAt *time.Time `db:"product_created_at"`
ProductUpdatedAt *time.Time `db:"product_updated_at"`
ProductDeletedAt *time.Time `db:"product_deleted_at"`
}

Expand All @@ -93,18 +95,18 @@ func (pr PlanProductRow) getPlan() (plan.Plan, error) {

func (pr PlanProductRow) getProduct() (product.Product, error) {
prod := Product{
ID: pr.ProductID,
ProviderID: pr.ProductProviderID,
ID: ptrToString(pr.ProductID),
ProviderID: ptrToString(pr.ProductProviderID),
PlanIDs: pr.ProductPlanIDs,
Name: pr.ProductName,
Name: ptrToString(pr.ProductName),
Title: pr.ProductTitle,
Description: pr.ProductDescription,
Behavior: pr.ProductBehavior,
Behavior: ptrToString(pr.ProductBehavior),
Config: pr.ProductConfig,
State: pr.ProductState,
State: ptrToString(pr.ProductState),
Metadata: pr.ProductMetadata,
CreatedAt: pr.ProductCreatedAt,
UpdatedAt: pr.ProductUpdatedAt,
CreatedAt: ptrToTime(pr.ProductCreatedAt),
UpdatedAt: ptrToTime(pr.ProductUpdatedAt),
DeletedAt: pr.ProductDeletedAt,
}

Expand Down Expand Up @@ -353,8 +355,9 @@ func (r BillingPlanRepository) List(ctx context.Context, filter plan.Filter) ([]
func (r BillingPlanRepository) ListWithProducts(ctx context.Context, filter plan.Filter) ([]plan.Plan, error) {
pln := goqu.T(TABLE_BILLING_PLANS).As("plan")
prd := goqu.T(TABLE_BILLING_PRODUCTS).As("product")
// a left join keeps plans that have no products; an inner join would drop them
stmt := dialect.From(pln).
Join(
LeftJoin(
prd,
goqu.On(
goqu.L("CAST(plan.id AS text)").Eq(goqu.L("ANY(product.plan_ids)")),
Expand All @@ -371,7 +374,7 @@ func (r BillingPlanRepository) ListWithProducts(ctx context.Context, filter plan
pln.Col("metadata").As("plan_metadata"),
pln.Col("created_at").As("plan_created_at"),
pln.Col("updated_at").As("plan_updated_at"),
prd.Col("deleted_at").As("plan_deleted_at"),
pln.Col("deleted_at").As("plan_deleted_at"),
prd.Col("id").As("product_id"),
prd.Col("provider_id").As("product_provider_id"),
prd.Col("name").As("product_name"),
Expand Down Expand Up @@ -437,19 +440,19 @@ func (r BillingPlanRepository) ListWithProducts(ctx context.Context, filter plan
if err != nil {
return nil, err
}

prod, err := row.getProduct()
if err != nil {
return nil, err
if existing, ok := planMap[pln.ID]; ok {
pln = existing
}

planInMap, exists := planMap[pln.ID]
if exists {
planInMap.Products = append(planInMap.Products, prod)
} else {
// a left join gives a null product id for a plan that has no products
if row.ProductID != nil {
prod, err := row.getProduct()
if err != nil {
return nil, err
}
pln.Products = append(pln.Products, prod)
planMap[pln.ID] = pln
}
planMap[pln.ID] = pln
}

plans := []plan.Plan{}
Expand Down
9 changes: 9 additions & 0 deletions internal/store/postgres/null_converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package postgres
import (
"database/sql"
"encoding/json"
"time"

"github.com/jmoiron/sqlx/types"
"github.com/raystack/frontier/pkg/metadata"
Expand Down Expand Up @@ -74,6 +75,14 @@ func ptrToString(ptr *string) string {
return ""
}

// ptrToTime safely converts a time pointer to a time.Time, returning zero time if nil
func ptrToTime(ptr *time.Time) time.Time {
if ptr != nil {
return *ptr
}
return time.Time{}
}

// unmarshalNullJSONText unmarshals NullJSONText to map[string]any
func unmarshalNullJSONText(metadata types.NullJSONText) (map[string]any, error) {
if !metadata.Valid {
Expand Down
Loading