diff --git a/.env.sample b/.env.sample new file mode 100755 index 0000000..e4d2b62 --- /dev/null +++ b/.env.sample @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +QMS_DATABASE_URI=postgres://de@localhost/qms?sslmode=disable +QMS_USERNAME_SUFFIX=iplantcollaborative.org +QMS_NATS_CLUSTER=nats://localhost:4222 diff --git a/.envrc b/.envrc new file mode 100755 index 0000000..e84a999 --- /dev/null +++ b/.envrc @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +_PWD=$(pwd) +_APP=`echo $_PWD | grep -Eo -i '([[:alnum:]-]*)$'` +echo "Starting $_APP environment..." +export PATH="$_PWD/bin:$PATH" + +export PRJ_ROOT_DIR=$(realpath "$_PWD") + +[ -f ${PRJ_ROOT_DIR}/.env ] && source ${PRJ_ROOT_DIR}/.env || { echo "Missing ${PRJ_ROOT_DIR}/.env!"; exit 1; } +## Export all VARS in .env +ALL_ENV_PRJ_VARS=($(grep -E '^[[:space:]]*[A-Z_][A-Z0-9_]*[[:space:]]*=' ${PRJ_ROOT_DIR}/.env | cut -f1 -d"=" | tr '\n' ' ')) +for idx in "${!ALL_ENV_PRJ_VARS[@]}"; do + # echo "idx: $idx :: ${ALL_ENV_PRJ_VARS[idx]}=${!ALL_ENV_PRJ_VARS[idx]}" + eval "export ${ALL_ENV_PRJ_VARS[idx]}='${!ALL_ENV_PRJ_VARS[idx]}'" +done + +## GOPRIVATE="gitlab.com/cyverse*,github.com/cyverse*,bitbucket.org/cyverse*" +export GOPRIVATE="gitlab.com/cyverse*" diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index fed97a5..aeab93f 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -8,6 +8,7 @@ on: jobs: call-workflow-passing-data: - uses: cyverse-de/github-workflows/.github/workflows/golangci-lint.yml@v0.0.4 + uses: cyverse-de/github-workflows/.github/workflows/golangci-lint.yml@v0.1.8 with: - go-version: 1.22 + go-version: 1.23 + golangci-lint-version: v1.62.2 diff --git a/.github/workflows/skaffold-build.yml b/.github/workflows/skaffold-build.yml index db0f5fc..aaf03c8 100644 --- a/.github/workflows/skaffold-build.yml +++ b/.github/workflows/skaffold-build.yml @@ -8,7 +8,7 @@ on: jobs: call-workflow-passing-data: - uses: cyverse-de/github-workflows/.github/workflows/skaffold-build.yml@v0.0.7 + uses: cyverse-de/github-workflows/.github/workflows/skaffold-build.yml@v0.1.6 with: build-prerelease: ${{ contains(github.ref_name, '-rc') }} secrets: diff --git a/.gitignore b/.gitignore index 7c27451..083423c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ configs/local.yml configs/local.yaml **/dotenv dev-test/ +.env +local.mod +local.sum diff --git a/app/addons.go b/app/addons.go index 711c992..a701367 100644 --- a/app/addons.go +++ b/app/addons.go @@ -17,72 +17,55 @@ import ( ) func (a *App) addAddon(ctx context.Context, request *qms.AddAddonRequest) *qms.AddonResponse { + var newAddon *db.Addon d := db.New(a.db) - - reqAddon := request.Addon response := qmsinit.NewAddonResponse() - if reqAddon.Name == "" { - response.Error = serrors.NatsError(ctx, errors.New("name must be set")) - return response - } - - if reqAddon.Description == "" { - response.Error = serrors.NatsError(ctx, errors.New("descriptions must be set")) - return response - } - - if reqAddon.DefaultAmount <= 0.0 { - response.Error = serrors.NatsError(ctx, errors.New("default_amount must be greater than 0.0")) - return response + // Validate the incoming request. + requestedAddon := db.NewAddonFromQMS(request.Addon) + if err := requestedAddon.Validate(); err != nil { + response.Error = serrors.NatsError(ctx, err) } - - if reqAddon.ResourceType.Name == "" && reqAddon.ResourceType.Uuid == "" { - response.Error = serrors.NatsError(ctx, errors.New("resource_type.name or resource_type.uuid must be set")) - return response + if err := requestedAddon.ValidateAddonRateUniqueness(); err != nil { + response.Error = serrors.NatsError(ctx, err) } - var lookupRT *db.ResourceType - + // Start a transaction. tx, err := d.Begin() if err != nil { response.Error = serrors.NatsError(ctx, err) return response } - defer func() { - _ = tx.Rollback() - }() + err = tx.Wrap(func() error { - if reqAddon.ResourceType.Name != "" && reqAddon.ResourceType.Uuid == "" { - lookupRT, err = d.GetResourceTypeByName(ctx, reqAddon.ResourceType.Name, db.WithTX(tx)) + // Look up the resource type. + resourceType, err := d.LookupResoureType(ctx, &requestedAddon.ResourceType, db.WithTX(tx)) if err != nil { - response.Error = serrors.NatsError(ctx, err) - return response + return err } - } else { - lookupRT, err = d.GetResourceType(ctx, reqAddon.ResourceType.Uuid, db.WithTX(tx)) + requestedAddon.ResourceType = *resourceType + + // Add the addon to the database. + addonID, err := d.AddAddon(ctx, requestedAddon, db.WithTX(tx)) if err != nil { - response.Error = serrors.NatsError(ctx, err) - return response + return err } - } - newAddon := db.NewAddonFromQMS(request.Addon) - newAddon.ResourceType = *lookupRT + // Retrieve the addon from the database. + newAddon, err = d.GetAddonByID(ctx, addonID, db.WithTX(tx)) + if err != nil { + return err + } - newID, err := d.AddAddon(ctx, newAddon, db.WithTX(tx)) + return nil + }) if err != nil { response.Error = serrors.NatsError(ctx, err) return response } - if err = tx.Commit(); err != nil { - response.Error = serrors.NatsError(ctx, err) - return response - } - + // Return the inserted addon. response.Addon = newAddon.ToQMSType() - response.Addon.Uuid = newID return response } @@ -191,14 +174,28 @@ func (a *App) updateAddon(ctx context.Context, request *qms.UpdateAddonRequest) updateAddon := db.NewUpdateAddonFromQMS(request) - result, err := d.UpdateAddon(ctx, updateAddon) + tx, err := d.Begin() if err != nil { response.Error = serrors.NatsError(ctx, err) return response } + err = tx.Wrap(func() error { + _, err := d.UpdateAddon(ctx, updateAddon, db.WithTX(tx)) + if err != nil { + return err + } - response.Addon = result.ToQMSType() + result, err := d.GetAddonByID(ctx, updateAddon.ID, db.WithTX(tx)) + if err != nil { + return err + } + response.Addon = result.ToQMSType() + return nil + }) + if err != nil { + response.Error = serrors.NatsError(ctx, err) + } return response } @@ -314,15 +311,26 @@ func (a *App) listSubscriptionAddons(ctx context.Context, request *requests.ByUU response := qmsinit.NewSubscriptionAddonListResponse() d := db.New(a.db) - - results, err := d.ListSubscriptionAddons(ctx, request.Uuid) + tx, err := d.Begin() if err != nil { response.Error = serrors.NatsError(ctx, err) return response } - for _, addon := range results { - response.SubscriptionAddons = append(response.SubscriptionAddons, addon.ToQMSType()) + err = tx.Wrap(func() error { + results, err := d.ListSubscriptionAddons(ctx, request.Uuid, db.WithTX(tx)) + if err != nil { + return err + } + + for _, addon := range results { + response.SubscriptionAddons = append(response.SubscriptionAddons, addon.ToQMSType()) + } + + return nil + }) + if err != nil { + response.Error = serrors.NatsError(ctx, err) } return response diff --git a/app/plans.go b/app/plans.go index 8fa81bb..91c17e3 100644 --- a/app/plans.go +++ b/app/plans.go @@ -22,27 +22,9 @@ func (a *App) listPlans(ctx context.Context) *qms.PlanList { return response } - for _, p := range plans { - newP := &qms.Plan{ - Uuid: p.ID, - Name: p.Name, - Description: p.Description, - PlanQuotaDefaults: []*qms.QuotaDefault{}, - } - - for _, q := range p.QuotaDefaults { - newP.PlanQuotaDefaults = append(newP.PlanQuotaDefaults, &qms.QuotaDefault{ - Uuid: q.ID, - QuotaValue: q.QuotaValue, - ResourceType: &qms.ResourceType{ - Uuid: q.ResourceType.ID, - Name: q.ResourceType.Name, - Unit: q.ResourceType.Unit, - }, - }) - } - - response.Plans = append(response.Plans, newP) + response.Plans = make([]*qms.Plan, len(plans)) + for i, p := range plans { + response.Plans[i] = p.ToQMSPlan() } return response @@ -83,34 +65,55 @@ func (a *App) addPlan(ctx context.Context, request *qms.AddPlanRequest) *qms.Pla d := db.New(a.db) - var qd []db.PlanQuotaDefault - var newPlanID string tx, err := d.Begin() if err != nil { response.Error = errors.NatsError(ctx, err) return response } err = tx.Wrap(func() error { - var err error - - for _, pqd := range request.Plan.PlanQuotaDefaults { - qd = append(qd, db.PlanQuotaDefault{ - QuotaValue: float64(pqd.QuotaValue), - ResourceType: db.ResourceType{ - ID: pqd.ResourceType.Uuid, - Name: pqd.ResourceType.Name, - Unit: pqd.ResourceType.Unit, - }, - }) + incomingPlan := db.NewPlanFromQMS(request.Plan) + err := incomingPlan.Validate() + if err != nil { + return err + } + + existingPlan, err := d.GetPlanByName(ctx, incomingPlan.Name, db.WithTX(tx)) + if err != nil { + return err + } else if existingPlan != nil { + return fmt.Errorf("a plan named %s already exists", incomingPlan.Name) } - newPlanID, err = d.AddPlan(ctx, &db.Plan{ - Name: request.Plan.Name, - Description: request.Plan.Description, - QuotaDefaults: qd, - }, db.WithTX(tx)) + for i, pqd := range incomingPlan.QuotaDefaults { + rt, err := d.LookupResoureType(ctx, &pqd.ResourceType, db.WithTX(tx)) + if err != nil { + return err + } + incomingPlan.QuotaDefaults[i].ResourceType = *rt + } - return err + err = incomingPlan.ValidateQuotaDefaultUniqueness() + if err != nil { + return err + } + + err = incomingPlan.ValidatePlanRateUniqueness() + if err != nil { + return err + } + + newPlanID, err := d.AddPlan(ctx, incomingPlan, db.WithTX(tx)) + if err != nil { + return err + } + + plan, err := d.GetPlanByID(ctx, newPlanID, db.WithTX(tx)) + if err != nil { + return err + } + + response.Plan = plan.ToQMSPlan() + return nil }) if err != nil { @@ -118,9 +121,6 @@ func (a *App) addPlan(ctx context.Context, request *qms.AddPlanRequest) *qms.Pla return response } - response.Plan = request.Plan - response.Plan.Uuid = newPlanID - return response } diff --git a/db/addons.go b/db/addons.go index e5282a2..53d3dd5 100644 --- a/db/addons.go +++ b/db/addons.go @@ -2,6 +2,7 @@ package db import ( "context" + "fmt" t "github.com/cyverse-de/subscriptions/db/tables" suberrors "github.com/cyverse-de/subscriptions/errors" @@ -29,6 +30,22 @@ func (d *Database) AddAddon(ctx context.Context, addon *Addon, opts ...QueryOpti return "", err } + // Add the addon rates. + addonRateRows := make([]interface{}, len(addon.AddonRates)) + for i, r := range addon.AddonRates { + addonRateRows[i] = goqu.Record{ + "addon_id": newAddonID, + "effective_date": r.EffectiveDate, + "rate": r.Rate, + } + } + addonRateDS := db.Insert(t.AddonRates). + Rows(addonRateRows...). + Executor() + if _, err := addonRateDS.ExecContext(ctx); err != nil { + return "", err + } + return newAddonID, nil } @@ -59,14 +76,24 @@ func (d *Database) GetAddonByID(ctx context.Context, addonID string, opts ...Que Where(t.Addons.Col("id").Eq(addonID)). Executor() - if addonFound, err = addonInfo.ScanStructContext(ctx, addon); err != nil || !addonFound { + addonFound, err = addonInfo.ScanStructContext(ctx, addon) + if err != nil { return nil, errors.Wrap(err, "unable to get add-on info") + } else if !addonFound { + return nil, fmt.Errorf("addon ID %s not found", addonID) } + addonRates, err := d.ListRatesForAddon(ctx, addonID, opts...) + if err != nil { + return nil, errors.Wrap(err, "unable to get add-on info") + } + addon.AddonRates = addonRates + return addon, nil } func (d *Database) ListAddons(ctx context.Context, opts ...QueryOption) ([]Addon, error) { + wrapMsg := "unable to list addons" _, db := d.querySettings(opts...) ds := db.From(t.Addons). @@ -86,12 +113,42 @@ func (d *Database) ListAddons(ctx context.Context, opts ...QueryOption) ([]Addon var addons []Addon if err := ds.ScanStructsContext(ctx, &addons); err != nil { - return nil, errors.Wrap(err, "unable to list addons") + return nil, errors.Wrap(err, wrapMsg) + } + + for i, addon := range addons { + addonRates, err := d.ListRatesForAddon(ctx, addon.ID, opts...) + if err != nil { + return nil, errors.Wrap(err, wrapMsg) + } + addons[i].AddonRates = addonRates } return addons, nil } +func (d *Database) ListRatesForAddon(ctx context.Context, addonID string, opts ...QueryOption) ([]AddonRate, error) { + _, db := d.querySettings(opts...) + + ds := db.From(t.AddonRates). + Select( + t.AddonRates.Col("id"), + t.AddonRates.Col("addon_id"), + t.AddonRates.Col("effective_date"), + t.AddonRates.Col("rate"), + ). + Where(goqu.Ex{"addon_id": addonID}). + Order(goqu.I("effective_date").Asc()) + d.LogSQL(ds) + + var addonRates []AddonRate + if err := ds.ScanStructsContext(ctx, &addonRates); err != nil { + return nil, errors.Wrapf(err, "unable to list rates for addon ID %s", addonID) + } + + return addonRates, nil +} + func (d *Database) ToggleAddonPaid(ctx context.Context, addonID string, opts ...QueryOption) (*Addon, error) { tx, err := d.Begin() if err != nil { @@ -146,48 +203,117 @@ func (d *Database) ToggleAddonPaid(ctx context.Context, addonID string, opts ... return retval, nil } -func (d *Database) UpdateAddon(ctx context.Context, updatedAddon *UpdateAddon, opts ...QueryOption) (*Addon, error) { +func (d *Database) AddAddonRate(ctx context.Context, r AddonRate, opts ...QueryOption) error { _, db := d.querySettings(opts...) - rec := goqu.Record{} - - if updatedAddon.UpdateName { - rec["name"] = updatedAddon.Name - } - if updatedAddon.UpdateDescription { - rec["description"] = updatedAddon.Description - } - if updatedAddon.UpdateResourceType { - rec["resource_type_id"] = updatedAddon.ResourceTypeID - } - if updatedAddon.UpdateDefaultAmount { - rec["default_amount"] = updatedAddon.DefaultAmount - } - if updatedAddon.UpdateDefaultPaid { - rec["default_paid"] = updatedAddon.DefaultPaid + ds := db.Insert(t.AddonRates). + Rows( + goqu.Record{ + "addon_id": r.AddonID, + "effective_date": r.EffectiveDate, + "rate": r.Rate, + }, + ). + Executor() + if _, err := ds.ExecContext(ctx); err != nil { + return err } - ds := db.Update(t.Addons). - Set(rec). - Where(t.Addons.Col("id").Eq(updatedAddon.ID)). - Returning( - t.Addons.Col("id"), - t.Addons.Col("name"), - t.Addons.Col("description"), - t.Addons.Col("default_amount"), - t.Addons.Col("default_paid"), - t.Addons.Col("resource_type_id").As(goqu.C("resource_types.id")), + return nil +} + +func (d *Database) UpdateAddonRate(ctx context.Context, r AddonRate, opts ...QueryOption) error { + _, db := d.querySettings(opts...) + + ds := db.Update(t.AddonRates). + Set( + goqu.Record{ + "addon_id": r.AddonID, + "effective_date": r.EffectiveDate, + "rate": r.Rate, + }, ). + Where(t.AddonRates.Col("id").Eq(r.ID)). Executor() + if _, err := ds.ExecContext(ctx); err != nil { + return err + } - retval := &Addon{} - found, err := ds.ScanStructContext(ctx, retval) - if err != nil { - return nil, errors.Wrap(err, "unable to scan results of update") + return nil +} + +func (d *Database) UpdateAddon( + ctx context.Context, + addonUpdateRecord *UpdateAddon, + opts ...QueryOption, +) (*Addon, error) { + var retval *Addon + _, db := d.querySettings(opts...) + + rec := goqu.Record{} + + updateAddon := false + if addonUpdateRecord.UpdateName { + rec["name"] = addonUpdateRecord.Name + updateAddon = true + } + if addonUpdateRecord.UpdateDescription { + rec["description"] = addonUpdateRecord.Description + updateAddon = true + } + if addonUpdateRecord.UpdateResourceType { + rec["resource_type_id"] = addonUpdateRecord.ResourceTypeID + updateAddon = true + } + if addonUpdateRecord.UpdateDefaultAmount { + rec["default_amount"] = addonUpdateRecord.DefaultAmount + updateAddon = true + } + if addonUpdateRecord.UpdateDefaultPaid { + rec["default_paid"] = addonUpdateRecord.DefaultPaid + updateAddon = true + } + + // Update the top-level addon record if requested. + if updateAddon { + ds := db.Update(t.Addons). + Set(rec). + Where(t.Addons.Col("id").Eq(addonUpdateRecord.ID)). + Returning( + t.Addons.Col("id"), + t.Addons.Col("name"), + t.Addons.Col("description"), + t.Addons.Col("default_amount"), + t.Addons.Col("default_paid"), + t.Addons.Col("resource_type_id").As(goqu.C("resource_types.id")), + ). + Executor() + + retval := &Addon{} + found, err := ds.ScanStructContext(ctx, retval) + if err != nil { + return nil, errors.Wrap(err, "unable to scan results of update") + } + if !found { + return nil, suberrors.ErrAddonNotFound + } } - if !found { - return nil, suberrors.ErrAddonNotFound + // Update existing addon rates. + if addonUpdateRecord.UpdateAddonRates { + for _, r := range addonUpdateRecord.AddonRates { + if r.ID == "" { + err := d.AddAddonRate(ctx, r, opts...) + if err != nil { + return nil, err + } + } else { + err := d.UpdateAddonRate(ctx, r, opts...) + if err != nil { + return nil, err + } + } + } } return retval, nil @@ -227,6 +353,9 @@ func subAddonDS(db GoquDatabase) *goqu.SelectDataset { t.Subscriptions.Col("last_modified_by").As(goqu.C("subscriptions.last_modified_by")), t.Subscriptions.Col("last_modified_at").As(goqu.C("subscriptions.last_modified_at")), t.Subscriptions.Col("paid").As(goqu.C("subscriptions.paid")), + t.PlanRates.Col("id").As(goqu.C("subscriptions.plan_rates.id")), + t.PlanRates.Col("effective_date").As(goqu.C("subscriptions.plan_rates.effective_date")), + t.PlanRates.Col("rate").As(goqu.C("subscriptions.plan_rates.rate")), t.Users.Col("id").As(goqu.C("subscriptions.users.id")), t.Users.Col("username").As(goqu.C("subscriptions.users.username")), t.Plans.Col("id").As(goqu.C("subscriptions.plans.id")), @@ -235,12 +364,18 @@ func subAddonDS(db GoquDatabase) *goqu.SelectDataset { t.SubscriptionAddons.Col("amount"), t.SubscriptionAddons.Col("paid"), + + t.AddonRates.Col("id").As(goqu.C("addon_rates.id")), + t.AddonRates.Col("effective_date").As(goqu.C("addon_rates.effective_date")), + t.AddonRates.Col("rate").As(goqu.C("addon_rates.rate")), ). Join(t.Subscriptions, goqu.On(t.SubscriptionAddons.Col("subscription_id").Eq(t.Subscriptions.Col("id")))). + Join(t.PlanRates, goqu.On(t.Subscriptions.Col("plan_rate_id").Eq(t.PlanRates.Col("id")))). Join(t.Addons, goqu.On(t.Addons.Col("id").Eq(t.SubscriptionAddons.Col("addon_id")))). Join(t.ResourceTypes, goqu.On(t.Addons.Col("resource_type_id").Eq(t.ResourceTypes.Col("id")))). Join(t.Users, goqu.On(t.Subscriptions.Col("user_id").Eq(t.Users.Col("id")))). - Join(t.Plans, goqu.On(t.Subscriptions.Col("plan_id").Eq(t.Plans.Col("id")))) + Join(t.Plans, goqu.On(t.Subscriptions.Col("plan_id").Eq(t.Plans.Col("id")))). + Join(t.AddonRates, goqu.On(t.SubscriptionAddons.Col("addon_rate_id").Eq(t.AddonRates.Col("id")))) } func (d *Database) GetSubscriptionAddonByID(ctx context.Context, subAddonID string, opts ...QueryOption) (*SubscriptionAddon, error) { @@ -264,7 +399,11 @@ func (d *Database) GetSubscriptionAddonByID(ctx context.Context, subAddonID stri return subAddon, nil } -func (d *Database) ListSubscriptionAddons(ctx context.Context, subscriptionID string, opts ...QueryOption) ([]SubscriptionAddon, error) { +func (d *Database) ListSubscriptionAddons( + ctx context.Context, + subscriptionID string, + opts ...QueryOption, +) ([]SubscriptionAddon, error) { _, db := d.querySettings(opts...) ds := subAddonDS(db). @@ -280,7 +419,11 @@ func (d *Database) ListSubscriptionAddons(ctx context.Context, subscriptionID st return addons, nil } -func (d *Database) AddSubscriptionAddon(ctx context.Context, subscriptionID, addonID string, opts ...QueryOption) (*SubscriptionAddon, error) { +func (d *Database) AddSubscriptionAddon( + ctx context.Context, + subscriptionID, addonID string, + opts ...QueryOption, +) (*SubscriptionAddon, error) { qs, db, err := d.querySettingsWithTX(opts...) if err != nil { return nil, err @@ -298,6 +441,10 @@ func (d *Database) AddSubscriptionAddon(ctx context.Context, subscriptionID, add if err != nil { return nil, err } + addonRate := addon.GetCurrentRate() + if addonRate == nil { + return nil, fmt.Errorf("no active rate found for addon %s", addon.ID) + } ds := db.Insert(t.SubscriptionAddons). Rows(goqu.Record{ @@ -305,6 +452,7 @@ func (d *Database) AddSubscriptionAddon(ctx context.Context, subscriptionID, add "addon_id": addonID, "amount": addon.DefaultAmount, "paid": addon.DefaultPaid, + "addon_rate_id": addonRate.ID, }). Returning(t.SubscriptionAddons.Col("id")). Executor() @@ -331,6 +479,7 @@ func (d *Database) AddSubscriptionAddon(ctx context.Context, subscriptionID, add Subscription: *subscription, Amount: addon.DefaultAmount, Paid: addon.DefaultPaid, + Rate: *addonRate, } return retval, nil diff --git a/db/plans.go b/db/plans.go index 8835369..d5e406f 100644 --- a/db/plans.go +++ b/db/plans.go @@ -9,19 +9,32 @@ import ( "github.com/pkg/errors" ) -func planDetailsDS(db GoquDatabase, planID string) *goqu.SelectDataset { +func planQuotaDefaultsDS(db GoquDatabase, planID string) *goqu.SelectDataset { return db.From(t.PQD). Select( t.PQD.Col("id"), t.PQD.Col("plan_id"), t.PQD.Col("quota_value"), + t.PQD.Col("effective_date"), t.RT.Col("id").As(goqu.C("resource_types.id")), t.RT.Col("name").As(goqu.C("resource_types.name")), t.RT.Col("unit").As(goqu.C("resource_types.unit")), ). Join(t.RT, goqu.On(t.PQD.Col("resource_type_id").Eq(t.RT.Col("id")))). - Where(t.PQD.Col("plan_id").Eq(planID)) + Where(t.PQD.Col("plan_id").Eq(planID)). + Order(t.PQD.Col("effective_date").Asc(), t.RT.Col("name").Asc()) +} + +func planRatesDS(db GoquDatabase, planID string) *goqu.SelectDataset { + return db.From(t.PlanRates). + Select( + t.PlanRates.Col("id"), + t.PlanRates.Col("effective_date"), + t.PlanRates.Col("rate"), + ). + Where(t.PlanRates.Col("plan_id").Eq(planID)). + Order(t.PlanRates.Col("effective_date").Asc()) } func (d *Database) getPlanList(ctx context.Context, opts ...QueryOption) ([]Plan, error) { @@ -41,12 +54,12 @@ func (d *Database) getPlanList(ctx context.Context, opts ...QueryOption) ([]Plan return plans, nil } -func (d *Database) loadPlanDetails(ctx context.Context, plan *Plan, opts ...QueryOption) error { - wrapMsg := fmt.Sprintf("unable to load details for plan ID %s", plan.ID) +func (d *Database) loadPlanQuotaDefaults(ctx context.Context, plan *Plan, opts ...QueryOption) error { + wrapMsg := fmt.Sprintf("unable to load the plan quota defaults for plan ID %s", plan.ID) _, db := d.querySettings(opts...) // Build the query. - query := planDetailsDS(db, plan.ID) + query := planQuotaDefaultsDS(db, plan.ID) d.LogSQL(query) // Execute the query and scan the results. @@ -57,6 +70,37 @@ func (d *Database) loadPlanDetails(ctx context.Context, plan *Plan, opts ...Quer return nil } +func (d *Database) loadPlanRates(ctx context.Context, plan *Plan, opts ...QueryOption) error { + wrapMsg := fmt.Sprintf("unable to load the plan rates for plan ID %s", plan.ID) + _, db := d.querySettings(opts...) + + // Build the query. + query := planRatesDS(db, plan.ID) + d.LogSQL(query) + + // Execute the query and scan the results. + err := query.ScanStructsContext(ctx, &plan.Rates) + if err != nil { + return errors.Wrap(err, wrapMsg) + } + + return nil +} + +func (d *Database) loadPlanDetails(ctx context.Context, plan *Plan, opts ...QueryOption) error { + err := d.loadPlanQuotaDefaults(ctx, plan, opts...) + if err != nil { + return err + } + + err = d.loadPlanRates(ctx, plan, opts...) + if err != nil { + return err + } + + return nil +} + func (d *Database) ListPlans(ctx context.Context, opts ...QueryOption) ([]Plan, error) { // Get the list of plans. plans, err := d.getPlanList(ctx, opts...) @@ -147,20 +191,35 @@ func (d *Database) AddPlan(ctx context.Context, plan *Plan, opts ...QueryOption) } for _, pqd := range plan.QuotaDefaults { - newDS := db.Insert(t.PQD). + pqdDS := db.Insert(t.PQD). Rows( goqu.Record{ "plan_id": newPlanID, "resource_type_id": pqd.ResourceType.ID, "quota_value": pqd.QuotaValue, + "effective_date": pqd.EffectiveDate, }, ).Executor() - if _, err := newDS.ExecContext(ctx); err != - nil { + if _, err := pqdDS.ExecContext(ctx); err != nil { return "", errors.Wrap(err, "unable to add plan quota defaults") } } + for _, pr := range plan.Rates { + prDS := db.Insert(t.PlanRates). + Rows( + goqu.Record{ + "plan_id": newPlanID, + "effective_date": pr.EffectiveDate, + "rate": pr.Rate, + }, + ).Executor() + + if _, err := prDS.ExecContext(ctx); err != nil { + return "", errors.Wrap(err, "unable to add plan rates") + } + } + return newPlanID, nil } diff --git a/db/resourcetypes.go b/db/resourcetypes.go index babda8b..6145141 100644 --- a/db/resourcetypes.go +++ b/db/resourcetypes.go @@ -2,6 +2,7 @@ package db import ( "context" + "fmt" t "github.com/cyverse-de/subscriptions/db/tables" "github.com/doug-martin/goqu/v9" @@ -110,3 +111,19 @@ func (d *Database) GetResourceTypeByName(ctx context.Context, name string, opts return &resourceType, nil } + +// LookupResourceType attempts to look up a resource type using either its ID or name in that order. It's an error to +// attempt a lookup with no ID or name specified. +func (d *Database) LookupResoureType( + ctx context.Context, + lookup *ResourceType, + opts ...QueryOption, +) (*ResourceType, error) { + if lookup.ID != "" { + return d.GetResourceType(ctx, lookup.ID, opts...) + } else if lookup.Name != "" { + return d.GetResourceTypeByName(ctx, lookup.Name, opts...) + } else { + return nil, fmt.Errorf("either the resource type ID or name must be specified") + } +} diff --git a/db/tables/tables.go b/db/tables/tables.go index 2fd38c4..b19009a 100644 --- a/db/tables/tables.go +++ b/db/tables/tables.go @@ -17,4 +17,6 @@ var ( Usages = goqu.T("usages") Updates = goqu.T("updates") Addons = goqu.T("addons") + PlanRates = goqu.T("plan_rates") + AddonRates = goqu.T("addon_rates") ) diff --git a/db/types.go b/db/types.go index 0fb369a..074143f 100644 --- a/db/types.go +++ b/db/types.go @@ -3,6 +3,7 @@ package db import ( "context" "database/sql" + "fmt" "time" "github.com/cyverse-de/p/go/qms" @@ -48,6 +49,11 @@ const QuotasTrackedMetric = "quotas" const DefaultPlanName = "Basic" +type PlanQuotaDefaultKey struct { + ResourceTypeID string + EffectiveDate int64 +} + type UpdateOperation struct { ID string `db:"id" goqu:"defaultifempty"` Name string `db:"name"` @@ -90,6 +96,16 @@ func NewResourceTypeFromQMS(q *qms.ResourceType) *ResourceType { } } +func (rt ResourceType) ValidateForPlan() error { + + // We must have enough information to at least attempt to look up the resource type. + if rt.ID == "" && rt.Name == "" { + return fmt.Errorf("either the resource type name or the resource type ID is required") + } + + return nil +} + var ResourceTypeNames = []string{ "cpu.hours", "data.size", @@ -132,6 +148,7 @@ type Subscription struct { LastModifiedBy string `db:"last_modified_by"` LastModifiedAt string `db:"last_modified_at" goqu:"defaultifempty"` Paid bool `db:"paid" goqu:"defaultifempty"` + Rate PlanRate `db:"plan_rates"` } func NewSubscriptionFromQMS(s *qms.Subscription) *Subscription { @@ -159,6 +176,7 @@ func NewSubscriptionFromQMS(s *qms.Subscription) *Subscription { CreatedBy: s.User.Username, LastModifiedBy: s.User.Username, Paid: s.Paid, + Rate: *NewPlanRateFromQMS(s.PlanRate, s.Plan.Uuid), } } @@ -169,7 +187,7 @@ func (up Subscription) ToQMSSubscription() *qms.Subscription { quotas[i] = quota.ToQMSQuota() } - // Convert th elist of usages. + // Convert the list of usages. usages := make([]*qms.Usage, len(up.Usages)) for i, usage := range up.Usages { usages[i] = usage.ToQMSUsage() @@ -184,6 +202,7 @@ func (up Subscription) ToQMSSubscription() *qms.Subscription { Quotas: quotas, Usages: usages, Paid: up.Paid, + PlanRate: up.Rate.ToQMSPlanRate(), } } @@ -192,18 +211,24 @@ type Plan struct { Name string `db:"name"` Description string `db:"description"` QuotaDefaults []PlanQuotaDefault `db:"-"` + Rates []PlanRate `db:"-"` } func NewPlanFromQMS(q *qms.Plan) *Plan { - pqd := make([]PlanQuotaDefault, 0) - for _, qd := range q.PlanQuotaDefaults { - pqd = append(pqd, *NewPlanQuotaDefaultFromQMS(qd, q.Uuid)) + pqd := make([]PlanQuotaDefault, len(q.PlanQuotaDefaults)) + for i, qd := range q.PlanQuotaDefaults { + pqd[i] = *NewPlanQuotaDefaultFromQMS(qd, q.Uuid) + } + pr := make([]PlanRate, len(q.PlanRates)) + for i, r := range q.PlanRates { + pr[i] = *NewPlanRateFromQMS(r, q.Uuid) } return &Plan{ ID: q.Uuid, Name: q.Name, Description: q.Description, QuotaDefaults: pqd, + Rates: pr, } } @@ -213,37 +238,212 @@ func (p Plan) ToQMSPlan() *qms.Plan { for i, quotaDefault := range p.QuotaDefaults { quotaDefaults[i] = quotaDefault.ToQMSQuotaDefault() } + rates := make([]*qms.PlanRate, len(p.Rates)) + for i, rate := range p.Rates { + rates[i] = rate.ToQMSPlanRate() + } return &qms.Plan{ Uuid: p.ID, Name: p.Name, Description: p.Description, PlanQuotaDefaults: quotaDefaults, + PlanRates: rates, + } +} + +func (p Plan) GetActiveRate() *PlanRate { + now := time.Now() + + var effectiveRate *PlanRate + for _, pr := range p.Rates { + if pr.EffectiveDate.After(now) { + break + } + effectiveRate = &pr + } + + return effectiveRate +} + +func (p Plan) GetActiveQuotaDefaults() []*PlanQuotaDefault { + now := time.Now() + + pqdMap := make(map[string]*PlanQuotaDefault) + for _, pqd := range p.QuotaDefaults { + if pqd.EffectiveDate.After(now) { + break + } + pqdMap[pqd.ResourceType.Name] = &pqd + } + + index := 0 + pqds := make([]*PlanQuotaDefault, len(pqdMap)) + for _, pqd := range pqdMap { + pqds[index] = pqd + index++ + } + + return pqds +} + +func (p Plan) Validate() error { + + // The plan name and description are both required. + if p.Name == "" { + return fmt.Errorf("a plan name is required") + } + if p.Description == "" { + return fmt.Errorf("a plan description is required") + } + + // Validate the quota defaults. + for _, qd := range p.QuotaDefaults { + if err := qd.ValidateForPlan(); err != nil { + return err + } } + + // Validate the plan rates. + for _, r := range p.Rates { + if err := r.ValidateForPlan(); err != nil { + return err + } + } + + return nil +} + +func (p Plan) ValidateQuotaDefaultUniqueness() error { + + // Verify that there's only one quota default per resource type and effective date. + uniquePlanQuotaDefaults := make(map[PlanQuotaDefaultKey]bool) + for _, qd := range p.QuotaDefaults { + key := qd.Key() + if uniquePlanQuotaDefaults[key] { + return fmt.Errorf("there can only be one quota default for each resource type and effective date") + } else { + uniquePlanQuotaDefaults[key] = true + } + } + + return nil +} + +func (p Plan) ValidatePlanRateUniqueness() error { + + // Verify that there's only one rate per effective date. + uniquePlanRates := make(map[int64]bool) + for _, r := range p.Rates { + key := r.EffectiveDate.UnixMicro() + if uniquePlanRates[key] { + return fmt.Errorf("there can only be one plan rate for each effective date") + } else { + uniquePlanRates[key] = true + } + } + + return nil } type PlanQuotaDefault struct { - ID string `db:"id" goqu:"defaultifempty"` - PlanID string `db:"plan_id"` - QuotaValue float64 `db:"quota_value"` - ResourceType ResourceType `db:"resource_types"` + ID string `db:"id" goqu:"defaultifempty"` + PlanID string `db:"plan_id"` + QuotaValue float64 `db:"quota_value"` + ResourceType ResourceType `db:"resource_types"` + EffectiveDate time.Time `db:"effective_date"` } func NewPlanQuotaDefaultFromQMS(q *qms.QuotaDefault, planID string) *PlanQuotaDefault { + var effectiveDate time.Time + if q.EffectiveDate != nil { + effectiveDate = q.EffectiveDate.AsTime() + } return &PlanQuotaDefault{ - ID: q.Uuid, - PlanID: planID, - QuotaValue: q.QuotaValue, - ResourceType: *NewResourceTypeFromQMS(q.ResourceType), + ID: q.Uuid, + PlanID: planID, + QuotaValue: q.QuotaValue, + ResourceType: *NewResourceTypeFromQMS(q.ResourceType), + EffectiveDate: effectiveDate, } } func (pqd PlanQuotaDefault) ToQMSQuotaDefault() *qms.QuotaDefault { return &qms.QuotaDefault{ - Uuid: pqd.ID, - QuotaValue: pqd.QuotaValue, - ResourceType: pqd.ResourceType.ToQMSResourceType(), + Uuid: pqd.ID, + QuotaValue: pqd.QuotaValue, + ResourceType: pqd.ResourceType.ToQMSResourceType(), + EffectiveDate: timestamppb.New(pqd.EffectiveDate), + } +} + +func (pqd PlanQuotaDefault) ValidateForPlan() error { + + // The default quota value must be specified and greater than zero. + if pqd.QuotaValue <= 0 { + return fmt.Errorf("plan quota default values must be specified and greater than zero") + } + + // The effective date must be specified. + if pqd.EffectiveDate.IsZero() { + return fmt.Errorf("all plan quota defaults must have an effective date") + } + + return pqd.ResourceType.ValidateForPlan() +} + +func (pqd PlanQuotaDefault) Key() PlanQuotaDefaultKey { + return PlanQuotaDefaultKey{ + ResourceTypeID: pqd.ResourceType.ID, + EffectiveDate: pqd.EffectiveDate.UnixMicro(), + } +} + +type PlanRate struct { + ID string `db:"id" goqu:"defaultifempty"` + PlanID string `db:"plan_id"` + EffectiveDate time.Time `db:"effective_date"` + Rate float64 `db:"rate"` +} + +func NewPlanRateFromQMS(r *qms.PlanRate, planID string) *PlanRate { + var effectiveDate time.Time + if r.EffectiveDate != nil { + effectiveDate = r.EffectiveDate.AsTime() + } + return &PlanRate{ + ID: r.Uuid, + PlanID: planID, + EffectiveDate: effectiveDate, + Rate: r.Rate, + } +} + +func (pr PlanRate) ToQMSPlanRate() *qms.PlanRate { + return &qms.PlanRate{ + Uuid: pr.ID, + EffectiveDate: timestamppb.New(pr.EffectiveDate), + Rate: pr.Rate, + } +} + +func (pr PlanRate) Validate() error { + + // The rate can't be negative. + if pr.Rate < 0 { + return fmt.Errorf("the plan rate must not be less than zero") } + + // The effective date has to be specified. + if pr.EffectiveDate.IsZero() { + return fmt.Errorf("the effective date of the plan rate must be specified") + } + + return nil +} + +func (pr PlanRate) ValidateForPlan() error { + return pr.Validate() } type Usage struct { @@ -336,19 +536,29 @@ type Addon struct { ResourceType ResourceType `db:"resource_types"` DefaultAmount float64 `db:"default_amount"` DefaultPaid bool `db:"default_paid"` + AddonRates []AddonRate `db:"-"` } func NewAddonFromQMS(q *qms.Addon) *Addon { + addonRates := make([]AddonRate, len(q.AddonRates)) + for i, r := range q.AddonRates { + addonRates[i] = *NewAddonRateFromQMS(r, q.Uuid) + } return &Addon{ Name: q.Name, Description: q.Description, DefaultAmount: q.DefaultAmount, DefaultPaid: q.DefaultPaid, ResourceType: *NewResourceTypeFromQMS(q.ResourceType), + AddonRates: addonRates, } } func (a *Addon) ToQMSType() *qms.Addon { + addonRates := make([]*qms.AddonRate, len(a.AddonRates)) + for i, r := range a.AddonRates { + addonRates[i] = r.ToQMSType() + } return &qms.Addon{ Uuid: a.ID, Name: a.Name, @@ -360,21 +570,120 @@ func (a *Addon) ToQMSType() *qms.Addon { Name: a.ResourceType.Name, Unit: a.ResourceType.Unit, }, + AddonRates: addonRates, + } +} + +func (a *Addon) Validate() error { + + // The name and description are both required. + if a.Name == "" { + return fmt.Errorf("name must be set") + } + if a.Description == "" { + return fmt.Errorf("description must be set") + } + + // The default amount must be positive. + if a.DefaultAmount <= 0.0 { + return fmt.Errorf("default_amount must be greater than 0.0") } + + // Verify that we have enough information to attempt to look up the resource type. + if err := a.ResourceType.ValidateForPlan(); err != nil { + return err + } + + // Validate the incoming addon rates. + return nil +} + +func (a *Addon) ValidateAddonRateUniqueness() error { + + // Verify that there's only one rate per effective date. + uniqueAddonRates := make(map[int64]bool) + for _, r := range a.AddonRates { + key := r.EffectiveDate.UnixMicro() + if uniqueAddonRates[key] { + return fmt.Errorf("there can only be one plan rate for each effective date") + } else { + uniqueAddonRates[key] = true + } + } + + return nil +} + +// GetCurrentRate determines the rate that is currently active. +func (a *Addon) GetCurrentRate() *AddonRate { + var currentRate *AddonRate + now := time.Now() + for _, r := range a.AddonRates { + if r.EffectiveDate.After(now) { + break + } + currentRate = &r + } + return currentRate +} + +type AddonRate struct { + ID string `db:"id" goqu:"defaultifempty,skipupdate"` + AddonID string `db:"addon_id"` + EffectiveDate time.Time `db:"effective_date"` + Rate float64 `db:"rate"` +} + +func NewAddonRateFromQMS(r *qms.AddonRate, addonID string) *AddonRate { + var effectiveDate time.Time + if r.EffectiveDate != nil { + effectiveDate = r.EffectiveDate.AsTime() + } + return &AddonRate{ + ID: r.Uuid, + AddonID: addonID, + EffectiveDate: effectiveDate, + Rate: r.Rate, + } +} + +func (r *AddonRate) ToQMSType() *qms.AddonRate { + return &qms.AddonRate{ + Uuid: r.ID, + EffectiveDate: timestamppb.New(r.EffectiveDate), + Rate: r.Rate, + } +} + +func (r *AddonRate) Validate() error { + + // The rate can't be negative. + if r.Rate < 0 { + return fmt.Errorf("the plan rate must not be less than zero") + } + + // The effective date has to be specified. + if r.EffectiveDate.IsZero() { + return fmt.Errorf("the effective date of the plan rate must be specified") + } + + return nil } type UpdateAddon struct { - ID string `db:"id" goqu:"skipupdate"` - Name string `db:"name"` - UpdateName bool `db:"-"` - Description string `db:"description"` - UpdateDescription bool `db:"-"` - ResourceTypeID string `db:"resource_type_id"` - UpdateResourceType bool `db:"-"` - DefaultAmount float64 `db:"default_amount"` - UpdateDefaultAmount bool `db:"-"` - DefaultPaid bool `db:"default_paid"` - UpdateDefaultPaid bool `db:"-"` + ID string `db:"id" goqu:"skipupdate"` + Name string `db:"name"` + UpdateName bool `db:"-"` + Description string `db:"description"` + UpdateDescription bool `db:"-"` + ResourceTypeID string `db:"resource_type_id"` + UpdateResourceType bool `db:"-"` + DefaultAmount float64 `db:"default_amount"` + UpdateDefaultAmount bool `db:"-"` + DefaultPaid bool `db:"default_paid"` + UpdateDefaultPaid bool `db:"-"` + AddonRates []AddonRate `db:"-"` + UpdateAddonRates bool `db:"-"` } func NewUpdateAddonFromQMS(u *qms.UpdateAddonRequest) *UpdateAddon { @@ -385,6 +694,7 @@ func NewUpdateAddonFromQMS(u *qms.UpdateAddonRequest) *UpdateAddon { UpdateResourceType: u.UpdateResourceType, UpdateDefaultAmount: u.UpdateDefaultAmount, UpdateDefaultPaid: u.UpdateDefaultPaid, + UpdateAddonRates: u.UpdateAddonRates, } if update.UpdateName { @@ -402,6 +712,13 @@ func NewUpdateAddonFromQMS(u *qms.UpdateAddonRequest) *UpdateAddon { if update.UpdateResourceType { update.ResourceTypeID = u.Addon.ResourceType.Uuid } + if update.UpdateAddonRates { + addonRates := make([]AddonRate, len(u.Addon.AddonRates)) + for i, r := range u.Addon.AddonRates { + addonRates[i] = *NewAddonRateFromQMS(r, u.Addon.Uuid) + } + update.AddonRates = addonRates + } return update } @@ -411,6 +728,7 @@ type SubscriptionAddon struct { Subscription Subscription `db:"subscriptions"` Amount float64 `db:"amount"` Paid bool `db:"paid"` + Rate AddonRate `db:"addon_rates"` } func NewSubscriptionAddonFromQMS(sa *qms.SubscriptionAddon) *SubscriptionAddon { @@ -420,6 +738,7 @@ func NewSubscriptionAddonFromQMS(sa *qms.SubscriptionAddon) *SubscriptionAddon { Subscription: *NewSubscriptionFromQMS(sa.Subscription), Amount: float64(sa.Amount), Paid: sa.Paid, + Rate: *NewAddonRateFromQMS(sa.AddonRate, sa.Uuid), } } @@ -430,6 +749,7 @@ func (sa *SubscriptionAddon) ToQMSType() *qms.SubscriptionAddon { Subscription: sa.Subscription.ToQMSSubscription(), Amount: sa.Amount, Paid: sa.Paid, + AddonRate: sa.Rate.ToQMSType(), } } diff --git a/db/userplans.go b/db/userplans.go index 44e5d67..b6b85a5 100644 --- a/db/userplans.go +++ b/db/userplans.go @@ -2,6 +2,7 @@ package db import ( "context" + "fmt" "time" t "github.com/cyverse-de/subscriptions/db/tables" @@ -44,9 +45,14 @@ func subscriptionDS(db GoquDatabase) *goqu.SelectDataset { t.Plans.Col("id").As(goqu.C("plans.id")), t.Plans.Col("name").As(goqu.C("plans.name")), t.Plans.Col("description").As(goqu.C("plans.description")), + + t.PlanRates.Col("id").As(goqu.C("plan_rates.id")), + t.PlanRates.Col("effective_date").As(goqu.C("plan_rates.effective_date")), + t.PlanRates.Col("rate").As(goqu.C("plan_rates.rate")), ). Join(t.Users, goqu.On(t.Subscriptions.Col("user_id").Eq(t.Users.Col("id")))). - Join(t.Plans, goqu.On(t.Subscriptions.Col("plan_id").Eq(t.Plans.Col("id")))) + Join(t.Plans, goqu.On(t.Subscriptions.Col("plan_id").Eq(t.Plans.Col("id")))). + Join(t.PlanRates, goqu.On(t.Subscriptions.Col("plan_rate_id").Eq(t.PlanRates.Col("id")))) } func (d *Database) GetSubscriptionByID(ctx context.Context, subscriptionID string, opts ...QueryOption) (*Subscription, error) { @@ -115,6 +121,12 @@ func (d *Database) SetActiveSubscription( n := time.Now() e := subscriptionOpts.EndDate + // Get the active plan rate. + activePlanRate := plan.GetActiveRate() + if activePlanRate == nil { + return "", fmt.Errorf("the %s subscription plan has no effective rate", plan.Name) + } + query := db.Insert(t.Subscriptions). Rows( goqu.Record{ @@ -125,6 +137,7 @@ func (d *Database) SetActiveSubscription( "created_by": "de", "last_modified_by": "de", "paid": subscriptionOpts.Paid, + "plan_rate_id": activePlanRate.ID, }, ). Returning(t.Subscriptions.Col("id")) @@ -136,7 +149,7 @@ func (d *Database) SetActiveSubscription( } // Add the quota defaults as the t.Quotas for the user plan. - for _, quotaDefault := range plan.QuotaDefaults { + for _, quotaDefault := range plan.GetActiveQuotaDefaults() { quotaValue := quotaDefault.QuotaValue * float64(subscriptionOpts.Periods) if quotaDefault.ResourceType.Consumable { quotaValue *= float64(subscriptionOpts.Periods) @@ -262,6 +275,33 @@ func (d *Database) SubscriptionUsages(ctx context.Context, subscriptionID string return usages, nil } +// SubscriptionPlanRates returns a list of rates assocaited with a user plan specified by the passed in UUID. Accepts a +// variable number of QueryOptions, though only WithTX is currently supported. +func (d *Database) SubscriptionPlanRates(ctx context.Context, planID string, opts ...QueryOption) ([]PlanRate, error) { + var ( + err error + db GoquDatabase + rates []PlanRate + ) + + _, db = d.querySettings(opts...) + + ratesQuery := db.From(t.PlanRates). + Select( + t.PlanRates.Col("id").As("id"), + t.PlanRates.Col("plan_id").As("plan_id"), + t.PlanRates.Col("effective_date").As("effective_date"), + t.PlanRates.Col("rate").As("rate"), + ). + Where(t.PlanRates.Col("plan_id").Eq(planID)) + d.LogSQL(ratesQuery) + + if err = ratesQuery.Executor().ScanStructsContext(ctx, &rates); err != nil { + return nil, err + } + return rates, nil +} + // SubscriptionQuotas returns a list of t.Quotas associated with the user plan specified // by the UUID passed in. Accepts a variable number of QueryOptions, though only // WithTX is currently supported. @@ -315,6 +355,7 @@ func (d *Database) SubscriptionQuotaDefaults(ctx context.Context, planID string, t.PQD.Col("id").As("id"), t.PQD.Col("quota_value").As("quota_value"), t.PQD.Col("plan_id").As("plan_id"), + t.PQD.Col("effective_date").As("effective_date"), t.RT.Col("id").As(goqu.C("resource_types.id")), t.RT.Col("name").As(goqu.C("resource_types.name")), t.RT.Col("unit").As(goqu.C("resource_types.unit")), @@ -341,28 +382,28 @@ func (d *Database) LoadSubscriptionDetails(ctx context.Context, subscription *Su quotas []Quota ) - log.Debug("before getting user plan quota defaults") defaults, err = d.SubscriptionQuotaDefaults(ctx, subscription.Plan.ID, opts...) if err != nil { return err } - log.Debug("after getting user plan quota defaults") - log.Debug("before getting user plan t.Quotas") quotas, err = d.SubscriptionQuotas(ctx, subscription.ID, opts...) if err != nil { return err } - log.Debug("after getting user plan t.Quotas") - log.Debug("before getting user plan usages") usages, err = d.SubscriptionUsages(ctx, subscription.ID, opts...) if err != nil { return err } - log.Debug("after getting user plan usages") + + planRates, err := d.SubscriptionPlanRates(ctx, subscription.Plan.ID) + if err != nil { + return err + } subscription.Plan.QuotaDefaults = defaults + subscription.Plan.Rates = planRates subscription.Quotas = quotas subscription.Usages = usages diff --git a/go.mod b/go.mod index 9fa7ed4..3e146ba 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ module github.com/cyverse-de/subscriptions -go 1.22 +go 1.23.3 require ( github.com/cyverse-de/go-mod/cfg v0.0.2 - github.com/cyverse-de/go-mod/gotelnats v0.0.11 - github.com/cyverse-de/go-mod/logging v0.0.2 - github.com/cyverse-de/go-mod/otelutils v0.0.3 - github.com/cyverse-de/go-mod/pbinit v0.1.11 - github.com/cyverse-de/go-mod/protobufjson v0.0.3 - github.com/cyverse-de/go-mod/subjects v0.1.4 - github.com/cyverse-de/p/go/qms v0.1.13 + github.com/cyverse-de/go-mod/gotelnats v0.0.15 + github.com/cyverse-de/go-mod/logging v0.0.3 + github.com/cyverse-de/go-mod/otelutils v0.0.5 + github.com/cyverse-de/go-mod/pbinit v0.1.13 + github.com/cyverse-de/go-mod/protobufjson v0.0.7 + github.com/cyverse-de/go-mod/subjects v0.1.5 + github.com/cyverse-de/p/go/qms v0.1.15 github.com/cyverse-de/p/go/requests v0.0.3 github.com/cyverse-de/p/go/svcerror v0.0.8 github.com/doug-martin/goqu/v9 v9.19.0 @@ -18,29 +18,31 @@ require ( github.com/knadh/koanf v1.5.0 github.com/labstack/echo/v4 v4.12.0 github.com/lib/pq v1.10.9 - github.com/nats-io/nats.go v1.34.1 + github.com/nats-io/nats.go v1.37.0 github.com/pkg/errors v0.9.1 - github.com/samber/lo v1.39.0 + github.com/samber/lo v1.47.0 github.com/sirupsen/logrus v1.9.3 - github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 - github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.2.4 - go.opentelemetry.io/otel v1.26.0 - google.golang.org/protobuf v1.34.1 + github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 + github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2 + go.opentelemetry.io/otel v1.31.0 + google.golang.org/protobuf v1.35.2 ) require ( - github.com/cyverse-de/p v0.0.0-20240228001927-426a6bd80191 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cyverse-de/p v0.0.0-20241022195522-7109f3ff6072 // indirect github.com/cyverse-de/p/go/analysis v0.0.16 // indirect github.com/cyverse-de/p/go/containers v0.0.2 // indirect github.com/cyverse-de/p/go/header v0.0.4 // indirect github.com/cyverse-de/p/go/monitoring v0.0.5 // indirect github.com/cyverse-de/p/go/user v0.0.11 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/joho/godotenv v1.5.1 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -50,16 +52,21 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect - go.opentelemetry.io/otel/metric v1.26.0 // indirect - go.opentelemetry.io/otel/sdk v1.26.0 // indirect - go.opentelemetry.io/otel/trace v1.26.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/grpc v1.67.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8480946..d14543e 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -38,20 +40,20 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cyverse-de/go-mod/cfg v0.0.2 h1:evHNKqLwOPWHhxxzF498/Rtac7LZb1zxnHAjZSuqiEo= github.com/cyverse-de/go-mod/cfg v0.0.2/go.mod h1:jjn1fZJRwqKiYgiS5AcXg9Dzxp2QOiLyrWVWCcq9Dw0= -github.com/cyverse-de/go-mod/gotelnats v0.0.11 h1:jpnnGrCUnBq1oUow6vujXaW3oiqusViqFUGpJD4njB0= -github.com/cyverse-de/go-mod/gotelnats v0.0.11/go.mod h1:UDIqvtSCeOKvjV+gZ+DrcPoTHPYkQjIuFxw7IlcbAI0= -github.com/cyverse-de/go-mod/logging v0.0.2 h1:MhmyAXl1UgxwXUgP2C5/jl7QAsF+Bs+r7Cs0SSZYLlI= -github.com/cyverse-de/go-mod/logging v0.0.2/go.mod h1:KkvW+/cMUdkfsJAD3uqMxHEb5naX8bwnO6z8znqHILQ= -github.com/cyverse-de/go-mod/otelutils v0.0.3 h1:4DAxvkZhf7YRUJeHaCyodbWv7t+tLzFchw6B4GrDsZ0= -github.com/cyverse-de/go-mod/otelutils v0.0.3/go.mod h1:h75ZnGtwiwGwHgqpxFQL0912bnHMrcOKfyGAv2+q2Vw= -github.com/cyverse-de/go-mod/pbinit v0.1.11 h1:HUGZ5Q0yOwkp8vnGjJzmKX7FweX96f68IrZe/lfpKT0= -github.com/cyverse-de/go-mod/pbinit v0.1.11/go.mod h1:eC6iC5kAxbWvJgZcqK04qeQbioX6KYwBoBw/SzsuPak= -github.com/cyverse-de/go-mod/protobufjson v0.0.3 h1:XTIZejY7EUbpF6ZdhRhiFX9PGYDkGGmPP760cDswGWM= -github.com/cyverse-de/go-mod/protobufjson v0.0.3/go.mod h1:p/ASemjpl2GEFYb3Tt7N8RozViwonsK0fOm0LxYeCt8= -github.com/cyverse-de/go-mod/subjects v0.1.4 h1:w2LSkQAQLjK054QW4c155HgcJww8nn3nwBknzlWU0BM= -github.com/cyverse-de/go-mod/subjects v0.1.4/go.mod h1:x8P+TNVSXvehkWMAmrg1RQHc5CCGafqtTKUn0hklkFA= -github.com/cyverse-de/p v0.0.0-20240228001927-426a6bd80191 h1:f0tJEoxQ9t5X9JhH6P160uQt+j1K0LqAwOS7Uy5qzgQ= -github.com/cyverse-de/p v0.0.0-20240228001927-426a6bd80191/go.mod h1:JM04cBlOajBkOVfQOEbBD6QwmWyupji9J84wfUA+LTc= +github.com/cyverse-de/go-mod/gotelnats v0.0.15 h1:1PzSmKGI0nfUY2JbzgymB51pBaOJtMhiS7oH+ooJ00w= +github.com/cyverse-de/go-mod/gotelnats v0.0.15/go.mod h1:Ksb6NSA9P/O0NklOqqt6ldAFoY1IySQb2nd7Ql6PDvs= +github.com/cyverse-de/go-mod/logging v0.0.3 h1:sbehQImEP5cYusWVN1sDvo3lqwEfaZSCZLmrhFaI8xI= +github.com/cyverse-de/go-mod/logging v0.0.3/go.mod h1:KRRtiAvsBZlnR9qxXLFYaFdvJAJZKGYAIhRnjuORpIQ= +github.com/cyverse-de/go-mod/otelutils v0.0.5 h1:PpVRfMO/zlzHMOTU6bzVQYvgUTjtCux8iRCSJhSzL2I= +github.com/cyverse-de/go-mod/otelutils v0.0.5/go.mod h1:ImgwzJFUE3EObZGtbsxCbEXVQCk43IP7Q2qs1H847PU= +github.com/cyverse-de/go-mod/pbinit v0.1.13 h1:WHjuxxKYYzBNINLOVxizIhihdf19S9f9DBiAwQTDPsw= +github.com/cyverse-de/go-mod/pbinit v0.1.13/go.mod h1:qtI+qGruDxvm3YwE6Nm6CAYKP+lAlZ4QhTiYUMGHA3w= +github.com/cyverse-de/go-mod/protobufjson v0.0.7 h1:0R4li2YHyhGms4szDzjT9AC3QALPR/Iwq/99IeMlXDg= +github.com/cyverse-de/go-mod/protobufjson v0.0.7/go.mod h1:G1H075/v5AjaXpBzCXPhdlclwGygE8zPDHXmSJ4EVu8= +github.com/cyverse-de/go-mod/subjects v0.1.5 h1:wuThkSyC+XbngIAHVD3+WecSz7ytGRyQBzEBzzr0agU= +github.com/cyverse-de/go-mod/subjects v0.1.5/go.mod h1:XjBoa1k2CovbxjNqQFP69eiXEstiV4bqxZWSBLizGjA= +github.com/cyverse-de/p v0.0.0-20241022195522-7109f3ff6072 h1:jLG5JQ9UUUV553CEgccOO+opsaAJkZr/ezPMFz3sRRQ= +github.com/cyverse-de/p v0.0.0-20241022195522-7109f3ff6072/go.mod h1:JM04cBlOajBkOVfQOEbBD6QwmWyupji9J84wfUA+LTc= github.com/cyverse-de/p/go/analysis v0.0.16 h1:E+SE/v3RE1znwnIzk4ugXiM0knVsBnTLmuv4fcvietc= github.com/cyverse-de/p/go/analysis v0.0.16/go.mod h1:SJi4jTyxhqOe2waLcHKbDIN6+CZdV6WAJI2JWIrpFIQ= github.com/cyverse-de/p/go/containers v0.0.2 h1:PIpw5KHC6A85KeloHms86zG5ADVAQXjsP05n8KEWeXE= @@ -60,8 +62,8 @@ github.com/cyverse-de/p/go/header v0.0.4 h1:asY/uixi9rCqbDLq2G21CT7WJRaendBfr1z2 github.com/cyverse-de/p/go/header v0.0.4/go.mod h1:1j79PqvJyMRmXXKyW4KbZWGQNontm0B9smsaq5t5Ct8= github.com/cyverse-de/p/go/monitoring v0.0.5 h1:n+GdL9a3R1zM7L+18SIufrtmLxXU8L/cKX6DhOJ7IhY= github.com/cyverse-de/p/go/monitoring v0.0.5/go.mod h1:CB0aMpTRJvCFlGeL/75uX4Snj/OXj9wzKF5KQX4tASg= -github.com/cyverse-de/p/go/qms v0.1.13 h1:4pcBCETTKwCYo9L3zfbQy92TXbaH/wv/VVyc3LGMl9E= -github.com/cyverse-de/p/go/qms v0.1.13/go.mod h1:Uv07x99AFaFmFwIwa3xTrN6bRxr76J7wK6R3Fgt/UG4= +github.com/cyverse-de/p/go/qms v0.1.15 h1:xP5D18L1zcgJsUo3OKp85ZokVrHD7IqgymKtyts2XJw= +github.com/cyverse-de/p/go/qms v0.1.15/go.mod h1:VCrtS3TdrM2H85JGT8A+/cdq2AVQXuNBHR6fyNFn0mg= github.com/cyverse-de/p/go/requests v0.0.3 h1:aFe6XxKPNib0nJSfS8QUPSQc8EnvCMv2dWUqUDEUnBU= github.com/cyverse-de/p/go/requests v0.0.3/go.mod h1:oS0KhADCAS7trkpQqcperoIl4+Bge9xMPqbOSYzO+bc= github.com/cyverse-de/p/go/svcerror v0.0.8 h1:UE06rE31kk7OcciTQlZfrJTFlC9AMTfNf6JKll41u0U= @@ -84,8 +86,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -95,8 +97,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -143,6 +145,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -194,19 +198,21 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -257,8 +263,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4= -github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= +github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -295,11 +301,13 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= -github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -310,6 +318,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -317,12 +326,14 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 h1:x3omFAG2XkvWFg1hvXRinY2ExAL1Aacl7W9ZlYjo6gc= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4/go.mod h1:qMKJr5fTnY0p7hqCQMNrAk62bCARWR5rAbTrGUFRuh4= -github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.2.4 h1:Pt/+CUTRusJb471SBXwkRCz+9pbOjNr80M6LlwqV07w= -github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.2.4/go.mod h1:kQgNoghy4K/wguxbOd/u0OJw/Y0maNPc7PF4JpEGeUc= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= +github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2 h1:zA9ZXfdtowo0EKt+t7uqXNlHxPeygrxuFSIroiBVgPU= +github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2/go.mod h1:ySXmuW9JLCm/TjsQksuMY/7MNiWqfHnhH2xeT34uOLU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -333,17 +344,23 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= -go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= -go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= -go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -353,11 +370,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -383,8 +398,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -430,8 +445,8 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -439,8 +454,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -465,6 +480,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -473,6 +492,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -484,13 +505,14 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 97a5b48..47f0eda 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ func main() { shutdown := otelutils.TracerProviderFromEnv(tracerCtx, serviceName, func(e error) { log.Fatal(e) }) defer shutdown() + //nolint:staticcheck nats.RegisterEncoder("protojson", protobufjson.NewCodec(protobufjson.WithEmitUnpopulated())) config, err = cfg.Init(&cfg.Settings{ @@ -145,6 +146,7 @@ func main() { a := app.New(natsClient, dbconn, userSuffix) + //nolint:staticcheck natsHandlers := map[string]nats.Handler{ qmssubs.GetUserUpdates: a.GetUserUpdatesHandler, qmssubs.AddUserUpdate: a.AddUserUpdateHandler, diff --git a/natscl/natscl.go b/natscl/natscl.go index 84a8110..a1f18de 100644 --- a/natscl/natscl.go +++ b/natscl/natscl.go @@ -82,6 +82,7 @@ func (s *ConnectionSettings) toConnectOptions() []nats.Option { return options } +//nolint:staticcheck func NewConnection(settings *ConnectionSettings) (*nats.EncodedConn, error) { log := log.WithFields(logrus.Fields{"context": "new nats conn"}) @@ -100,12 +101,14 @@ func NewConnection(settings *ConnectionSettings) (*nats.EncodedConn, error) { return encConn, nil } +//nolint:staticcheck type Client struct { conn *nats.EncodedConn subscriptions []*nats.Subscription queueSuffix string } +//nolint:staticcheck func NewClient(conn *nats.EncodedConn, queueSuffix string) *Client { return &Client{ conn: conn, @@ -118,6 +121,7 @@ func (c *Client) queueName(base string) string { return strings.Join([]string{base, c.queueSuffix}, ".") } +//nolint:staticcheck func (c *Client) Subscribe(subject string, handler nats.Handler) error { queue := c.queueName(subject)