From 5d84e2c8247e24e2c29df7b942a3b54672beb9c0 Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Tue, 29 Oct 2024 18:15:07 -0700 Subject: [PATCH] CORE-2016: added duplicate checks for plan quota defaults and plan rates --- internal/controllers/plans.go | 56 +++++++++++++++++++++++++++++----- internal/db/plan.go | 41 ++++++++++++++++++++++--- internal/httpmodel/new_plan.go | 53 +++++++++++++++++++++++++++++++- 3 files changed, 138 insertions(+), 12 deletions(-) diff --git a/internal/controllers/plans.go b/internal/controllers/plans.go index c5ad118..9c0d2f8 100644 --- a/internal/controllers/plans.go +++ b/internal/controllers/plans.go @@ -130,6 +130,15 @@ func (s Server) AddPlan(ctx echo.Context) error { // Begin a transaction. return s.GORMDB.Transaction(func(tx *gorm.DB) error { dbPlan := plan.ToDBModel() + + // Make sure that a plan with the same name doesn't already exist. + planNameExists, err := db.CheckPlanNameExistence(context, tx, plan.Name) + if err != nil { + return model.Error(ctx, err.Error(), http.StatusInternalServerError) + } else if planNameExists { + return model.Error(ctx, fmt.Sprintf("a plan named `%s` already exists", plan.Name), http.StatusBadRequest) + } + // Look up each resource type and update it in the struct. for i, planQuotaDefault := range dbPlan.PlanQuotaDefaults { resourceType, err := db.GetResourceTypeByName(context, tx, planQuotaDefault.ResourceType.Name) @@ -147,7 +156,7 @@ func (s Server) AddPlan(ctx echo.Context) error { log.Debugf("translated plan: %+v", dbPlan) // Add the plan to the database. - err := tx.WithContext(context).Create(&dbPlan).Error + err = tx.WithContext(context).Create(&dbPlan).Error if err != nil { return model.Error(ctx, err.Error(), http.StatusInternalServerError) } @@ -310,16 +319,19 @@ func (s Server) AddPlanQuotaDefaults(ctx echo.Context) error { if err = ctx.Bind(&planQuotaDefaultList); err != nil { return model.Error(ctx, err.Error(), http.StatusBadRequest) } + if err = planQuotaDefaultList.Validate(); err != nil { + return model.Error(ctx, err.Error(), http.StatusBadRequest) + } // Begin a transaction. return s.GORMDB.Transaction(func(tx *gorm.DB) error { context := ctx.Request().Context() // Verify that the plan exists. - exists, err := db.CheckPlanExistence(context, tx, planID) + plan, err := db.GetPlanByID(context, tx, planID) if err != nil { return model.Error(ctx, err.Error(), http.StatusInternalServerError) - } else if !exists { + } else if plan == nil { msg := fmt.Sprintf("plan ID %s not found", planID) return model.Error(ctx, msg, http.StatusNotFound) } @@ -345,6 +357,24 @@ func (s Server) AddPlanQuotaDefaults(ctx echo.Context) error { planQuotaDefaults[i].ResourceType = *rt } + // Compare the plan quota defaults to existing quota defaults to check for duplicates. + existingPlanQuotaDefaults := make(map[httpmodel.PlanQuotaDefaultKey]bool) + for _, pqd := range plan.PlanQuotaDefaults { + key := httpmodel.KeyFromPlanQuotaDefault(pqd) + existingPlanQuotaDefaults[key] = true + } + for _, pqd := range planQuotaDefaults { + key := httpmodel.KeyFromPlanQuotaDefault(pqd) + if existingPlanQuotaDefaults[key] { + msg := fmt.Sprintf( + "plan quota default for resource type %s with effective date %s already exists", + pqd.ResourceType.Name, + pqd.EffectiveDate, + ) + return model.Error(ctx, msg, http.StatusBadRequest) + } + } + // Save the list of plan quota defaults. err = db.SavePlanQuotaDefaults(context, tx, planQuotaDefaults) if err != nil { @@ -352,7 +382,7 @@ func (s Server) AddPlanQuotaDefaults(ctx echo.Context) error { } // Look up the plan with the new plan quota defaults included and return it in the response. - plan, err := db.GetPlanByID(context, tx, planID) + plan, err = db.GetPlanByID(context, tx, planID) if err != nil { return model.Error(ctx, err.Error(), http.StatusInternalServerError) } else if plan == nil { @@ -407,10 +437,10 @@ func (s Server) AddPlanRates(ctx echo.Context) error { context := ctx.Request().Context() // Verify that the plan existws. - exists, err := db.CheckPlanExistence(context, tx, planID) + plan, err := db.GetPlanByID(context, tx, planID) if err != nil { return model.Error(ctx, err.Error(), http.StatusInternalServerError) - } else if !exists { + } else if plan == nil { msg := fmt.Sprintf("plan ID %s not found", planID) return model.Error(ctx, msg, http.StatusNotFound) } @@ -418,6 +448,18 @@ func (s Server) AddPlanRates(ctx echo.Context) error { // Convert the list of plan rates to the corresponding DB model. planRates := planRateList.ToDBModel() + // Verify that none of the incoming plan rates duplicate existing plan rates. + existingPlanRates := make(map[int64]bool) + for _, pr := range plan.PlanRates { + existingPlanRates[pr.EffectiveDate.UnixMilli()] = true + } + for _, pr := range planRates { + if existingPlanRates[pr.EffectiveDate.UnixMilli()] { + msg := fmt.Sprintf("plan rate with effective date %s already exists", pr.EffectiveDate) + return model.Error(ctx, msg, http.StatusBadRequest) + } + } + // Plug the plan ID into each of the plan rates. for i := range planRates { planRates[i].PlanID = &planID @@ -430,7 +472,7 @@ func (s Server) AddPlanRates(ctx echo.Context) error { } // Look up the plan with the new plan quota defaults included and return it in the response. - plan, err := db.GetPlanByID(context, tx, planID) + plan, err = db.GetPlanByID(context, tx, planID) if err != nil { return model.Error(ctx, err.Error(), http.StatusInternalServerError) } else if plan == nil { diff --git a/internal/db/plan.go b/internal/db/plan.go index 694989a..f715820 100644 --- a/internal/db/plan.go +++ b/internal/db/plan.go @@ -21,7 +21,11 @@ func GetPlan(ctx context.Context, db *gorm.DB, planName string) (*model.Plan, er err = db. WithContext(ctx). Where("name=?", planName). - Preload("PlanQuotaDefaults"). + Preload("PlanQuotaDefaults", func(db *gorm.DB) *gorm.DB { + return db. + Joins("INNER JOIN resource_types ON plan_quota_defaults.resource_type_id = resource_types.id"). + Order("plan_quota_defaults.effective_date asc, resource_types.name asc") + }). Preload("PlanQuotaDefaults.ResourceType"). Preload("PlanRates", func(db *gorm.DB) *gorm.DB { return db.Order("effective_date asc") @@ -36,7 +40,28 @@ func GetPlan(ctx context.Context, db *gorm.DB, planName string) (*model.Plan, er return &plan, nil } -// PlanExists determines whether or not a subscription plan with the given identifier exists. +// CheckPlanNameExistence determines whether or not a subscription plan with a given name exists. +func CheckPlanNameExistence(ctx context.Context, db *gorm.DB, planName string) (bool, error) { + wrapMsg := fmt.Sprintf("unable to look up plan Name `%s`", planName) + var err error + + // Query the database. + var exists bool + var plan = model.Plan{} + err = db.Model(plan). + Select("count(*) > 0"). + Where("name = ?", planName). + Find(&exists). + Error + + // Return the result. + if err != nil { + return false, errors.Wrap(err, wrapMsg) + } + return exists, nil +} + +// CheckPlanExistence determines whether or not a subscription plan with the given identifier exists. func CheckPlanExistence(ctx context.Context, db *gorm.DB, planID string) (bool, error) { wrapMsg := fmt.Sprintf("unable to look up plan ID '%s'", planID) var err error @@ -64,7 +89,11 @@ func GetPlanByID(ctx context.Context, db *gorm.DB, planID string) (*model.Plan, plan := model.Plan{ID: &planID} err = db. WithContext(ctx). - Preload("PlanQuotaDefaults"). + Preload("PlanQuotaDefaults", func(db *gorm.DB) *gorm.DB { + return db. + Joins("INNER JOIN resource_types ON plan_quota_defaults.resource_type_id = resource_types.id"). + Order("plan_quota_defaults.effective_date asc, resource_types.name asc") + }). Preload("PlanQuotaDefaults.ResourceType"). Preload("PlanRates", func(db *gorm.DB) *gorm.DB { return db.Order("effective_date asc") @@ -131,7 +160,11 @@ func ListPlans(ctx context.Context, db *gorm.DB) ([]*model.Plan, error) { var plans []*model.Plan err = db. WithContext(ctx). - Preload("PlanQuotaDefaults"). + Preload("PlanQuotaDefaults", func(db *gorm.DB) *gorm.DB { + return db. + Joins("INNER JOIN resource_types ON plan_quota_defaults.resource_type_id = resource_types.id"). + Order("plan_quota_defaults.effective_date asc, resource_types.name asc") + }). Preload("PlanQuotaDefaults.ResourceType"). Preload("PlanRates", func(db *gorm.DB) *gorm.DB { return db.Order("effective_date asc") diff --git a/internal/httpmodel/new_plan.go b/internal/httpmodel/new_plan.go index 61679e9..95e83ff 100644 --- a/internal/httpmodel/new_plan.go +++ b/internal/httpmodel/new_plan.go @@ -10,6 +10,28 @@ import ( // Note: the names in the comments may deviate a bit from the actual structure names in order to avoid producing // confusing Swagger docs. +// A plan quota default key. +type PlanQuotaDefaultKey struct { + ResourceTypeName string + EffectiveDate int64 +} + +// KeyFromPlanQuotaDefault generates a plan quota default key from a PlanQuotaDefault object. +func KeyFromPlanQuotaDefault(planQuotaDefault model.PlanQuotaDefault) PlanQuotaDefaultKey { + return PlanQuotaDefaultKey{ + ResourceTypeName: planQuotaDefault.ResourceType.Name, + EffectiveDate: planQuotaDefault.EffectiveDate.UnixMilli(), + } +} + +// KeyFromNewPlanQuotaDefault generates a plan quota defual key from a NewPlanQuotaDefaultObject. +func KeyFromNewPlanQuotaDefault(newPlanQuotaDefault NewPlanQuotaDefault) PlanQuotaDefaultKey { + return PlanQuotaDefaultKey{ + ResourceTypeName: newPlanQuotaDefault.ResourceType.Name, + EffectiveDate: newPlanQuotaDefault.EffectiveDate.UnixMilli(), + } +} + // NewPlan // // swagger:model @@ -52,7 +74,17 @@ func (p NewPlan) Validate() error { } } - // Validate each of the plan rates. + // Verify that the resource type and effective date are unique for all of the plan quota defaults. + uniquePlanQuotaDefaults := make(map[PlanQuotaDefaultKey]bool) + for _, d := range p.PlanQuotaDefaults { + key := KeyFromNewPlanQuotaDefault(d) + if uniquePlanQuotaDefaults[key] { + return fmt.Errorf("multiple plan quota defaults found with the same resource type and effective date") + } + uniquePlanQuotaDefaults[key] = true + } + + // Verify each of the plan rates. for _, pr := range p.PlanRates { err = pr.Validate() if err != nil { @@ -60,6 +92,15 @@ func (p NewPlan) Validate() error { } } + // Verify that the effective date it unique for all of the plan rates. + uniquePlanRates := make(map[int64]bool) + for _, pr := range p.PlanRates { + if uniquePlanRates[pr.EffectiveDate.UnixMilli()] { + return fmt.Errorf("multiple plan rates found with the same effective date") + } + uniquePlanRates[pr.EffectiveDate.UnixMilli()] = true + } + return nil } @@ -108,6 +149,16 @@ func (pqdl NewPlanQuotaDefaultList) Validate() error { } } + // Verify that the resource type and effective date are unique for all of the plan quota defaults. + uniquePlanQuotaDefaults := make(map[PlanQuotaDefaultKey]bool) + for _, d := range pqdl.PlanQuotaDefaults { + key := KeyFromNewPlanQuotaDefault(d) + if uniquePlanQuotaDefaults[key] { + return fmt.Errorf("multiple plan quota defaults found with the same resource type and effective date") + } + uniquePlanQuotaDefaults[key] = true + } + return nil }