diff --git a/.vscode/default.code-workspace b/.vscode/default.code-workspace new file mode 100644 index 000000000..cbd0bbb24 --- /dev/null +++ b/.vscode/default.code-workspace @@ -0,0 +1,12 @@ +{ + "folders": [ + { + "name": "root", + "path": "../" + }, + { + "name": "ci", + "path": "../ci" + } + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index e1bda36ed..1a6c256cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,18 +1,16 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch Server", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceFolder}/cmd/server", - "args": [ - "--config", "${workspaceFolder}/config.example.yaml", - ] - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/server", + "args": ["--config", "${workspaceFolder}/config.example.yaml"] + } + ] +} diff --git a/api/api.gen.go b/api/api.gen.go index 0d2847ee3..4e73e1e46 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -30,6 +30,28 @@ const ( PortalTokenAuthScopes = "PortalTokenAuth.Scopes" ) +// Defines values for LedgerEntryType. +const ( + GRANT LedgerEntryType = "GRANT" + GRANTUSAGE LedgerEntryType = "GRANT_USAGE" + RESET LedgerEntryType = "RESET" + VOID LedgerEntryType = "VOID" +) + +// Defines values for LedgerGrantExpirationPeriodDuration. +const ( + DAY LedgerGrantExpirationPeriodDuration = "DAY" + HOUR LedgerGrantExpirationPeriodDuration = "HOUR" + MONTH LedgerGrantExpirationPeriodDuration = "MONTH" + WEEK LedgerGrantExpirationPeriodDuration = "WEEK" + YEAR LedgerGrantExpirationPeriodDuration = "YEAR" +) + +// Defines values for LedgerGrantType. +const ( + LedgerGrantTypeUsage LedgerGrantType = "USAGE" +) + // Defines values for ListFeaturesParamsOrderBy. const ( ListFeaturesParamsOrderByCreatedAt ListFeaturesParamsOrderBy = "createdAt" @@ -44,22 +66,120 @@ const ( ListLedgersParamsOrderBySubject ListLedgersParamsOrderBy = "subject" ) +// CreateFeatureRequest A feature is a feature or service offered to a customer. +// For example: CPU-Hours, Tokens, API Calls, etc. +type CreateFeatureRequest struct { + // Archived If the feature is archived, it will not be used for grants or usage. + Archived *bool `json:"archived,omitempty"` + + // MeterGroupByFilters Optional meter group by filters. Useful if the meter scope is broader than what feature tracks. + MeterGroupByFilters *map[string]string `json:"meterGroupByFilters,omitempty"` + + // MeterSlug The meter that the feature is associated with and decreases grants by usage. + MeterSlug string `json:"meterSlug"` + + // Name The name of the feature. + Name string `json:"name"` +} + // CreateLedger A ledger represented in open meter. A ledger must be assigned to a single // subject. type CreateLedger = credit.Ledger // CreateLedgerGrantRequest Grants are used to increase balance of specific subjects. -type CreateLedgerGrantRequest = credit.Grant +type CreateLedgerGrantRequest struct { + // Amount The amount to grant. Can be positive or negative number. + Amount float64 `json:"amount"` + + // CreatedAt The time the grant was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EffectiveAt The effective date. + EffectiveAt time.Time `json:"effectiveAt"` + Expiration *LedgerGrantExpirationPeriod `json:"expiration,omitempty"` + + // FeatureID The unique feature ULID that the grant is associated with, if any. + FeatureID string `json:"featureID"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // ParentId The parent grant ULID that the grant is associated with, if any. + ParentId *string `json:"parentId,omitempty"` + + // Priority The priority of the grant. Grants with higher priority are applied first. + // Priority is a positive decimal numbers. With lower numbers indicating higher importance. + // For example, a priority of 1 is more urgent than a priority of 2. + // When there are several grants available for the same subject, the system selects the grant with the highest priority. + // In cases where grants share the same priority level, the grant closest to its expiration will be used first. + // In the case of two grants have identical priorities and expiration dates, the system will use the grant that was created first. + Priority *int `json:"priority,omitempty"` + + // Rollover Grant rollover configuration. + Rollover *LedgerGrantRollover `json:"rollover,omitempty"` + + // Type The grant type: + // - `USAGE` - Increase balance by the amount in the unit of the associated meter. + Type LedgerGrantType `json:"type"` + + // UpdatedAt The time the grant was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` +} // Event CloudEvents Specification JSON Schema type Event = event.Event -// Feature A feature is a feature or service offered to a customer. -// For example: CPU-Hours, Tokens, API Calls, etc. -type Feature = credit.Feature +// Feature defines model for Feature. +type Feature struct { + // Archived If the feature is archived, it will not be used for grants or usage. + Archived *bool `json:"archived,omitempty"` + + // CreatedAt The time the feature was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // Id Readonly unique ULID identifier of the feature. + Id *string `json:"id,omitempty"` + + // MeterGroupByFilters Optional meter group by filters. Useful if the meter scope is broader than what feature tracks. + MeterGroupByFilters *map[string]string `json:"meterGroupByFilters,omitempty"` + + // MeterSlug The meter that the feature is associated with and decreases grants by usage. + MeterSlug string `json:"meterSlug"` + + // Name The name of the feature. + Name string `json:"name"` + + // UpdatedAt The time the feature was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` +} // FeatureBalance defines model for FeatureBalance. -type FeatureBalance = credit.Feature +type FeatureBalance struct { + // Archived If the feature is archived, it will not be used for grants or usage. + Archived *bool `json:"archived,omitempty"` + + // Balance The balance of the feature. + Balance float64 `json:"balance"` + + // CreatedAt The time the feature was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // Id Readonly unique ULID identifier of the feature. + Id *string `json:"id,omitempty"` + + // MeterGroupByFilters Optional meter group by filters. Useful if the meter scope is broader than what feature tracks. + MeterGroupByFilters *map[string]string `json:"meterGroupByFilters,omitempty"` + + // MeterSlug The meter that the feature is associated with and decreases grants by usage. + MeterSlug string `json:"meterSlug"` + + // Name The name of the feature. + Name string `json:"name"` + + // UpdatedAt The time the feature was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Usage The usage of the feature. + Usage float64 `json:"usage"` +} // IdOrSlug A unique identifier. type IdOrSlug = string @@ -78,22 +198,157 @@ type Ledger = credit.Ledger type LedgerAlreadyExistsProblem = credit.LedgerAlreadyExistsProblemResponse // LedgerBalance Balance of a subject. -type LedgerBalance = credit.Balance +type LedgerBalance struct { + // FeatureBalances Features with balances. + FeatureBalances []FeatureBalance `json:"featureBalances"` + + // GrantBalances The grants applied to the subject. + GrantBalances []LedgerGrantBalance `json:"grantBalances"` + + // LastReset The last reset of the ledger. + LastReset *time.Time `json:"lastReset,omitempty"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // Subject The subject of the ledger. + Subject string `json:"subject"` +} // LedgerEntry A ledger entry. -type LedgerEntry = credit.LedgerEntry +type LedgerEntry struct { + // Amount The amount to apply. Can be positive or negative number. If applicable. + Amount float64 `json:"amount"` + + // FeatureID The unique feature ULID that the entry is associated with, if any. + FeatureID string `json:"featureID"` + + // Id Readonly unique ULID identifier of the ledger entry. + Id *string `json:"id,omitempty"` + + // Period A time period + Period *Period `json:"period,omitempty"` + + // Time The time the ledger entry was created. + Time time.Time `json:"time"` + Type LedgerEntryType `json:"type"` +} // LedgerEntryType defines model for LedgerEntryType. -type LedgerEntryType = credit.LedgerEntryType +type LedgerEntryType string // LedgerGrantBalance defines model for LedgerGrantBalance. -type LedgerGrantBalance = credit.Grant +type LedgerGrantBalance struct { + // Amount The amount to grant. Can be positive or negative number. + Amount float64 `json:"amount"` + + // Balance The balance of the grant. + Balance float64 `json:"balance"` + + // CreatedAt The time the grant was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EffectiveAt The effective date. + EffectiveAt time.Time `json:"effectiveAt"` + Expiration *LedgerGrantExpirationPeriod `json:"expiration,omitempty"` + + // ExpiresAt The expiration date of the grant. + ExpiresAt *time.Time `json:"expiresAt,omitempty"` + + // FeatureID The unique feature ULID that the grant is associated with, if any. + FeatureID string `json:"featureID"` + + // Id Readonly unique ULID identifier of the grant. + Id *string `json:"id,omitempty"` + + // LedgerID The ledger ID. + LedgerID string `json:"ledgerID"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // ParentId The parent grant ULID that the grant is associated with, if any. + ParentId *string `json:"parentId,omitempty"` + + // Priority The priority of the grant. Grants with higher priority are applied first. + // Priority is a positive decimal numbers. With lower numbers indicating higher importance. + // For example, a priority of 1 is more urgent than a priority of 2. + // When there are several grants available for the same subject, the system selects the grant with the highest priority. + // In cases where grants share the same priority level, the grant closest to its expiration will be used first. + // In the case of two grants have identical priorities and expiration dates, the system will use the grant that was created first. + Priority *int `json:"priority,omitempty"` + + // Rollover Grant rollover configuration. + Rollover *LedgerGrantRollover `json:"rollover,omitempty"` + + // Type The grant type: + // - `USAGE` - Increase balance by the amount in the unit of the associated meter. + Type LedgerGrantType `json:"type"` + + // UpdatedAt The time the grant was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Void If the grant is voided, it will not be applied to the subject's balance anymore. + Void *bool `json:"void,omitempty"` +} // LedgerGrantExpirationPeriod Expiration period of a ledger grant. -type LedgerGrantExpirationPeriod = credit.ExpirationPeriod +type LedgerGrantExpirationPeriod struct { + // Count The expiration period count like 12 months. + Count int `json:"count"` + + // Duration The expiration period duration like month. + Duration LedgerGrantExpirationPeriodDuration `json:"duration"` +} + +// LedgerGrantExpirationPeriodDuration The expiration period duration like month. +type LedgerGrantExpirationPeriodDuration string // LedgerGrantResponse defines model for LedgerGrantResponse. -type LedgerGrantResponse = credit.Grant +type LedgerGrantResponse struct { + // Amount The amount to grant. Can be positive or negative number. + Amount float64 `json:"amount"` + + // CreatedAt The time the grant was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EffectiveAt The effective date. + EffectiveAt time.Time `json:"effectiveAt"` + Expiration *LedgerGrantExpirationPeriod `json:"expiration,omitempty"` + + // ExpiresAt The expiration date of the grant. + ExpiresAt *time.Time `json:"expiresAt,omitempty"` + + // FeatureID The unique feature ULID that the grant is associated with, if any. + FeatureID string `json:"featureID"` + + // Id Readonly unique ULID identifier of the grant. + Id *string `json:"id,omitempty"` + + // LedgerID The ledger ID. + LedgerID string `json:"ledgerID"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // ParentId The parent grant ULID that the grant is associated with, if any. + ParentId *string `json:"parentId,omitempty"` + + // Priority The priority of the grant. Grants with higher priority are applied first. + // Priority is a positive decimal numbers. With lower numbers indicating higher importance. + // For example, a priority of 1 is more urgent than a priority of 2. + // When there are several grants available for the same subject, the system selects the grant with the highest priority. + // In cases where grants share the same priority level, the grant closest to its expiration will be used first. + // In the case of two grants have identical priorities and expiration dates, the system will use the grant that was created first. + Priority *int `json:"priority,omitempty"` + + // Rollover Grant rollover configuration. + Rollover *LedgerGrantRollover `json:"rollover,omitempty"` + + // Type The grant type: + // - `USAGE` - Increase balance by the amount in the unit of the associated meter. + Type LedgerGrantType `json:"type"` + + // UpdatedAt The time the grant was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Void If the grant is voided, it will not be applied to the subject's balance anymore. + Void *bool `json:"void,omitempty"` +} // LedgerGrantRollover Grant rollover configuration. type LedgerGrantRollover = credit.GrantRollover @@ -105,7 +360,7 @@ type LedgerGrantRolloverType = credit.GrantRolloverType // LedgerGrantType The grant type: // - `USAGE` - Increase balance by the amount in the unit of the associated meter. -type LedgerGrantType = credit.GrantType +type LedgerGrantType string // LedgerReset Ledger reset configuration. type LedgerReset = credit.Reset @@ -130,7 +385,13 @@ type MeterQueryResult struct { type MeterQueryRow = models.MeterQueryRow // Period A time period -type Period = credit.Period +type Period struct { + // From Period start time where the amount was applied. If applicable. + From time.Time `json:"from"` + + // To Period end time where the amount was applied. If applicable. + To time.Time `json:"to"` +} // PortalToken A consumer portal token. type PortalToken struct { @@ -173,6 +434,9 @@ type LedgerGrantID = credit.GrantID // LedgerID defines model for ledgerID. type LedgerID = credit.LedgerID +// LedgerIncludeVoids defines model for ledgerIncludeVoids. +type LedgerIncludeVoids = bool + // LedgerQueryLimit defines model for ledgerQueryLimit. type LedgerQueryLimit = int @@ -299,6 +563,9 @@ type ListLedgerGrantsParams struct { // Limit Number of entries to return Limit *LedgerQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // IncludeVoids Include void entries in the response. + IncludeVoids *LedgerIncludeVoids `form:"includeVoids,omitempty" json:"includeVoids,omitempty"` } // GetLedgerBalanceParams defines parameters for GetLedgerBalance. @@ -311,6 +578,9 @@ type GetLedgerBalanceParams struct { type ListLedgerGrantsByLedgerParams struct { // Limit Number of entries to return Limit *LedgerQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // IncludeVoids Include void entries in the response. + IncludeVoids *LedgerIncludeVoids `form:"includeVoids,omitempty" json:"includeVoids,omitempty"` } // GetLedgerHistoryParams defines parameters for GetLedgerHistory. @@ -404,7 +674,7 @@ type IngestEventsApplicationCloudeventsPlusJSONRequestBody = Event type IngestEventsApplicationCloudeventsBatchPlusJSONRequestBody = IngestEventsApplicationCloudeventsBatchPlusJSONBody // CreateFeatureJSONRequestBody defines body for CreateFeature for application/json ContentType. -type CreateFeatureJSONRequestBody = Feature +type CreateFeatureJSONRequestBody = CreateFeatureRequest // CreateLedgerJSONRequestBody defines body for CreateLedger for application/json ContentType. type CreateLedgerJSONRequestBody = CreateLedger @@ -1028,6 +1298,14 @@ func (siw *ServerInterfaceWrapper) ListLedgerGrants(w http.ResponseWriter, r *ht return } + // ------------- Optional query parameter "includeVoids" ------------- + + err = runtime.BindQueryParameter("form", true, false, "includeVoids", r.URL.Query(), ¶ms.IncludeVoids) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "includeVoids", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListLedgerGrants(w, r, params) })) @@ -1110,6 +1388,14 @@ func (siw *ServerInterfaceWrapper) ListLedgerGrantsByLedger(w http.ResponseWrite return } + // ------------- Optional query parameter "includeVoids" ------------- + + err = runtime.BindQueryParameter("form", true, false, "includeVoids", r.URL.Query(), ¶ms.IncludeVoids) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "includeVoids", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListLedgerGrantsByLedger(w, r, ledgerID, params) })) @@ -2007,152 +2293,154 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/client/go/client.gen.go b/api/client/go/client.gen.go index 24fc62c63..7a68f7d63 100644 --- a/api/client/go/client.gen.go +++ b/api/client/go/client.gen.go @@ -31,6 +31,28 @@ const ( PortalTokenAuthScopes = "PortalTokenAuth.Scopes" ) +// Defines values for LedgerEntryType. +const ( + GRANT LedgerEntryType = "GRANT" + GRANTUSAGE LedgerEntryType = "GRANT_USAGE" + RESET LedgerEntryType = "RESET" + VOID LedgerEntryType = "VOID" +) + +// Defines values for LedgerGrantExpirationPeriodDuration. +const ( + DAY LedgerGrantExpirationPeriodDuration = "DAY" + HOUR LedgerGrantExpirationPeriodDuration = "HOUR" + MONTH LedgerGrantExpirationPeriodDuration = "MONTH" + WEEK LedgerGrantExpirationPeriodDuration = "WEEK" + YEAR LedgerGrantExpirationPeriodDuration = "YEAR" +) + +// Defines values for LedgerGrantType. +const ( + LedgerGrantTypeUsage LedgerGrantType = "USAGE" +) + // Defines values for ListFeaturesParamsOrderBy. const ( ListFeaturesParamsOrderByCreatedAt ListFeaturesParamsOrderBy = "createdAt" @@ -45,22 +67,120 @@ const ( ListLedgersParamsOrderBySubject ListLedgersParamsOrderBy = "subject" ) +// CreateFeatureRequest A feature is a feature or service offered to a customer. +// For example: CPU-Hours, Tokens, API Calls, etc. +type CreateFeatureRequest struct { + // Archived If the feature is archived, it will not be used for grants or usage. + Archived *bool `json:"archived,omitempty"` + + // MeterGroupByFilters Optional meter group by filters. Useful if the meter scope is broader than what feature tracks. + MeterGroupByFilters *map[string]string `json:"meterGroupByFilters,omitempty"` + + // MeterSlug The meter that the feature is associated with and decreases grants by usage. + MeterSlug string `json:"meterSlug"` + + // Name The name of the feature. + Name string `json:"name"` +} + // CreateLedger A ledger represented in open meter. A ledger must be assigned to a single // subject. type CreateLedger = credit.Ledger // CreateLedgerGrantRequest Grants are used to increase balance of specific subjects. -type CreateLedgerGrantRequest = credit.Grant +type CreateLedgerGrantRequest struct { + // Amount The amount to grant. Can be positive or negative number. + Amount float64 `json:"amount"` + + // CreatedAt The time the grant was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EffectiveAt The effective date. + EffectiveAt time.Time `json:"effectiveAt"` + Expiration *LedgerGrantExpirationPeriod `json:"expiration,omitempty"` + + // FeatureID The unique feature ULID that the grant is associated with, if any. + FeatureID string `json:"featureID"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // ParentId The parent grant ULID that the grant is associated with, if any. + ParentId *string `json:"parentId,omitempty"` + + // Priority The priority of the grant. Grants with higher priority are applied first. + // Priority is a positive decimal numbers. With lower numbers indicating higher importance. + // For example, a priority of 1 is more urgent than a priority of 2. + // When there are several grants available for the same subject, the system selects the grant with the highest priority. + // In cases where grants share the same priority level, the grant closest to its expiration will be used first. + // In the case of two grants have identical priorities and expiration dates, the system will use the grant that was created first. + Priority *int `json:"priority,omitempty"` + + // Rollover Grant rollover configuration. + Rollover *LedgerGrantRollover `json:"rollover,omitempty"` + + // Type The grant type: + // - `USAGE` - Increase balance by the amount in the unit of the associated meter. + Type LedgerGrantType `json:"type"` + + // UpdatedAt The time the grant was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` +} // Event CloudEvents Specification JSON Schema type Event = event.Event -// Feature A feature is a feature or service offered to a customer. -// For example: CPU-Hours, Tokens, API Calls, etc. -type Feature = credit.Feature +// Feature defines model for Feature. +type Feature struct { + // Archived If the feature is archived, it will not be used for grants or usage. + Archived *bool `json:"archived,omitempty"` + + // CreatedAt The time the feature was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // Id Readonly unique ULID identifier of the feature. + Id *string `json:"id,omitempty"` + + // MeterGroupByFilters Optional meter group by filters. Useful if the meter scope is broader than what feature tracks. + MeterGroupByFilters *map[string]string `json:"meterGroupByFilters,omitempty"` + + // MeterSlug The meter that the feature is associated with and decreases grants by usage. + MeterSlug string `json:"meterSlug"` + + // Name The name of the feature. + Name string `json:"name"` + + // UpdatedAt The time the feature was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` +} // FeatureBalance defines model for FeatureBalance. -type FeatureBalance = credit.Feature +type FeatureBalance struct { + // Archived If the feature is archived, it will not be used for grants or usage. + Archived *bool `json:"archived,omitempty"` + + // Balance The balance of the feature. + Balance float64 `json:"balance"` + + // CreatedAt The time the feature was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // Id Readonly unique ULID identifier of the feature. + Id *string `json:"id,omitempty"` + + // MeterGroupByFilters Optional meter group by filters. Useful if the meter scope is broader than what feature tracks. + MeterGroupByFilters *map[string]string `json:"meterGroupByFilters,omitempty"` + + // MeterSlug The meter that the feature is associated with and decreases grants by usage. + MeterSlug string `json:"meterSlug"` + + // Name The name of the feature. + Name string `json:"name"` + + // UpdatedAt The time the feature was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Usage The usage of the feature. + Usage float64 `json:"usage"` +} // IdOrSlug A unique identifier. type IdOrSlug = string @@ -79,22 +199,157 @@ type Ledger = credit.Ledger type LedgerAlreadyExistsProblem = credit.LedgerAlreadyExistsProblemResponse // LedgerBalance Balance of a subject. -type LedgerBalance = credit.Balance +type LedgerBalance struct { + // FeatureBalances Features with balances. + FeatureBalances []FeatureBalance `json:"featureBalances"` + + // GrantBalances The grants applied to the subject. + GrantBalances []LedgerGrantBalance `json:"grantBalances"` + + // LastReset The last reset of the ledger. + LastReset *time.Time `json:"lastReset,omitempty"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // Subject The subject of the ledger. + Subject string `json:"subject"` +} // LedgerEntry A ledger entry. -type LedgerEntry = credit.LedgerEntry +type LedgerEntry struct { + // Amount The amount to apply. Can be positive or negative number. If applicable. + Amount float64 `json:"amount"` + + // FeatureID The unique feature ULID that the entry is associated with, if any. + FeatureID string `json:"featureID"` + + // Id Readonly unique ULID identifier of the ledger entry. + Id *string `json:"id,omitempty"` + + // Period A time period + Period *Period `json:"period,omitempty"` + + // Time The time the ledger entry was created. + Time time.Time `json:"time"` + Type LedgerEntryType `json:"type"` +} // LedgerEntryType defines model for LedgerEntryType. -type LedgerEntryType = credit.LedgerEntryType +type LedgerEntryType string // LedgerGrantBalance defines model for LedgerGrantBalance. -type LedgerGrantBalance = credit.Grant +type LedgerGrantBalance struct { + // Amount The amount to grant. Can be positive or negative number. + Amount float64 `json:"amount"` + + // Balance The balance of the grant. + Balance float64 `json:"balance"` + + // CreatedAt The time the grant was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EffectiveAt The effective date. + EffectiveAt time.Time `json:"effectiveAt"` + Expiration *LedgerGrantExpirationPeriod `json:"expiration,omitempty"` + + // ExpiresAt The expiration date of the grant. + ExpiresAt *time.Time `json:"expiresAt,omitempty"` + + // FeatureID The unique feature ULID that the grant is associated with, if any. + FeatureID string `json:"featureID"` + + // Id Readonly unique ULID identifier of the grant. + Id *string `json:"id,omitempty"` + + // LedgerID The ledger ID. + LedgerID string `json:"ledgerID"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // ParentId The parent grant ULID that the grant is associated with, if any. + ParentId *string `json:"parentId,omitempty"` + + // Priority The priority of the grant. Grants with higher priority are applied first. + // Priority is a positive decimal numbers. With lower numbers indicating higher importance. + // For example, a priority of 1 is more urgent than a priority of 2. + // When there are several grants available for the same subject, the system selects the grant with the highest priority. + // In cases where grants share the same priority level, the grant closest to its expiration will be used first. + // In the case of two grants have identical priorities and expiration dates, the system will use the grant that was created first. + Priority *int `json:"priority,omitempty"` + + // Rollover Grant rollover configuration. + Rollover *LedgerGrantRollover `json:"rollover,omitempty"` + + // Type The grant type: + // - `USAGE` - Increase balance by the amount in the unit of the associated meter. + Type LedgerGrantType `json:"type"` + + // UpdatedAt The time the grant was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Void If the grant is voided, it will not be applied to the subject's balance anymore. + Void *bool `json:"void,omitempty"` +} // LedgerGrantExpirationPeriod Expiration period of a ledger grant. -type LedgerGrantExpirationPeriod = credit.ExpirationPeriod +type LedgerGrantExpirationPeriod struct { + // Count The expiration period count like 12 months. + Count int `json:"count"` + + // Duration The expiration period duration like month. + Duration LedgerGrantExpirationPeriodDuration `json:"duration"` +} + +// LedgerGrantExpirationPeriodDuration The expiration period duration like month. +type LedgerGrantExpirationPeriodDuration string // LedgerGrantResponse defines model for LedgerGrantResponse. -type LedgerGrantResponse = credit.Grant +type LedgerGrantResponse struct { + // Amount The amount to grant. Can be positive or negative number. + Amount float64 `json:"amount"` + + // CreatedAt The time the grant was created. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // EffectiveAt The effective date. + EffectiveAt time.Time `json:"effectiveAt"` + Expiration *LedgerGrantExpirationPeriod `json:"expiration,omitempty"` + + // ExpiresAt The expiration date of the grant. + ExpiresAt *time.Time `json:"expiresAt,omitempty"` + + // FeatureID The unique feature ULID that the grant is associated with, if any. + FeatureID string `json:"featureID"` + + // Id Readonly unique ULID identifier of the grant. + Id *string `json:"id,omitempty"` + + // LedgerID The ledger ID. + LedgerID string `json:"ledgerID"` + Metadata *map[string]string `json:"metadata,omitempty"` + + // ParentId The parent grant ULID that the grant is associated with, if any. + ParentId *string `json:"parentId,omitempty"` + + // Priority The priority of the grant. Grants with higher priority are applied first. + // Priority is a positive decimal numbers. With lower numbers indicating higher importance. + // For example, a priority of 1 is more urgent than a priority of 2. + // When there are several grants available for the same subject, the system selects the grant with the highest priority. + // In cases where grants share the same priority level, the grant closest to its expiration will be used first. + // In the case of two grants have identical priorities and expiration dates, the system will use the grant that was created first. + Priority *int `json:"priority,omitempty"` + + // Rollover Grant rollover configuration. + Rollover *LedgerGrantRollover `json:"rollover,omitempty"` + + // Type The grant type: + // - `USAGE` - Increase balance by the amount in the unit of the associated meter. + Type LedgerGrantType `json:"type"` + + // UpdatedAt The time the grant was last updated. + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Void If the grant is voided, it will not be applied to the subject's balance anymore. + Void *bool `json:"void,omitempty"` +} // LedgerGrantRollover Grant rollover configuration. type LedgerGrantRollover = credit.GrantRollover @@ -106,7 +361,7 @@ type LedgerGrantRolloverType = credit.GrantRolloverType // LedgerGrantType The grant type: // - `USAGE` - Increase balance by the amount in the unit of the associated meter. -type LedgerGrantType = credit.GrantType +type LedgerGrantType string // LedgerReset Ledger reset configuration. type LedgerReset = credit.Reset @@ -131,7 +386,13 @@ type MeterQueryResult struct { type MeterQueryRow = models.MeterQueryRow // Period A time period -type Period = credit.Period +type Period struct { + // From Period start time where the amount was applied. If applicable. + From time.Time `json:"from"` + + // To Period end time where the amount was applied. If applicable. + To time.Time `json:"to"` +} // PortalToken A consumer portal token. type PortalToken struct { @@ -174,6 +435,9 @@ type LedgerGrantID = credit.GrantID // LedgerID defines model for ledgerID. type LedgerID = credit.LedgerID +// LedgerIncludeVoids defines model for ledgerIncludeVoids. +type LedgerIncludeVoids = bool + // LedgerQueryLimit defines model for ledgerQueryLimit. type LedgerQueryLimit = int @@ -300,6 +564,9 @@ type ListLedgerGrantsParams struct { // Limit Number of entries to return Limit *LedgerQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // IncludeVoids Include void entries in the response. + IncludeVoids *LedgerIncludeVoids `form:"includeVoids,omitempty" json:"includeVoids,omitempty"` } // GetLedgerBalanceParams defines parameters for GetLedgerBalance. @@ -312,6 +579,9 @@ type GetLedgerBalanceParams struct { type ListLedgerGrantsByLedgerParams struct { // Limit Number of entries to return Limit *LedgerQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // IncludeVoids Include void entries in the response. + IncludeVoids *LedgerIncludeVoids `form:"includeVoids,omitempty" json:"includeVoids,omitempty"` } // GetLedgerHistoryParams defines parameters for GetLedgerHistory. @@ -405,7 +675,7 @@ type IngestEventsApplicationCloudeventsPlusJSONRequestBody = Event type IngestEventsApplicationCloudeventsBatchPlusJSONRequestBody = IngestEventsApplicationCloudeventsBatchPlusJSONBody // CreateFeatureJSONRequestBody defines body for CreateFeature for application/json ContentType. -type CreateFeatureJSONRequestBody = Feature +type CreateFeatureJSONRequestBody = CreateFeatureRequest // CreateLedgerJSONRequestBody defines body for CreateLedger for application/json ContentType. type CreateLedgerJSONRequestBody = CreateLedger @@ -1636,6 +1906,22 @@ func NewListLedgerGrantsRequest(server string, params *ListLedgerGrantsParams) ( } + if params.IncludeVoids != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "includeVoids", runtime.ParamLocationQuery, *params.IncludeVoids); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -1748,6 +2034,22 @@ func NewListLedgerGrantsByLedgerRequest(server string, ledgerID LedgerID, params } + if params.IncludeVoids != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "includeVoids", runtime.ParamLocationQuery, *params.IncludeVoids); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -5413,152 +5715,154 @@ func ParseGetSubjectResponse(rsp *http.Response) (*GetSubjectResponse, error) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9+3LbOJb3q6D4bdUkPdTVdhK7ampKcWxHndhxfEnSHftLQyQkoUMBCgHaVlz+Y99i", - "n2+f5CvcSJAEJcqWknyZbHXtxCIJHBwcHJwbfrj1AjqZUoIIZ97OrTeFMZwgjmL51xBBnsSo/0L8ESIW", - "xHjKMSXejtcDCcFfEgTOX/dfABwiwvEQoxgMaQwg0F82Pd/D4vUp5GPP9wicIG/Hatf3YvQlwTEKvR0e", - "J8j3WDBGEyg6RDdwMo3E++1O7+TPjaMXe6/OTt9tnpzs7799sn2wtd975/ken03FO4zHmIw837tpjGhD", - "/xjEKMS8uW/1lz5u4MmUxlyNmo+9HW+E+TgZNAM6adEpIpIPmGb/bmHCUUxg1FLtend3d74XoXCE4oMY", - "Ej6XUSUeqQ/BSHxZwah829+GWVlva2LVfbg0lz+1WRMkjNMJihs4rMeL11n7a2LG2wTFs9d4gnmZKUfJ", - "ZIBiQIcAER5jxACnIEY8iYlhxxfxucUP2ZA9+BANYRJxb6fTbrf9jBcd8dcE3uBJMjEPJ5joP1PmCMpH", - "KPYKBL8ZDhmqSzH7jKcV9FLVjpPgMrWGvLaTPMnqfvgmPo2SUX0J42ME5KcVIpZvdp6c/VeMht6O939a", - "mUptqaeslTYgKJVM2McRF0ubJtPnM/G5iz/D3Et2ZzAMsRgZjI5jOkUxx0iq7LxQ3/kFLpxiwVCg2pXj", - "H4nGwWDGwDXmY4BuYMDBBPJg3LwgF+ScwRHaAX/9O0fKR9HN5b8wmSb8Imm3u0/yjyc0RNHlv0ZT3tj8", - "60JIazqVt558KJaNeOpZczlNuKBY/00Hf6NA/sD4TC7fEKHpm/RXi4uvK/WKeo7JCEASpmMFkyTiWDCC", - "JbI9lh+rUSv/andefnjy7tXW7ub2s+cbved/bB+fdNpPto+PC6Pyqt+sWqqZaslm9TurJIulp4ox8zi6", - "iI//1j/+K1W7HSUrpd+7F1UKTb+aYxLmaOKWdf0DjGM4s1ZaTCflcZxyGHMQQo4aHE8QwASc7O+CjY2N", - "bbEuJpA3L0ifBFHC8BVqVlI4FK27t5tuu7vRaHca7c5Zu70j//vT8z3VupBn03lpM0ppt/RDnvz+EBDK", - "AZuiQOiyEEDAMBlFCMDRKEYjyBG4xlEEBkhvGSiU6x3BYGymSy4KOfprTEJ63bwgf+lHfwHMAAQxYii+", - "QtbSuYJRMocdI4euSjnyUa99PdxLf+m5PKNlVuyRcAXzyOmiWezeexbfS+6e4q9o8UT62UwmYh0tmk+x", - "f4kNLUZ8JjZe8XcmFVOx7CsmXk5VNUOuM6Lr7nLWOAtjP8MT9CcljvGfjZGSKSFwgnjRvRmInNGvlCAA", - "GQjREItRYyKf9XtHPSDaBaJh8AJyOIAMgUdjzqc7rdb19XUTQwKbNB61REMN0RB7LMShxHPR4PnZruxQ", - "9md4nTAULuJROjinHeOdn+3mtoreBMU4gK0jdP3pDxp/dsqNnihhN7xCs2UMZv1lhTlTaPfhdrPcOGLE", - "ppQwZYI8h+EJ+pIgxo9jOojQ5EQ/FQ8DSjgicl+B02mEAygG1JqqN//5NxOju7XNhRBxiIW9MEYwRDHY", - "VS00zmZTBMaQgYSgmykKOAq1IF3kmr6ZRBeemBoOecK8nU1hS3LM5ciewxBoYrORJTHZ0QTJ7XVnAMNG", - "rN+6q7sY9OAVg/KTZ/d653tqS+9FMYLhbO8GM85WwrmAkmGEA47JaI/wWFmZoTZW3h22T9u7h3/+fvq2", - "u3Gwffjqw8nb46eetKNhCLkcn5jjKTqGswkivC8+neJPm2/i3ufx66sZHmO6Pd3qjLcx3ifPvUxsM0Fr", - "dJQRqudQ+7xIDjI3KdvZpOiXoGJI9nLV9OgXas9MNb9dk6XeBntpJ0eU79OEhKuVbmmWSbU0FI3neLOZ", - "8eaIcrCvX6jiB6G8oRpZhbBmPaqx9wXpQh7Qijmgo0KSBzjrxOLEVruT50Q/99o8ftgNroor/Xyb5wQm", - "fExj/HXVnJlgJkwCQGOAyRWMcAg4/YxITkgs1tiUzOFLYr+2CqacFxo8TzXzavlhaXwUxzTOiUjb5kP6", - "3p5+r5oX5tUVcaJA4V3aqtwjd2MEOVKqxbW5axUYo6mwwIWECbNHuG86WAHSdyYJ48JSgYzhkbCPOE1N", - "yAtirAFpwkxzoQJb0dcOJ1jTsfTmUHbsq5zMMxOUEVJvbFY+xswMWphlYqA8hsFnFUJlwv5R4yybU5mV", - "8zHt9bJI0Rx/e+Xetp8TARlzNRZBiRsHangwRum4MQliJKzdAYwgCaTBrM3ZwHbGS5MOJzQhFRxXz0Tz", - "KiYNdiERkjWlDHN8hYT+IcIVEf8mMtTXzIdBZJxO80+9IKY5kCMNexXdSiNfGOCyV3ANGdBfFFpfyqEW", - "m/sbEs2MXVuW5OEQBWIoVXSlL0jv8iG0OFbRFMdQ9XXrwSh6M/R2PtaxWqQs7KWfH0v/zru79L3rGHOU", - "jfjOn5e+EePTDoTZdmUih48ht+YCM6FWaIDFbMgAoQ/wEEAya5biXz9qisb/ARTdNMY0xnyWj8j7jkkx", - "bxoPWK9ErQJkiHaMR2MUZ28KvSB3UBSCIY6ZUPbH5qGM4qQLOEQBnsBIL17WBO9FgxG9Fqan+g1gEsq9", - "mIxMT2oWhJppXpB9Ksx3yRtfNG3R2xG9TahQU/EICU0yhqTwTrd5Qd6PkXThBd0xAgxdoRhGRovDK4gj", - "OIhQGt5gcJJGGJWrzmaMowlgKBJ6zlYeYjziT0k642nfMggEAsgQA9eya90dGwsa0m5SWiN0hSLfajqI", - "KBMtCu3LGciWcC5WkM5AX0UpRI9yLq+p6XEMr4zbHsDI9IgRk1EZq12hQlhuwLKnhNnqUq5YS2emBOT0", - "spXr6W5tzU/1+F5Mo4heKcukpko6MZ+k4l/7U+HIi8+SabjkNhFBxoH+bI17RcF+kE99s5P6uWy2vank", - "1Hw9Y0PyYw0acO/KWNpO9TeEEUNFbbQb0SSUHzJwqk0LJZe/n745AqfKRM4pSqNkcxmeBk/iARW7gHBZ", - "mLfjdbobrryPDE1sBZ32EIao0Qm2UWMzfBI0nnWfbjWCrW6w8eTpRifcCIS5T5M4kHOE4iscoIYMb/me", - "sIGuUMzUEDrNtmfHJApxLDwpCkpnR/7XbLc7f2YUTmM6mWo9bu8Z8/cUJUoOs0KwFEzhLKIwbM5JjVUw", - "zrW/CEq0P2UWX2Ey1UMgnpqtRXykw/ngUDgRMJSKkVMZQe+2N5+YCLqgkgh18THnq0kf7dJedaWnUtW8", - "RmQkhLfjeySJpHKvNMsEVXYUNRcrN7FOpfLVa0oDysGoATDAadNe6kmMl6dDSOOC/uVM5mawrvjmaSn1", - "baR7Qf9yxm+48AyvxzgYA0i0dI3hdIoIyotXca3Y/GnEaIhiRAJUgzp7jTnD+eqhkTNbkbCcIlFUp6wU", - "OxvLk6xW8CKCqtzIF/KvgREX7UdqslSXOplgWJl7No1pmAQoBo/SIHsIBjOgpudxntK8bllAsVI9Jd7h", - "CWIcTqaCjGttJAEaBEkspyabVtd63djY2G5WboEFzebcBpdcIW5Nk+e50TeKoTGKpIkiHHcxshiPMFGm", - "ZjbK/Bi07l20J0um62WTl1Df7Nc13X61qNWGWXcjDoSEyw9Zi4WfGyPauuq25A+SUu3cLLkB91LXTBrx", - "5g8aA72QAR2KRasjPkYA8zb6Dtg9Pm+8pEnMfHAmNxIf9I77YBdGEfMB4oEzTBAHY3yFQme+UsydTZp+", - "1weYKwuVUJ7ZwzQ2hi+NVUqzYKrpwVcYYQNKIwTJMnEEQ9u3iiS4tooTBENKollVmSTNsXEpb3ohPdIO", - "1JUDqlyDPaBk6M1UfaQCclkhgKr4YU1wztAwiQAeZrVUgAV0KoVjEFOZtZOu4LXYqc3syNAda86vDnIZ", - "O7IHd5FXGjfMAhm2oOZDGdLdCpEKpDEjpIOZU0i1EfaJUw4jV0hH5VddBNkJ7Yr57vX12nS1XNcvssX+", - "+3hG2rDIJqie12PU4+r9Ht3ycxUkrR9rMxTd+bcFxTjImipPhRWMrZ5sV4y0LOaX8zhlxrN6hi1VP9mc", - "t0AoQZrRhcyIUISPzgkWGzSMohk4V+2+Rjc4oKMYTsc4kA9OacxlICi1e+PHBTV58GTrz6dbW739971X", - "L/c63aM/2rtvt/dfig0NcjE+b8f7vx/bje3e890Xe/sHL39/dXh0/Pbk9Ozd+w9//Hl5231y91+OVXdb", - "PbIJvDFm0pONotVk9wobX9uN7ct/Pvr3zqf0j8e/Obq7dCz7PhkhxlF4H9+9RwDWn2vTSyo8alS5zCEq", - "G1ympwpuKDJdLuPQL+HBh9/Pg89GrjKCpcS7qpVSplvRKkr5Mk937JlvS13ZMmXyuEgbmsspWvXVpWNz", - "zFKK9TRdLhFZVnd1TS6dlluRxbVKC0ufH1ihgVV2PS5d6rtmSjeJdXA3l7r8LhnJOXUxS0W58hUzfnVJ", - "ks6IZDVJRy9+P9na6O49Ozh7/u50t/vh1daLTa92WdEjnVtpVjf22C4r4ozL5W4iA1njvocJ42qnl8UC", - "uvxrJ6IBjFq/H76JAs5evXvWaIv/61SVMu3q8WYzCwc04TuDCJLPZQXjZM/iMLrNi/K+PU4mkDTEoOVm", - "im6mESRK+acpY+mRY2YHG/T60VUS+b1+QMNZVnigAsKpyJZXb8rKMnHnJ32Qxp6U1Y4LUT5DY03a6s1W", - "IThYjinp2XRpvZdnZ8dAvQACGiIwQgTF0q8YzKzIhvTT02MmtbkrCxRT+jDhG13Pyt9sbW9b+ZvNdtuV", - "wdHyV+Y3BGxMY+4XpYIlkwmMZwW6ZPwmz15nxeSioJCs1Qwo4RATBqCcdddcV3c7tyZz0XS6EziKR+lU", - "p0tombqQuUWb69LQz6t8kOeZ/5HVATuiO9oxcYi3djB0pln7M9pJT2v1a3hOhsRSIb/vKT/bvbJMClhn", - "s3WcMDeSWlRY2cVKSgpCkabzNH31pGB9npgaQlq3W2FNIPH8/nU+gtGzWnU+oD8EOrkziFCtsp8HG232", - "6FYVG1MHIhZW9umymspIvcP2lcSuzQKun1OXIqNy6q4gear8JmgpTackca1yfqbHaFKNBye9ozPP9969", - "kbn1k73TPfGn/PnT+WnvYC+ffDTv1z5Fl/W6rnHlVNB9Sr3SzeRhoShVRrS6QFRuXGtlXqnWrXz+K6uY", - "0ced5P6XO17v0JBBtYJEpRblyyDCnxHodMGEEj4uhq47XZchFiZZnV+djsz7qi/Zke5HL4qXb85PPN97", - "0fvD8733e3uvPN87fHN09tLzvT/2eifeZa0FUGTrC0PnGo6V2joozPpRE1BPB5WkYK0yZ5epLx9GyRXz", - "lpetnHPEeotlT2wI8xbwD5PFchL3sH16bml4WhFO0yI4y6opEpKPGS4X3MnOH1eFeb5hFZer3M5dLA5M", - "BZ9wuoZ4pBed8xQAvOlV2IqHyuW07EXTbC5WlVl8Sxb+mUE4jRVzQrhu1VzKkW/C9zO3mztGGedl6QOn", - "IGFo54I0wF8ne4e9/lH/6OBT7/DN+dHZX6ABTHsgRhOIiQQqkNxuyk/enPQP+ke91+4vGspPUq7zMIl0", - "CW3WgrVtFDv3fK/QeN6WKj6sDx2TY9FaJ6N6ErRWmE0166WxKLjXL56b0AEbLeK6GighOC0FspLG6uRN", - "jq0OK1T9VJ9fa+XTCXLitLw2sWiG+GIVsfCkhHKFqG7PirqDPgcBJLoeRHN3mLhS4N8rPyBpXm96IF8W", - "XE+nqYlbvVgciieugIJKg8lKo5xA6PJKefaegTG9FhMt8WFk/USGTqDqnwqZRPNYY26cH3qlxEhfnWVU", - "VRfia9HOWS5v56ewElYu8r+aOTAJ8QPXleRMZrCLeVtZ6qpzFzP1vqm5sLAOdrzD/tH52V45OJ8by/xN", - "TnK5Z71frKop89/624hmCktULhQB55pbC6v0LHbeVlYjm70qnc16RXi5eakqL8qaKc1YVcnREZygUGYV", - "jqHEJJrGiDFMicSTQjc8hoE5pmTDkjAwjOnEquAMIYdN8ArNWJqt0NpAyG5ACcOMA6klYDQdQ5JIbAb5", - "NCEhillAYwSCMRQ9ophV1GvPkcVShtalq2qBU9Wse1hsWs+t7yhlBipJUkWoD2XiN6iuKC394tgdklYU", - "NIUskYpXvrriH0zVn2t9MWtekDP7Jf0xjcHp+aEPeu8OfHDYP/Iliw57H4ClWpjSwUQjqUlEDzkOpYgD", - "HaqFMTOFn+lJzH0ag/Oj/tvzvU+7wmizm/XzZCuKsgye6qIJRBOlbzMGGBYKGvGI0Lgc5bS0amkarnM4", - "OEsgyeRcMoXDZmtjW8vlelmw18pFy5pqT3zYXjv9PGqp5rJttpffMByBeJvHmbvgWPra3FQb6K62yO25", - "9nyv9+7A88XuJf5/70PeKlVfzrNJbWb0csxdNV8khOAJYvIUpNOHks9UGE/ZJRJ3p+k6bfTx1mUcFAqV", - "ilVAVQVFUshVKE9J0R4JqyGgtKBxKFjiNmDvLn2FEFZp4HK6sIP5Zomp0cpQtn54jtTK4VmiQq9dicSh", - "xnVbVYaFrgz3a0WaTs6sq8grzxrHVh7Ta+Nt1VtLP7LAFAW+hs057zR3zfFVGNY1wSsKZ40U49VZuzmx", - "yYXGvGburTPrkMXhLMavWqLVTK0O5dCWdzW6fFf2YJbYzdPFsdLdqyoD1VMREJXCcdVbOBEoVWuASSBK", - "hcKoznNl8ahrmBZDzM/Ar0L1OalDKULkd6GtICEabJPTmlGUtSWLjmnMYSRdcZc4CKdIeEFAAidEKsJR", - "DI1EEb1G4aE5LMFk1Nt2hi5zR56q2KjySWFaCW7ll6pWvC64nONEVlZcqxF7dPJJDe1Tv0debBxP37/v", - "9rrv42eT7b+HX9HL6ODDs5vJ7ofrg+Zs68vmaaP3/st+8uTL30O4/7X99e2Xzb2v3WcnjMzeXf8+HH7Y", - "+nJzeEUdMZcyk24rzidJZBKDESm90TwUpoK1T8EadMv2nJTZXw1POsGkrx52CpaJ7ylHWj/WWCy5Qup1", - "IdoYSbitcZgul4a83x5RM6W4srhFtuHWhKZM5dUZtJZxNMyUrKSYqsLHFnOFaX7Brkvk75uRdNmF96rW", - "7gH9GXgh6x+ZruQFj072d8HTZ+2nj5sXpJe2B7IVWqgfztdvggmcyVCFKncvOm+mdHtuKfHqMDsLPtOv", - "YulfxdK/iqXXXyytbfJT+ZVRTyu1yS2g+qXO1hsPTUZbq7CUE6ZisEgexiioMCWeXNmYtpfbLW7nuTfn", - "Oru+F2I2jeDsSME17+rtDRyp03GLLbfPaFZGo7AA0MbJgE2pgjHrdDc2t56oFRzjKTK9yYdBwj5lysBx", - "VqU0/LId0a1l2Cx0e138u68VtRiAxp4Au5fiXNSEkFmV+fNZwYDXNn0Wo96pjiyJdsvG4khISXgKZNpy", - "tJBvBf0jhu2ydeZh6lvha32zAWD4K7Ij6Tqa6tv1lLmAefpCjZj5exsqf2W6TXAWBUmM+UyCballJ9F0", - "din9jFEvEa3eunC7JMTHNRoIrxwE8m2DB5/+pRHhP31iKuVlnVGb4ldIxlhlY5aza7ocIBijeN8sMDqF", - "X2ToxkWK0xk2WPnSQJKNZd2POZ+mnd+7W8GB2l0tHuLf17zckWtkBqi3oU44gwyIZgEVd9IrU7GGFzRw", - "2FMvaJBMEOEmMZPEkf6a7bQyMWpi2gpFA9KcHFKXz4zIoVXwIRlG1IEPdetEhr6rjmfrlGb2oWCv9KEZ", - "mNFEYdKOEOO6DsRXYWYd+ZRtqnTnBBLRfowUe1jzgjQajQvy25spinUGNEVx/N//+W/wSFL3GBCqxi0x", - "I1W2OUWKxMSiTE5/8zcZhIpwgHQhrxb33hQGYwS68mB5xkB9YQSUT+WVEfpT1nrd3907Ot1rdJvt5phP", - "Istk9HL88Hwvd3K92RavimmBU+zteBvNdnNDIROM5ey24BS3rjoKKUjFtp3VWpjxQsZZ8QgTAFV0LobE", - "QJZQw0epxsW3CntLdpzd7ldR0Jy90souz7nz6718RuWrlXeCKcofdonZvDvMFlxhdlm4qKLbbs8BIjcA", - "5K57bO4JkfD/IeahRMJYcqzCcrCOsifcHux2pxt2wmdPG+1tGDY2B0HQgFtPw8bWYGNrq7u5vYHC7roH", - "260abN3cZB6bo3y0sHyJhCyRSZeA8C5CNEhGI0xGTdHAppJFV6epzLaqb1aRLXQWtzDvjgJJtF5oi9up", - "QvZXl9dI/7hCdQm+QxXu1prpUjgX1AV9rhjNMmRDGoOBLD60mSm2IVUhMwdt0KUdVfOpftQu8nMazuYo", - "Bgvt7Z9lJVEDGuTOr2qvIcf2z/mq52dHWf359UxdNVNbvWSVnI71kZpm2obTUg4KcUl74VgxSlmCJoyu", - "GMqAV8VrTDde1cokD1hZXKmVAMNq1eYvqbor7eObjvv9kiBAjA2TKJqlGuhnVrV92/x26dg7PzU4bQCA", - "apPTvFVlVxqwgKUty9LVtzUMzPLts8rUNLesyZVY8HLiEMUKqhBFYdUVtOKl4oWFOkgg28wydzYU36Uj", - "blHevIIoCVEKkJkxtIIWrD7oGfBNpw2s45nFjN6Drdtl4B2WMXmyQf/QRsowE+Z05RDp5k2khnq0dzNF", - "MZbud/R4jsmijoJaiK2u5aNeyoAP61key1kb6Vy594sUh5NqwIJmDUXb+RbU6UcpjsKPo7S36rQx/460", - "dQiwkiYzo7Ul2LEhtG7T2wzulGhHiDtRnsXvFirxYAY+o5lL1NWrmagvt1Vklys4FNxmJYANUISHK1M6", - "m6qzhTPvvBlwHXOuZ2DZOffd+/0B4jWm8gDxtcxj+1tqFYny+PPKhTWT91EEysxaYBjKKkANo2cqrvSH", - "Vbbia91uSW7yzUv7zrSVq7hILzW7z03hzsOOubvlKIjMyDRwCB06L5Mrw0jNH4OMZRviVR5fXnqEJziC", - "sVU4c4VDFAKObviCIZ6qT89owT4sVKSiGw6GMRxNkDq7z5CwQ+dckvfTmO2ZKNi2ew7AIR31N7GaM5TG", - "ukazLBoXfrwQUrO0fvY44Ty1siKzXN5BIJ24QnWoDRPrMtVT0NV1WOp5EF63ua61EqdgkFrG39Rgn0+e", - "uX1M8/HHEdXN9vbiNmqAOa7PbmdOUXzA5t3KIBar9/DszlQwSSKOpxGqt4erqwjvmU2Ud0aodoQpeJ89", - "5xtq7CICW231bWOP/fx6e54oPUSMbyMtKHctC+uu0n/hedg7WLoQt8KlyYOq3i+WqYW5cISGYqJ2c42P", - "okoiDKrqDkjr0gAm6R1O5s418EJNlgzgE3qt4U8dtpCubHNkix509Gadflqe59W37hteZbUeP7fntliG", - "V7Sg6mwROS2mK2JTeqoNpuJW8XyWGk8PWFo//z7xA8Y717Nf5Ea9KsMeatAvSupJaQmx8QHiebl+tyAP", - "Kum2wTXq2feJ5ztXQpVeVyCF/wHyXkc4V6rRzS+SwwuC+O8oDgtAuWAwA/0XTXAMY47lJUw0BiqHLm/z", - "M5oqxXS7ojiUIDDv5D/M5cyU/EOVFTiR3P/B0k0OktmEunNkosWHr0/NiPp7SO1Uw2ubb5oPP61xImXF", - "lpQVZBzsi9uVdaE7UEI4z1p/qMb2l5We9RvDNVVnyq+fPosBAcNkFBl44RWpyTFmnKo7FeZ6kvq9ghWu", - "2pknmS91+9/W2L1/ID5XMyWhKIzDKmvKM7dVNbDAaa30UTVyQ94eWb3PWhrSHgkfNKAlvHD6A/rgSzgx", - "6oKHOs5LzjU3C+Xn98tzGuHhQVpLJ8UGpdft4UgsWGblJjP7SYrlVxTTontuEpuQhAYpegagxKA1RpzM", - "TM4DCs9rOEnFgz35NblKNtqxQ2QNsoEB1lOgv9/BP6qksLCoNIE/UBblB1uUJwWo6WxJ3GdNGkmeFw1T", - "7zSd4a5D9f0Kzxd9H+DkGsBAK4FWXgoDcJmAmpmkHzr+NTHSYuRUi091jOu33oKjjr/1Fdqf4+Cpb5CP", - "YFZSacAB80jfQxwh0dgFScMWFuS9K3xmkFPXodL13LtDXfqS91WHur47cvm3WoD+Qydh91dtbr0V/7//", - "899AL6eJXi2lZV/aiVq38n/Nbexzo3gLdcMFSat11bKR4RVAYyBkqbpk1yzu5Uy9HOE1I2mK4HzJ7sPs", - "jR9YGPRkVArDnApdx/S5AhFrmrj2LwW6QgWqZN4O4/2Abu/9NZaq96m0qN9a4BDCKZMSULat5WsrEWd/", - "fXAHdV61UZ6X+uQMT9CflNT/TJVYGVSs5b460Eut7lfp+w9WFv+xWO5LaBEbNl9e1IlueCtgVxVOpO7x", - "k8QX9vUfiIS+Zpgv+esLfvqSVxfENSy/8GNH/mhY/anjW9Pjy1PmfqdbaqqTb0qxpru4qW671FTX1dRG", - "vqluril1MtzfdMRXS2pZ3quj7o35idPSlvq9n3Y3Bxrmh0zSYw8qOJleZ1ERQDk1jX4X08UVhymolEy+", - "5oMD141WZGdaViJqa4xWpKQuEBcFhJWXmvvYAwXMsCY4G2MGEAmnsqYSMzBNBhEOohlAN1PKpPvDafod", - "q7AlFM5XhUVxjzuZZCJIoruleaB0zDXTXQVDdPljOT+V4fKNTZBf2++v7febbb8azFHqmhLg4MdLIfJu", - "vMWPl0K8i9u3Bh0s7uLqa6daNsg5FUq4RiRJ7ghOQMfKUyPWaBYe/8yw4nJNZ5BxaT5ebAHdrebyEHLd", - "LQtBTvzxDQHkauU87KsllrAl8nPxKyxbIxInWZfjm2sZLaw7rrzsw5UxsWd32bzJspeHuD3j2k5nThDd", - "aZgc7ulS2ZjVbc4LyNz9jykyLsDr1t4PWphcwQiHUGUX7pmBvCD9tBlWsUVYoevqE1lZM4Wd416LRYpb", - "1iSAUeRCGbSfF1Vpdu2WYKH1rhzN/NbyK0TVtVoNLgxXl3usvHZs3iBMdVB2hiiloEJJ3OXURB753XWD", - "bY1Bz7vQ5SFDKeOHF8DCy0qhPwSEAhxaoigMivQMri/71R2aG4OyZRI274dJd5wbSK69X/v14v3aEouF", - "u7al7BbGquravWm4psLSteJX6zcTswj/fcNNP+BOllpljnBTyt0HlMlckPMpQzFnlgYB5hR+Cg3BrO2p", - "P7QProCQIkb+wQG6wYz7APNUO5jDTKVP5Kss9+40PV2jAQUzxPMUCEaCnzhFTQ3hNAU5uW/xzarFzL73", - "klOQSDJXbwuummz9qqb3ly6ut0qVEIIMasexTB06uHWr/9UP38Sv0GxllS1G8FILcy4aXbZ4lks05Imv", - "Wd1iBCxf3/IzVPX++FU2c+XTv7c5cIB4bYk7QHx94rY6NzrVmNUa8ic/cybExprYCp1WCOSmQVt3KNe+", - "qEdHceU1d67gp7x/r3gpSaf7tNlutpudnWfPnj1znNmS6PFz7oJRz0XPejSOE1IyqcZAjCJpjKSA4ZiM", - "5AGS9KoCfTmMwphuXpCPrxGMCZjQGF0+qryHpjVCXLTVkLkQFLZkKy16heIrjK4fy0WjI7cardp5kKtM", - "prqpk4zU1TIyCCyo1FXg96ZPLz8ngTrrWZNAXWSdy2XWJmtCCeL4K2qFkI0HFMahDtw0QnSFIqFmGqME", - "hyhHoHaBahJo+TT3ZJZpIUdEumJqkoGskxr3YJD9eYVczTkKcnd59/8CAAD//+TD1PcI1wAA", + "H4sIAAAAAAAC/+x963LbOJbwq6D4bdUkPZQsyXYSu2pqSnFsR534El+SdMf+0hAJSehQBEOAthWXf+xb", + "7PPtk3yFGwmSIEXJcuIvk62uncgkgYODcz8HB7eOR6YRCVHIqLN960QwhlPEUCx+jRBkSYwGr/gPH1Ev", + "xhHDJHS2nT5IQvw1QeD87eAVwD4KGR5hFIMRiQEE6su24zqYvx5BNnFcJ4RT5Gwb47pOjL4mOEa+s83i", + "BLkO9SZoCvmE6AZOo4C/3+n2T/5cP3y1++bs9P3Gycne3rtnW/ube/33juuwWcTfoSzG4dhxnZvWmLTU", + "H70Y+Zi194z50sctPI1IzOSq2cTZdsaYTZJh2yPTNRKhUOABk+zfazhkKA5hsCbHde7u7lwnQP4Yxfsx", + "DFktoko4kh+CMf+yAlH5sb8PsrLZHghVy2CpFj+NUeMllJEpilvYb4aLt9n4D4WM0AsSH70n2KdltKin", + "4IpgH6CQxRhRgEPAJgjEiEYkpBmPfU1QPMtwg82RTXz4aASTgDnbIxhQ5Gb4kYhTGBgSEiAYOhmo7/j4", + "b/EUszKgh8l0iGJARimUjIAYsSQOK8ALxEBWuLqdTscAq8t/TeENniZT/XCKQ/UzBZgjeYziIsBHoxFF", + "TSGmX3BUAS+R41gBLkOrwetYwRNUMfCP4tMgGTdnBr7r4tMKbsgPW8cS/xWjkbPt/J+1TPqvyad0LR2A", + "QyqQsIcDxqUQSaKXM/65DT+j3EvmZND3MV8ZDI5jEqGYYSRIPc9/d24BC6eYIxTIccX6x3xwMJxRcI3Z", + "BKAb6DEwhcybtC/Ci/CcwjHaBn/9OwfKJz7N5b9wGCXsIul0es/yj6fER8Hlv8YRa238dcGpNd3KW0c8", + "5BzOnzrGXkYJ4xCr32T4N/LEHyibCUnjIxQdpX81sPi2UgTK5zgcAxj66VrBNAkY5oigiRiP5teqJeC/", + "Ot3XH5+9f7O5s7H14uV6/+UfW8cn3c6zrePjwqqc6jerWDWTgtmu/mDpaaD0VCKmDqPz8Phv9cd/pRqi", + "K2ml9PfeRZVAU6/mkIQZmtppXf0BxjGcGZwWk2l5HacMxgz4kKEWw1PEFcDJ3g5YX1/f4nwxhax9EQpV", + "QfEValdCOOKj2zVjr9Nbb3W6rU73rNPZFv/96biOHJ3Ts568pDdT2A35UFBiIxASBmiEPC7LfAABxeE4", + "QACOxzEaQ4bANQ4CMERKZSBf8DuC3kRvl2AKsfprHPrkun0R/qUe/QUwBZDrQxRfIYN1rmCQ1KBjbJFV", + "KUY+Kd5Xy710F97LM1JGxW7or2AfGZm3i72ld/GDwO4p/obmb6Sb7WTC+WjefnL9xRVajNiMK17+O6OK", + "iLN9xcaLrapGyHUGdFMtZ6yzsPYzPEV/ktCy/rMJkjTFCY4Dz6fXCxE7+o2ECEAKfDTCfNXKVBv0D/uA", + "jwv4wOAVZHAIKQJPJoxF22tr19fXbQxD2CbxeI0P1OID0aecHEo45wOen+2ICcV8GtcJRf48HKWLs9ox", + "zvnZTk5V9Kcoxh5cO0TXn/8g8Rcr3aiN4nbDGzRbxLZXX1aYM4Vx72/iC8WhzWbByi+hf4K+Joiy45gM", + "AzQ9UU/5Q4+EDIVCr8AoCrAH+YLWIvnmP/+mfHW3prngIwYxtxcmCPooBjtyhNbZLEJgAilIQnQTIY8h", + "XxHSRW7om2lw4fCtYZAl1Nne4LYkw0ys7CX0gQI2W1kSh9sKIKFet4fQb8XqrbumzKAWLxGU3zxz1jvX", + "kSq9H8QI+rPdG0wZXQnmPBKOAuwxHI53QxZLK9NXxsr7g85pZ+fgz99P3/XW97cO3nw8eXf83BF2NPQh", + "E+vjexyhYzibopAN+KcR/rxxFPe/TN5ezfAEk61oszvZwngvfOlkZJsRWqsrjVC1h8o9R2KRuU3ZyjZF", + "vQQlQrKXq7ZHvdB4Z6rxbdss+TbYTSc5JGyPJKG/WuoWZpkQSyM+eA43GxluDgkDe+qFKnyEhLXkIKsg", + "1mxGufYBB53TA1oxBlQAS+AAZ5MYmNjsdPOYGOReq8OHOeCqsDLIj3kewoRNSIy/rRozU0y5SQBIDHB4", + "BQPsA0a+oDBHJAZqTEhq8JKYr60CKeeFAc9TybxafBgSH8UxiXMk0jHxkL63q96rxoV+dUWYKEB4l44q", + "dOROjCBDKoSqVUGlW69iSkULQDOMsNT1DxIDbrJjjxtRIxQjTisAAq3D2xfhHrcGJVq3wc7xees1SWLq", + "gjNOU9QF/eMB2IFBQF2AmCetnygXZYCxN8FX3GiwmLPcljJBU++6ADNpVnEGV6aVCkHAkFEOubB423nX", + "Wi2eC+ujMJhVRdRUAEg5TNJLpfeIlBxF8iMZHMr8HxnooG1wTtEoCQAeZSEkQD0SiUUPYyKMFTaBIbie", + "QJZihMXQ+0Lb9UERWxREzGCPbZ2lADA+VXEDKCUehpwWRYiH2/4+8mIEKaIa+cOZFfmOkDP0MyMMBmXb", + "T5uVNoBMO15nLgqD9weK5qwmcGaZfpLTmDi4tGBIMpXU1zaLWdkVMYq4W8vFNvclSIRCFQEE6TvThAoa", + "hZTicah5SPplF6E2sS2cYVpPjSnPoIOFLa5ytKwqcpNSCVcl2hFkE0z1ogVDMiJJVBPGiMRynfUbpGct", + "7UtNEGvlIaw8CYiciyFb89jYl8uDMUrXjUPJFGAIAxgKAap9RM+McJXF4ZQkYQXG5TM+vMxJgR0YcsqK", + "CMUMXwmBHXL/nv87FPHzApuI4HcWbiDJMDBiDfITvvGeWLvfrwBE+NKcGQUc4BpSoL4osuUicasKsWzQ", + "9miEPL64KrjSF0QQ5z6wWPgqwjGUc906MAiORs72pybOgaCO3fTzYxFGce4uXec6xgxlK75z6xK6fH3K", + "T9cCWaR2UzEt96IspF2uV2A4a5fCzI3zkHfuIxBGEYzVpzbUyKcKCd8TMVGMSYzZLJ8hc20gqje1JlNM", + "rKSH0KcTPJ6gOHuTixRh0XLzBseU64lj/VDYainv+8jDUxgovqdt8IEPGJBr7grKvwEc+sI2Dsd6Jikq", + "uYTKG3MuH9qAt8tnmxIu4eIxR7SwRvLv9NoX4YcJEiE1DneMAEVXKIaBVgDwCuIADgOUhhsp1+xKHsrQ", + "GZ1RhqaAooCLSFPK8PXwnwJ0ytK5RVAWeMIEuRZTq+nohMOQTpPCGqArFLjG0F5AKB+RC25GQcbrudhd", + "ugMDGTXkM4q9vCZ6xgm80mE0DwZ6RoyosJSMcbmsobkFi5kSaspVQcGGcE0ByIl0I/fa29ysT726TkyC", + "gFxJo6ah7DrRn6Rc2fjTM/76neskkb+gPgkgZUB99oBKpWB6iKeuVsJurhDG1D45fWCzH3evtD/a3Avb", + "CUjiiw8pOFW2gqSW30+PDsGpdCRzUlVL5JzJ32JJPCRcVkmDeNvp9tZt2VERwNv0up0R9FGr622h1ob/", + "zGu96D3fbHmbPW/92fP1rr/ucaeYJLEnMCc9wpYyo7lRc4ViKpfQbXccM3JXiPbiaXH7utviv3an0/0z", + "gzCKyTSSQj+nYOoVkNxgi1XAUQoiOAsI9Ns1vlIF4mzKiEOiog6aJQqbKR8C/lQLfP6RSnqBA+4VQF+I", + "K0ZEnqnX2Xim80wcypAz8adcRENEMi5NXig9FQLgLQrH3PLtuk6YBELkVlpVHCoz15BzwXVGQApi+ZqU", + "S2IxcgEUMNI2GTCJ8eJwYH/u/GInczvYlHzzsJTm1tQ9Z36x4zeMu3rXE+xx/1dR1wRGEQpRnryKvGLi", + "pxWjEYpR6KEG0Jk8Zk16yYeazkxBQnOCREKdopLrG5oHWXLwPICq/MJX4tdQk4tyDBVYckqVctOozD2L", + "YuInHorBkzQV5YPhDMjteZqHNC9b5kAsRU8Jd3iKKIPTiINxrUwXQDwvicXWZNtq49f19fWtdqViKkg2", + "q3JakEPskiaPcy1vJEJjFAjDgXvifGUxHuNQGoDZKvNrULJ3nqYUSFdsk6dQV2vRhn68ZGqpMJt68R6n", + "cPEhXaP+l9aYrF311sQfBKQqGtrcVbMGUe/c24ICauoXaw9tRZ5xI1l5gqBPwmBWVfhbHz+rdXrmeuZN", + "7TsTL6uy8ObSadk+u/yxAfCSNaHo7qWMFTWnWk3mZUIdZkOVt8OISVVTRNNQkQj1VoQrRNHJCmYp7Kle", + "nJ7cssF3rrNQ/Wa7LlJNQqQ2o5CZ4Rz25DzEXPTBIJiBcznuW3SDPTKOYTThfmAwA6fcy+aOb2pRxE8L", + "/Lf/bPPP55ub/b0P/Tevd7u9wz86O++29l47LheADMV8yv/7qdPa6r/cebW7t//69zcHh8fvTk7P3n/4", + "+Mefl7e9Z3f/ZZEWt9Urm8IbrYCerRf1kTkrbH3rtLYu//nk39uf0x9Pf7NMd2kRD4NwjChD/jJeUT8E", + "WH2ulJoIBBCdUxE5TGndiPRYwcBHespFXKUFfCP/x/lG2cplRrKU+Je1WlIpFl2pFC918mVXf1uayqQp", + "nUdGSoUv5nLLr2w+dJZ9WUSHq6+W190qg/EIVbc6arFCzb2ksqzIfiWxCmbZ1Nz3Td7U1OUsFD/IV+y4", + "1SVRKgKc1UQdvvr9ZHO9t/ti/+zl+9Od3sc3m682nMZlTU9ULLldPdhTs6yJUSbYXftc2eCug0PKpDUg", + "ihVU+dl2QDwYrP1+cBR4jL55/6LV4f/XrSql2lHrzXYWDknCtocBDL+UBYwVPfPDhiYuynp7kkxh2OKL", + "FsoU3UQBDKXwT7NrwtfB1HTjFP+oKo28rh8Sf5blaGWoLSXZMvemqCwDd34yAKlXL4MkuBA/0TA2hK3Z", + "bhXCLmVvXe2mTeq9Pjs7BvIF4BEfgTEKUSx8xuHM8BmFHZwec2mM3Y2ceYdDtt5zjHj15taWEa+W1ZSl", + "iLWivzK+IaATEjO3SBU0mU5hPCvAJTzjPHqtFZvz3G1RK+qRkEEccl+B77ptr6unra0Jnbed9oC1xFG6", + "1SkLLZJCry0afSgJ/bLKT3mZ+ShZHbIlXz7KuU4WKlc+kkqwKddBpd7TIwMNnCwNaek8geuIrEU1BGeT", + "NCOlc3kqHpNbVyNgjNxKDUDctT5B1mN1HBjheXPNzeoti3vZOY+8eqUQm7QjwFSi9XxYJMMiUdREw1Je", + "SAurK8wtxJ8vXzPCaW/WqGYEDEZA5RWGwbJxgfuUMoiVPljG/t4WuLkTq4qgydM1c8tEVfFIZUDb4shI", + "ZD6UO9M8ISzIWyaEbbHkVJPJVImReFXkPYdxzhQgOm22f9I/PHNc5/2RGORk93SX/xR//nx+2t/fzSfS", + "9PulFVpE7jIVQKkqvV+wThaNrDCIZg+e1ZUulU/NZXUN6pCY0Nq5/gkWseVVSy1UGlG8DAL8BYFuD0xJ", + "yCbFytduz2Y++klWttVkIv2+nEtMpOZRhPX66PzEcZ1X/T8c1/mwu/vGcZ2Do8Oz147r/LHbP3Eu5ymL", + "FCRX4aCatPOks1QoJFe7WCY+gQFE+/N3gsuBOjJcbbHfPaS0Fbj7iefqzhhnmaQdvGrfQy1dEVxdBJ/W", + "sPG3LAXwdsPyHzQVHzCcTUm8ZEG8TV4LcA3EzJUjJ0blkaV+FujKJO5cjfBY8Yi1MBre9CtMngPpWhpm", + "jx42F5PKzJQFC5r0Iqx6TJ9EbuBy5THyUN5VGWQr+aaYF8ljRkBC0fZF2AJ/newe9AeHg8P9z/2Do/PD", + "s79AC+jxQIymEIeiIYLAdlt8cnQy2B8c9t/av2hJQpUu8igJVGlgNoIhaIuTO65TGDyvwYsPm3fTyaHo", + "QTejehNUyd8sUqgXJgrH3qBYSq4CM4rEVT1FEuLUmTGsZnkYIYdWi+0j/2TDF//oEE45330qruNcJt/S", + "BVZ4nG91sJj7m3N5e26NtzRviRrP8N3AgAEPhkogKrSMEluy+0cF8AXMDxu/z9cpNhNGcuNWT/cH/InN", + "oZV5KpFqzxGEqiwTh/MpmJBrvtGigYyon83aF8jSj0KqTz9WTTnOD5xS5mIgDzvKzDT/mo9zlkusuWnf", + "CSNZ+F/tXLcJ/gemSlupSDEXE6uiyk8FO2byfX06yWiGsO0cDA7Pz3bL0fPcWuq1k8By33i/eP6sjH/j", + "tybNtG9R+UgVOFfYmlugZKDztrIQUyuZdDeb1R/l9qUqoJQNU9qxqsN5XLr5Iux/DEXToihGlGISioZT", + "6IbF0NPnCMy+JRSMYjI1itd8yGAbvEEzmqYTlDTgtOuRkGLKgJASMIgmMExE8wbxNAl9FFOPxAh4E8hn", + "RDGtKFWtocWSE2GTVY26VzUsTJhrN9PaAoxS6L4SJFl/d18kfofyhxLrF9duobQiocnWEyl55csf/kFl", + "6a2SF7P2RXhmvqQ+JjE4PT9wQf/9vgsOBoeuQNFB/yMwRAuVMjhUrdZEyw+xDimIPRUqhDHVlU/pqbI9", + "EoPzw8G7893PO9zaMod182BLiLIUm5yiDfgQpW8zBGgUchjxOCRxOXJlSNXSNlznGuUs0GomdxRRNmoz", + "pbEp5XKzzNG1gmlpW+rE++na6Mt4TQ6Xqdl+XmFYAsEmjjM738L6yk6UCnRHmdLmXjuu03+/77hce/H/", + "3/+YNyfll3XGt4mMfg65q8aL6DF4gqg4lmV1fsQzGbGSdolozNO2HbT4dGszDgqVRMUynaqKH0HkMmol", + "qWg39Kt7RClCY5CjxG7A3l26soVYpYHLyNwJ6s0SndDJ2nA9eow0SqsZpEKubRm1kWr8tqqoOVlZY7AV", + "STqxs7Y4ZB41FlUek2uj5WkDXnrMBFMk+AY2Z10Ss+H6Kgzr5VKZEvHymFHNgYm5xrxC7q01wJ4F0AzE", + "r5qi5U7dPkhduFxdfipzMQto85Q5Vqq9qpItfRkBkdkKW0GEtUWlHA1Q0alStmmUR1myQNI1TOsT6jPA", + "qxB9VuhQ2kLyh8BWTOnLbpyMWMXiMYkZDIR/bNsj7qlw1wSI49WBDDsU4xVBQK6Rf6AbkYgYW85Ducx1", + "Yqham8zg+Ea/6DSjU8WGqkyxxrOrrFOWK3bI9LNc2udBP3y1fhx9+NDr9z7EL6Zbf4++odfB/scXN9Od", + "j9f77dnm143TVv/D173k2de/R3DvW+fbu68bu996L05oOHt//fto9HHz683BFbEEQspIuq1oryOqA3Rn", + "R+Ei5htYyr756ZFuNbK5J2X0VzcVneJwIB92C+aC60jvVj1WrR1y5ccP1SBDU8Jtgx5HucTfcoK7YRJv", + "ZcGETAs2bCiZ0qs1kiyCW5hKWkk7oXLHl+8VJnmGfSiSXzTIW1dItFSNcx+oz8ArUTVIVf0reHKytwOe", + "v+g8f9q+CPvpeCDj0ELVbb7qEUzhTMQPZJF40aPSBc+1Bbir67RZcGR+lRj/KjH+VWL88CXGylA+FV9p", + "8bRSQ9loL79Qy0PtNokQaFUH5ITKwCgSRxgKIkySJ5PWq+l69orqPPdmrQfqOj6mUQBnh7LJ8o5Sb+BQ", + "nimbb7l9QbPy6XijGHeSDGlEZEltt7e+sflMcnCMI6RnEw+9hH7OhIHlhEdp+WU7otfIsJnri9rwt6wV", + "Nb8hhrkB5izFvWjY0mJV5s8X2by7sekzvwJbTmRQtJ025ocnSsRTANOko7l4K8gfvmybrVPXCd+IKav7", + "CADF35AZ3lYhTtes58tFsdMXGgSyP5gN7lcm2zhmkZfEmM1E8x/JdqK7xw4hXzDqJ3zUW1sfIXHw/BoN", + "uasMPPG27uKe/lJ93D9/pjIPZZzsivAbJAKfYjDD2dVTDhGMUbynGYxE8KuIp9hAsTrDusO9MJDEYNn0", + "E8aidPKlp+UYaDzV/CX+fc3KE9lWpjuBtuS5YJA1xpgDxZ3wymQdxSviWeypV8RLpihkOluSxIH6mm6v", + "ZWTUxmTN5wMIc3JEbD4zCg+MKgyBsFCeApDH9rP2nvJQs8ozZh9y9AofmoIZSWTTyzGiTBVnuDL2q8KR", + "YkyZg5zCkI8fI4ke2r4IW63WRfjbUYRilZZMe7397//8N3gioHsKQiLXLTrLyRRw2k8OhwZkYvvbv4nI", + "UIA9pEpnFbn3I+hNEOiJ49gZAtU1D1A8FRc9qE/p2tvBzu7h6W6r1+60J2waGCajk8OH4zq5897tDn+V", + "bwuMsLPtrLc77XV5nn8idncNRnjtqis7l8iAs7WEClNWSANLHOEQQBkyi2GoO+4SjUchxvm3sheQmDi7", + "PrCihDh7ZS278ubObfbyGRGvVt7kJSG/39VjdTePzbl47LJwvUSv06lpH67bhttun1myscD/hz3YRP+I", + "BdfKLQfjAHjCzMVudXt+13/xvNXZgn5rY+h5Lbj53G9tDtc3N3sbW+vI7z30YntVi22aMMx3tCjfMVS+", + "+kHUraQswL0LHw2T8RiH4zYfYEPSom3SlGbXqu9DESN0549Qd7OAAFox2vxxqvrxyytnhH9cIbo43qEM", + "dyvJdMmdC2LrrSwRTbNOayQGQ1ERaCKTqyFZtlLT/cwmHeXwqXxULvJL4s9qBIPRfeqfZSHRoKHGnVs1", + "Xkus7Z/1oudn7/r488uZpmKmsXjJyist/JGaZsqGU1QOCnFJk3GMGKWoC+NGVwxFwKviNaoGrxplmm+g", + "V+TUyoankmvzV0vdlfT4huVWvsTzEKWjJAhmqQT6mUXtwDS/bTL2zk0NTnW+co7Jqd+qsiv12fqFLcvS", + "hbUNDMzynbFlU/Mo9lEsr9ZAgV91Uyx/qXCvYHbFmqyhV0EC8SPL3JmN7myn/KquB9b3lmQIrb8TuK/v", + "RKm7Frh0SOu+1u0i3RAWMXmyRT9qI2WUEXPKOaFw86ZCQj3ZvYlQjIX7HTytMVnk4Uujj6CNfXINJxtb", + "HotZG/amlnblkd4pQ9Qx8XYDqdtdGagpWZWhU4/S0+uPR4JvNhmj/pqzh6Bmue16RxuTs0U7rN2m5/Dv", + "JJ0HiFlb0PK/G40zhzPwBc1sdC9fzeh+Mb2RtQWwSLuNyuYvQALur0wCbcjJ5u689XK/h9hztQOL7rlr", + "V/77iDXYyn3EHmQfO99TqohGiT8vXRg7uYwgkDbXHCtR1OmpTnS6/Ep9WGU4vlXjlugmP7ww9vRYufKL", + "9AqlZS77th5HzN1kRUCgV6bO9pOR9eqq8uXS9WsQgW0NvEzqi3tS8BQHMDaqaK6wj3zA0A2bs8RT+ekZ", + "KRiLhZpRdMPAKIbjKZLH4iniRmnNlVw/jQ2fkYJpyOdaXaar/i4mdNbosKkFLcq6uVPPiVSz1s8eNKwT", + "Kyuy0cU9kcKjK5SKmu3AbHZ72rf04cx2TSV2c11JJUbAMLWMv6vBXg+evrBI4fHxkOpGZ2v+GA36IT6c", + "3U6tpHgP5b0mew/W6/DshkYwTQKGowA10+Hy9rIlU4vi/tO3uoPMg+ocFYp5T7BPne8p54vNvxoLfbN9", + "1s8v7esI8D7Ef6sbFN2tGW3WKr0elu+4BkuXdlY4QvlupsuFQxULFI7GEBxKG0D1PZFVFbqP6TZIS9sA", + "DtNrafQ1UuCV3CyRAwjJteo0ajGoVHGcJeF0ryM1D+nd5XFefd2+xlVWLvJz+3vzaXhFDNVEseSkmCqq", + "TeGpNrOKCublLDW57sFav7SLXbs8wtjqw2iZ3KpX5URA1buLhM1ou9Sc8R5EffnwLki+f6Td3lfNy35M", + "7sDKCVXaQPYa/A+g9ybEuVI9oP8iMDwnYcAFZaFDLBjOwOBVGxzDmGFxZxKJgUzei8t3taRKO7zJTpjt", + "i/C9+Ie+O5aE/1iiI2aeQ/mI9+dPhYgFtEjTtMZbE28KDz+tSSNoxaSUFWQ3zHulpU2iJpBEWGfj31di", + "u4tSz8Ob0A1FZ4qvnz5jAgHF4TjQnYRXJCYnmDIiO/zX+p/qvYLtLsepo8zXavzHaSLXB/3Fma7UzRXF", + "7JmzKweY4+pWeraqj0PeHlm9p1ta0m7o32tBC/ju5BF67gs4MfLiiybOS86h14zy83vzOYlw/4CwIZNi", + "3bPX7uGIzrDUyINm9pMgy28oJkWnXidRYejrhs8zAEVHWm3EiSxoXb/vvIQTUNzb/38gV8nsfWwhWd1S", + "QbfZky2Af4B/VAlhgakUgI8oY/PImPKk0Hg6Y4lleFJTcl0MTb7TtgbJDuT3KzzY9GPaKDfoSLSSRssL", + "dQRcJKCmN+lRx7+mmlo0nSryqY5x/dafc8byt4G6Nb984tXVLZdgVr5p3P9v9P0e4QDxwS7CNGxhdK63", + "hc90H9WHEOlq7+2hLnmedeWhrh/ex/x7MaB7303Y+VUH3Izj//d//hsodpoqbimxfUkTrd2K/9WXp9dG", + "8ebKhoswrQyWbCPCK+Ja/SAZV5cHa+ZezNTLAd4wkiYBzpcH38/eeMTEoDajkhhqqoEt22cLRDzQxnV+", + "CdAVClBJ82YY7xG6vctLLFlbVGlRvzO6UnCnTFBA2bYWr62EnN2H67PQ5FWz5/NCn5zhKfqThM0/k+Vc", + "uh3XYl/tK1Zr+lX6/r2FxX9sZ/cFpIjZRJ8zIEM3bM2jVxVOpJrxs+g27KofKPRdhTBX4Nfl+HQFri5C", + "27Lcwh+74o8a1Z+7rrE9rjje7nZ7paG6+aEkanrzh+p1SkP1bEOt54fq5YaSR9LdDUt8tSSWxS078haZ", + "nzgtbYjf5aS7PjxRHzJJj1jI4GR6uUVFAOVUD/pDTBdbHKYgUjL6qu9K3DRakZ2fWQmpPWC0IgV1DrnI", + "Dlx5qlnGHig0K2uDswmmAIV+JCoxMQVRMgywF8wAuokIFe4PI+l3tMKWkA3GKiyKJW5oEokg0VYuzQOl", + "a26Y7ioYoosfAfqpDJfvbIL8Ur+/1O93U7+qi6SQNaVOh58uOcnbGz1+uuTkXVTfqtthUYvLr61iWbfs", + "qRDCDSJJQiNYO0lWnlAxVjP3qGnWpC43dNarLs3HcxXQ22wv3ruut2m0ruM/vmPnukY5D/NOiwVsifxe", + "/ArLNojECdTl8GZjo7l1x5W3jNgyJubuLpo3WfTWErtn3NjpzBGiPQ2Ta7i6UDZmdcp5Dpg7/zFFxoW+", + "vo31wRoOr2CAfSizC0tmIC/CQToMrVARRui6+hxXNkxBcyzFLILcsiEBDAJbe0PzeVGUZpdwcRQa74rV", + "1I+W5xBZ12oMODdcXZ6x8hKyukXo6qDs5FEKQYWQuMuJiXzLedt9tg0WXXeTzH2WUm5cXuhSXhYKgxEI", + "CcC+QYrcoEjP+7piXjWhvqooYxO/vVwzvOPcQnLj/dLX8/W1QRZztbYh7ObGqpravWm4psLSNeJXD28m", + "ZhH+ZcNNj1CTpVaZJdyUYvceZTIX4XlEUcyoIUGAPvGftqGghnoajMyDK8AniIb/YADdYMpcgFkqHfRh", + "ptIn4lWaezdKT9eoToZZq/W06Yzou2IlNbmE07ShyrLFN6smM/MWTEZAIsBcvS24arDVqwreX7K4GZdK", + "IgRZWx8Lm1pk8Nqt+tfAP4rfoNnKKls04aUWZm3nu4x5Fks05IFvWN2iCSxf3/IzVPU+/iqbWvp0lzYH", + "9hFrTHH7iD0cua3OjU4lZrWE/MnPnHGyMTa2QqYVArlp0NYeyjVvCFJRXHG/ni34KS7+K96G0u09b3fa", + "nXZ3+8WLFy8sZ7ZE2/qaS2jkcz6zWo3lhJRIqlEQo0AYI2mnchyOxQGS9I4EdSuNbG7dvgg/vUUwDsGU", + "xOjySeUFOGtjxPhYLZELQf6aGGWNXKH4CqPrp4JpVORWtcm2HuQqgymvCA3H8k4bEQTmUKoq8KXhU+xn", + "BVBlPRsCqIqsc7nMxmBNSYgY/obWfEgnQwJjXwVuWj66QgEXM61xgn2UA1C5QA0BNHyaJZGlR8gBkXJM", + "QzCQcVJjCQSZn1fQVc1RkLvLu/8XAAD//1EynL7i1wAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/openapi.yaml b/api/openapi.yaml index 0b899fb7a..ea5baf493 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -681,7 +681,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Feature" + $ref: "#/components/schemas/CreateFeatureRequest" responses: "201": description: Feature created. @@ -947,6 +947,7 @@ paths: parameters: - $ref: "#/components/parameters/queryFilterLedgerID" - $ref: "#/components/parameters/ledgerQueryLimit" + - $ref: "#/components/parameters/ledgerIncludeVoids" responses: "200": description: List of ledger grants. @@ -973,6 +974,7 @@ paths: parameters: - $ref: "#/components/parameters/ledgerID" - $ref: "#/components/parameters/ledgerQueryLimit" + - $ref: "#/components/parameters/ledgerIncludeVoids" responses: "200": description: List of ledger grants created. @@ -1214,10 +1216,7 @@ components: tokens: "1234" model: "gpt-4-turbo" validationError: "meter not found for event" - Feature: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.Feature + CreateFeatureRequest: type: object description: | A feature is a feature or service offered to a customer. @@ -1227,12 +1226,6 @@ components: - meterSlug additionalProperties: false properties: - id: - readOnly: true - description: | - Readonly unique ULID identifier of the feature. - type: string - example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: description: | The name of the feature. @@ -1257,20 +1250,35 @@ components: If the feature is archived, it will not be used for grants or usage. type: boolean example: false - createdAt: - readOnly: true - description: | - The time the feature was created. - type: string - format: date-time - example: "2023-01-01T00:00:00Z" - updatedAt: - readOnly: true - description: | - The time the feature was last updated. - type: string - format: date-time - example: "2023-01-01T00:00:00Z" + Feature: + type: object + description: | + A feature is a feature or service offered to a customer. + For example: CPU-Hours, Tokens, API Calls, etc. + allOf: + - $ref: "#/components/schemas/CreateFeatureRequest" + - type: object + required: + - id + properties: + id: + description: | + Readonly unique ULID identifier of the feature. + readOnly: true + type: string + example: 01ARZ3NDEKTSV4RRFFQ69G5FAV + createdAt: + description: | + The time the feature was created. + type: string + format: date-time + example: "2023-01-01T00:00:00Z" + updatedAt: + description: | + The time the feature was last updated. + type: string + format: date-time + example: "2023-01-01T00:00:00Z" CreateLedger: x-go-type-import: path: github.com/openmeterio/openmeter/internal/credit @@ -1377,9 +1385,6 @@ components: format: date-time example: "2023-01-01T00:00:00Z" LedgerEntry: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.LedgerEntry type: object description: | A ledger entry. @@ -1387,6 +1392,8 @@ components: - id - type - time + - featureID + - amount properties: id: description: | @@ -1406,13 +1413,16 @@ components: description: | The amount to apply. Can be positive or negative number. If applicable. type: number + format: double example: 100 + featureID: + description: | + The unique feature ULID that the entry is associated with. + type: string + example: 01ARZ3NDEKTSV4RRFFQ69G5FAV period: $ref: "#/components/schemas/Period" Period: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.Period type: object description: | A time period @@ -1433,9 +1443,6 @@ components: format: date-time example: "2023-01-01T00:00:00Z" LedgerEntryType: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.LedgerEntryType type: string enum: - GRANT @@ -1444,28 +1451,44 @@ components: - GRANT_USAGE example: GRANT LedgerBalance: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.Balance type: object description: | Balance of a subject. required: - - amount - - grants + - featureBalances + - grantBalances + - subject properties: - features: + featureBalances: description: | Features with balances. type: array items: $ref: "#/components/schemas/FeatureBalance" - grants: + grantBalances: description: | The grants applied to the subject. type: array items: $ref: "#/components/schemas/LedgerGrantBalance" + metadata: + type: object + additionalProperties: + type: string + example: + stripePaymentId: "pi_4OrAkhLvyihio9p51h9iiFnB" + subject: + description: | + The subject of the ledger. + type: string + example: "subject-1" + lastReset: + description: | + The last reset of the ledger. + type: string + format: date-time + example: "2023-01-01T00:00:00Z" + LedgerReset: x-go-type-import: path: github.com/openmeterio/openmeter/internal/credit @@ -1495,17 +1518,17 @@ components: # # Given that we are not using this type as a return type of a call, this is just # an inconsistency we can ignore. - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.GrantBalance allOf: - $ref: "#/components/schemas/LedgerGrantResponse" - type: object + required: + - balance properties: balance: description: | The balance of the grant. type: number + format: double example: 100 FeatureBalance: # TODO: until https://github.com/deepmap/oapi-codegen/pull/1610 is not merged, the @@ -1513,22 +1536,26 @@ components: # # Given that we are not using this type as a return type of a call, this is just # an inconsistency we can ignore. - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.FeatureBalance allOf: - $ref: "#/components/schemas/Feature" - type: object + required: + - balance + - usage properties: balance: description: | The balance of the feature. type: number + format: double + example: 100 + usage: + description: | + The usage of the feature. + type: number + format: double example: 100 CreateLedgerGrantRequest: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.Grant type: object description: | Grants are used to increase balance of specific subjects. @@ -1545,11 +1572,9 @@ components: description: | The amount to grant. Can be positive or negative number. type: number + format: double example: 100 featureID: - x-go-type: credit.FeatureID - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit description: | The unique feature ULID that the grant is associated with, if any. type: string @@ -1585,6 +1610,11 @@ components: type: string example: stripePaymentId: "pi_4OrAkhLvyihio9p51h9iiFnB" + parentId: + description: | + The parent grant ULID that the grant is associated with, if any. + type: string + example: 01ARZ3NDEKTSV4RRFFQ69G5FAV createdAt: readOnly: true description: | @@ -1600,15 +1630,13 @@ components: format: date-time example: "2023-01-01T00:00:00Z" LedgerGrantResponse: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.Grant allOf: - $ref: "#/components/schemas/CreateLedgerGrantRequest" - type: object required: - - subject - id + - void + - ledgerID properties: id: description: | @@ -1616,12 +1644,16 @@ components: readOnly: true type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV - subject: - description: | - The subject to grant the amount to. + ledgerID: type: string - example: customer-id + description: The ledger ID. + example: 01ARZ3NDEKTSV4RRFFQ69G5FAV + void: + description: | + If the grant is voided, it will not be applied to the subject's balance anymore. + type: boolean readOnly: true + example: false expiresAt: readOnly: true description: | @@ -1630,13 +1662,12 @@ components: format: date-time example: "2023-01-01T00:00:00Z" LedgerGrantType: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.GrantType description: | The grant type: - `USAGE` - Increase balance by the amount in the unit of the associated meter. type: string + x-enumNames: + - LedgerGrantTypeUsage enum: - USAGE example: USAGE @@ -1670,9 +1701,6 @@ components: - ORIGINAL_AMOUNT example: ORIGINAL_AMOUNT LedgerGrantExpirationPeriod: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.ExpirationPeriod type: object description: | Expiration period of a ledger grant. @@ -1681,9 +1709,6 @@ components: - count properties: duration: - x-go-type-import: - path: github.com/openmeterio/openmeter/internal/credit - x-go-type: credit.ExpirationPeriodDuration description: | The expiration period duration like month. type: string @@ -2044,6 +2069,15 @@ components: path: github.com/openmeterio/openmeter/internal/credit type: string example: "customer-id" + ledgerIncludeVoids: + name: includeVoids + in: query + required: false + description: Include void entries in the response. + schema: + type: boolean + default: false + example: true ledgerQueryLimit: name: limit in: query diff --git a/dagger.json b/dagger.json index 5fe1e7ae4..f4377d0d4 100644 --- a/dagger.json +++ b/dagger.json @@ -10,10 +10,6 @@ "go.work.sum" ], "dependencies": [ - { - "name": "stainless", - "source": "github.com/sagikazarmark/daggerverse/stainless@8ba370b4aba45c4d2c9de7f4b2d89f2bdbd8f46e" - }, { "name": "archivist", "source": "github.com/sagikazarmark/daggerverse/archivist@b45dbd7448bb967aca4a538af9ce7f042abf0316" @@ -53,6 +49,10 @@ { "name": "spectral", "source": "github.com/sagikazarmark/daggerverse/spectral@b45dbd7448bb967aca4a538af9ce7f042abf0316" + }, + { + "name": "stainless", + "source": "github.com/sagikazarmark/daggerverse/stainless@8ba370b4aba45c4d2c9de7f4b2d89f2bdbd8f46e" } ], "source": "ci", diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index c506f34ab..08dd34750 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -513,7 +513,7 @@ func TestCredit(t *testing.T) { }) t.Run("Create Feature", func(t *testing.T) { - resp, err := client.CreateFeatureWithResponse(context.Background(), api.Feature{ + resp, err := client.CreateFeatureWithResponse(context.Background(), api.CreateFeatureRequest{ Name: "Credit Test Feature", MeterSlug: "credit_test_meter", MeterGroupByFilters: &map[string]string{ @@ -524,10 +524,10 @@ func TestCredit(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusCreated, resp.StatusCode(), "Invalid status code [response_body=%s]", string(resp.Body)) - featureId := resp.JSON201.ID + featureId := resp.JSON201.Id archived := false expected := &api.Feature{ - ID: featureId, + Id: featureId, Name: "Credit Test Feature", MeterSlug: "credit_test_meter", MeterGroupByFilters: &map[string]string{ @@ -593,20 +593,20 @@ func TestCredit(t *testing.T) { require.NotNil(t, featureListResp.JSON200) require.Len(t, *featureListResp.JSON200, 1) features := *featureListResp.JSON200 - featureId := features[0].ID + featureId := features[0].Id + priority := 1 // Create grant resp, err := client.CreateLedgerGrantWithResponse(context.Background(), ledgerID, api.CreateLedgerGrantRequest{ - Type: credit.GrantTypeUsage, - LedgerID: ledgerID, - FeatureID: featureId, + Type: api.LedgerGrantTypeUsage, + FeatureID: *featureId, Amount: 100, - Priority: 1, + Priority: &priority, EffectiveAt: effectiveAt, Rollover: &api.LedgerGrantRollover{ Type: credit.GrantRolloverTypeRemainingAmount, }, - Expiration: api.LedgerGrantExpirationPeriod{ + Expiration: &api.LedgerGrantExpirationPeriod{ Duration: "DAY", Count: 1, }, @@ -617,23 +617,27 @@ func TestCredit(t *testing.T) { require.NotEmpty(t, resp.JSON201.CreatedAt) require.NotEmpty(t, resp.JSON201.UpdatedAt) - expected := &credit.Grant{ - ID: resp.JSON201.ID, - LedgerID: ledgerID, - Type: credit.GrantTypeUsage, - FeatureID: featureId, + fBool := false + + expected := &api.LedgerGrantResponse{ + Id: resp.JSON201.Id, + ExpiresAt: resp.JSON201.ExpiresAt, + Type: api.LedgerGrantTypeUsage, + FeatureID: *featureId, Amount: 100, - Priority: 1, + LedgerID: string(ledgerID), + Priority: &priority, EffectiveAt: effectiveAt, Rollover: &api.LedgerGrantRollover{ Type: credit.GrantRolloverTypeRemainingAmount, }, - Expiration: api.LedgerGrantExpirationPeriod{ + Expiration: &api.LedgerGrantExpirationPeriod{ Duration: "DAY", Count: 1, }, CreatedAt: resp.JSON201.CreatedAt, UpdatedAt: resp.JSON201.UpdatedAt, + Void: &fBool, } require.Equal(t, expected, resp.JSON201) @@ -662,20 +666,44 @@ func TestCredit(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode()) + lastReset := time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC).Truncate(time.Second) + expected := &api.LedgerBalance{ - LedgerID: ledgerID, - Subject: subject, - Metadata: ledgerMeta, - FeatureBalances: []credit.FeatureBalance{ + LastReset: &lastReset, + Subject: subject, + Metadata: &ledgerMeta, + FeatureBalances: []api.FeatureBalance{ { - Feature: feature, - Balance: 99, + Archived: feature.Archived, + CreatedAt: feature.CreatedAt, + Id: feature.Id, + MeterGroupByFilters: feature.MeterGroupByFilters, + MeterSlug: feature.MeterSlug, + Name: feature.Name, + UpdatedAt: feature.UpdatedAt, + Balance: 99, + Usage: 1, }, }, - GrantBalances: []credit.GrantBalance{ + GrantBalances: []api.LedgerGrantBalance{ { - Grant: grant, - Balance: 99, + Id: grant.Id, + Type: api.LedgerGrantTypeUsage, + Balance: 99, + Amount: 100, + EffectiveAt: grant.EffectiveAt, + Expiration: &api.LedgerGrantExpirationPeriod{ + Duration: "DAY", + Count: 1, + }, + ExpiresAt: grant.ExpiresAt, + FeatureID: *feature.Id, + ParentId: grant.ParentId, + Metadata: grant.Metadata, + Priority: grant.Priority, + Rollover: grant.Rollover, + UpdatedAt: grant.UpdatedAt, + CreatedAt: grant.CreatedAt, }, }, } @@ -702,7 +730,7 @@ func TestCredit(t *testing.T) { require.NotNil(t, featureListResp.JSON200) require.Len(t, *featureListResp.JSON200, 1) features := *featureListResp.JSON200 - featureId := features[0].ID + featureId := features[0].Id // Reset credit resetResp, err := client.ResetLedgerWithResponse(context.Background(), ledgerID, api.LedgerReset{ @@ -728,26 +756,31 @@ func TestCredit(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode(), "response body: %s", string(resp.Body)) + priority := 1 + fBool := false + grants := *resp.JSON200 expected := &[]api.LedgerGrantResponse{ { - ID: grants[0].ID, - ParentID: parentGrant.ID, - LedgerID: ledgerID, - Type: credit.GrantTypeUsage, - FeatureID: featureId, + Id: grants[0].Id, + ParentId: parentGrant.Id, + Type: api.LedgerGrantTypeUsage, + FeatureID: *featureId, Amount: 99, - Priority: 1, + Priority: &priority, EffectiveAt: effectiveAt, Rollover: &api.LedgerGrantRollover{ Type: credit.GrantRolloverTypeRemainingAmount, }, - Expiration: api.LedgerGrantExpirationPeriod{ + Expiration: &api.LedgerGrantExpirationPeriod{ Duration: "DAY", Count: 1, }, + LedgerID: grants[0].LedgerID, + ExpiresAt: grants[0].ExpiresAt, CreatedAt: grants[0].CreatedAt, UpdatedAt: grants[0].UpdatedAt, + Void: &fBool, }, } diff --git a/go.mod b/go.mod index 59f1ea2f0..a205223dc 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/openmeterio/openmeter go 1.22.0 +replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.123.0 + require ( entgo.io/ent v0.13.1 github.com/AppsFlyer/go-sundheit v0.5.0 @@ -13,7 +15,7 @@ require ( github.com/cloudevents/sdk-go/v2 v2.15.2 github.com/confluentinc/confluent-kafka-go/v2 v2.4.0 github.com/deepmap/oapi-codegen/v2 v2.1.0 - github.com/getkin/kin-openapi v0.124.0 + github.com/getkin/kin-openapi v0.123.0 github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 diff --git a/go.sum b/go.sum index a3197f885..3bb0fd0b1 100644 --- a/go.sum +++ b/go.sum @@ -487,8 +487,8 @@ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9 github.com/gdamore/optopia v0.2.0/go.mod h1:YKYEwo5C1Pa617H7NlPcmQXl+vG6YnSSNB44n8dNL0Q= github.com/generikvault/gvalstrings v0.0.0-20180926130504-471f38f0112a h1:J8FuFJ7K+Hiwkla2kT9fVIVix+EZhAlDsZwRlfFI3MA= github.com/generikvault/gvalstrings v0.0.0-20180926130504-471f38f0112a/go.mod h1:ms6iGk40n2YQrbM9Sr6onzwYBD1q5D0T5DQmcaye6uU= -github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= +github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/internal/credit/balance.go b/internal/credit/balance.go index 5d56b8998..0296ff3a3 100644 --- a/internal/credit/balance.go +++ b/internal/credit/balance.go @@ -26,4 +26,5 @@ type GrantBalance struct { type FeatureBalance struct { Feature Balance float64 `json:"balance"` + Usage float64 `json:"usage"` } diff --git a/internal/credit/creditdriver/balance.go b/internal/credit/creditdriver/balance.go index c4093a5d2..c5be6645e 100644 --- a/internal/credit/creditdriver/balance.go +++ b/internal/credit/creditdriver/balance.go @@ -7,6 +7,7 @@ import ( "github.com/openmeterio/openmeter/api" "github.com/openmeterio/openmeter/internal/credit" + "github.com/openmeterio/openmeter/pkg/convert" "github.com/openmeterio/openmeter/pkg/defaultx" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport" @@ -22,10 +23,10 @@ type GetLedgerBalanceRequest struct { Cutline time.Time } -type GetLedgerBalanceHandler httptransport.HandlerWithArgs[GetLedgerBalanceRequest, credit.Balance, GetLedgerBalaceHandlerParams] +type GetLedgerBalanceHandler httptransport.HandlerWithArgs[GetLedgerBalanceRequest, api.LedgerBalance, GetLedgerBalaceHandlerParams] func (b *builder) GetLedgerBalance() GetLedgerBalanceHandler { - return httptransport.NewHandlerWithArgs[GetLedgerBalanceRequest, credit.Balance, GetLedgerBalaceHandlerParams]( + return httptransport.NewHandlerWithArgs[GetLedgerBalanceRequest, api.LedgerBalance, GetLedgerBalaceHandlerParams]( func(ctx context.Context, r *http.Request, queryIn GetLedgerBalaceHandlerParams) (GetLedgerBalanceRequest, error) { ns, err := b.resolveNamespace(ctx) if err != nil { @@ -37,10 +38,15 @@ func (b *builder) GetLedgerBalance() GetLedgerBalanceHandler { Cutline: defaultx.WithDefault(queryIn.QueryParams.Time, time.Now()), }, nil }, - func(ctx context.Context, request GetLedgerBalanceRequest) (credit.Balance, error) { - return b.CreditConnector.GetBalance(ctx, request.LedgerID, request.Cutline) + func(ctx context.Context, request GetLedgerBalanceRequest) (api.LedgerBalance, error) { + balance, err := b.CreditConnector.GetBalance(ctx, request.LedgerID, request.Cutline) + if err != nil { + return api.LedgerBalance{}, err + } + highWatermark, err := b.CreditConnector.GetHighWatermark(ctx, request.LedgerID) + return mapBalanceToAPI(balance, highWatermark), err }, - commonhttp.JSONResponseEncoder[credit.Balance], + commonhttp.JSONResponseEncoder[api.LedgerBalance], httptransport.AppendOptions( b.Options, httptransport.WithOperationName("getLedgerBalance"), @@ -61,3 +67,61 @@ func (b *builder) GetLedgerBalance() GetLedgerBalanceHandler { }))..., ) } + +func mapBalanceToAPI(balance credit.Balance, highwatermark credit.HighWatermark) api.LedgerBalance { + var featureBalances []api.FeatureBalance = make([]api.FeatureBalance, 0, len(balance.FeatureBalances)) + for _, featureBalance := range balance.FeatureBalances { + featureBalances = append(featureBalances, mapFeatureBalanceToAPI(featureBalance)) + } + + var grantBalances []api.LedgerGrantBalance = make([]api.LedgerGrantBalance, 0, len(balance.GrantBalances)) + for _, grantBalance := range balance.GrantBalances { + grantBalances = append(grantBalances, mapGrantBalanceToAPI(grantBalance)) + } + + return api.LedgerBalance{ + FeatureBalances: featureBalances, + GrantBalances: grantBalances, + LastReset: &highwatermark.Time, + Metadata: &balance.Metadata, + Subject: balance.Subject, + } +} + +func mapFeatureBalanceToAPI(featureBalance credit.FeatureBalance) api.FeatureBalance { + return api.FeatureBalance{ + Archived: featureBalance.Archived, + CreatedAt: featureBalance.CreatedAt, + Id: convert.ToStringLike[credit.FeatureID, string](featureBalance.ID), + MeterGroupByFilters: featureBalance.MeterGroupByFilters, + MeterSlug: featureBalance.MeterSlug, + Name: featureBalance.Name, + UpdatedAt: featureBalance.UpdatedAt, + Usage: featureBalance.Usage, + Balance: featureBalance.Balance, + } +} + +func mapGrantBalanceToAPI(grantBalance credit.GrantBalance) api.LedgerGrantBalance { + priority := int(grantBalance.Priority) + + return api.LedgerGrantBalance{ + Amount: grantBalance.Amount, + Balance: grantBalance.Balance, + CreatedAt: grantBalance.CreatedAt, + EffectiveAt: grantBalance.EffectiveAt, + Expiration: &api.LedgerGrantExpirationPeriod{ + Count: int(grantBalance.Expiration.Count), + Duration: api.LedgerGrantExpirationPeriodDuration(grantBalance.Expiration.Duration), + }, + ExpiresAt: &grantBalance.ExpiresAt, + FeatureID: string(*grantBalance.FeatureID), + Id: convert.ToStringLike[credit.GrantID, string](grantBalance.ID), + Metadata: &grantBalance.Metadata, + ParentId: convert.ToStringLike[credit.GrantID, string](grantBalance.ParentID), + Priority: &priority, + Rollover: grantBalance.Rollover, + Type: api.LedgerGrantType(grantBalance.Type), + UpdatedAt: grantBalance.UpdatedAt, + } +} diff --git a/internal/credit/creditdriver/feature.go b/internal/credit/creditdriver/feature.go index d6d799dfe..a015c9230 100644 --- a/internal/credit/creditdriver/feature.go +++ b/internal/credit/creditdriver/feature.go @@ -85,6 +85,21 @@ func (b *builder) CreateFeature() CreateFeatureHandler { httptransport.AppendOptions( b.Options, httptransport.WithOperationName("createFeature"), + httptransport.WithErrorEncoder(func(ctx context.Context, err error, w http.ResponseWriter) bool { + if _, ok := err.(*credit.FeatureInvalidFiltersError); ok { + models.NewStatusProblem(ctx, err, http.StatusBadRequest).Respond(w) + return true + } + if _, ok := err.(*models.MeterNotFoundError); ok { + models.NewStatusProblem(ctx, err, http.StatusNotFound).Respond(w) + return true + } + if _, ok := err.(*credit.FeatureWithNameAlreadyExistsError); ok { + models.NewStatusProblem(ctx, err, http.StatusConflict).Respond(w) + return true + } + return false + }), )..., ) } diff --git a/internal/credit/creditdriver/grant.go b/internal/credit/creditdriver/grant.go index 570ae5e49..ee7b89636 100644 --- a/internal/credit/creditdriver/grant.go +++ b/internal/credit/creditdriver/grant.go @@ -7,15 +7,16 @@ import ( "github.com/openmeterio/openmeter/api" "github.com/openmeterio/openmeter/internal/credit" + "github.com/openmeterio/openmeter/pkg/convert" "github.com/openmeterio/openmeter/pkg/defaultx" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport" ) -type ListLedgerGrantsHandler httptransport.HandlerWithArgs[credit.ListGrantsParams, []credit.Grant, api.ListLedgerGrantsParams] +type ListLedgerGrantsHandler httptransport.HandlerWithArgs[credit.ListGrantsParams, []api.LedgerGrantResponse, api.ListLedgerGrantsParams] func (b *builder) ListLedgerGrants() ListLedgerGrantsHandler { - return httptransport.NewHandlerWithArgs[credit.ListGrantsParams, []credit.Grant, api.ListLedgerGrantsParams]( + return httptransport.NewHandlerWithArgs[credit.ListGrantsParams, []api.LedgerGrantResponse, api.ListLedgerGrantsParams]( func(ctx context.Context, r *http.Request, queryIn api.ListLedgerGrantsParams) (credit.ListGrantsParams, error) { ns, err := b.resolveNamespace(ctx) if err != nil { @@ -25,7 +26,7 @@ func (b *builder) ListLedgerGrants() ListLedgerGrantsHandler { request := credit.ListGrantsParams{ Namespace: ns, FromHighWatermark: true, - IncludeVoid: true, + IncludeVoid: defaultx.WithDefault(queryIn.IncludeVoids, false), Limit: defaultx.WithDefault(queryIn.Limit, DefaultLedgerQueryLimit), } @@ -34,7 +35,17 @@ func (b *builder) ListLedgerGrants() ListLedgerGrantsHandler { } return request, nil }, - b.CreditConnector.ListGrants, + func(ctx context.Context, request credit.ListGrantsParams) ([]api.LedgerGrantResponse, error) { + grants, err := b.CreditConnector.ListGrants(ctx, request) + if err != nil { + return nil, err + } + resp := make([]api.LedgerGrantResponse, 0, len(grants)) + for _, grant := range grants { + resp = append(resp, mapGrantWithBalanceToAPI(grant)) + } + return resp, nil + }, commonhttp.JSONResponseEncoder, httptransport.AppendOptions( b.Options, @@ -48,10 +59,10 @@ type ListLedgerGrantsByLedgerParams struct { Params api.ListLedgerGrantsByLedgerParams } -type ListLedgerGrantsByLedgerHandler httptransport.HandlerWithArgs[credit.ListGrantsParams, []credit.Grant, ListLedgerGrantsByLedgerParams] +type ListLedgerGrantsByLedgerHandler httptransport.HandlerWithArgs[credit.ListGrantsParams, []api.LedgerGrantResponse, ListLedgerGrantsByLedgerParams] func (b *builder) ListLedgerGrantsByLedger() ListLedgerGrantsByLedgerHandler { - return httptransport.NewHandlerWithArgs[credit.ListGrantsParams, []credit.Grant, ListLedgerGrantsByLedgerParams]( + return httptransport.NewHandlerWithArgs[credit.ListGrantsParams, []api.LedgerGrantResponse, ListLedgerGrantsByLedgerParams]( func(ctx context.Context, r *http.Request, queryIn ListLedgerGrantsByLedgerParams) (credit.ListGrantsParams, error) { ns, err := b.resolveNamespace(ctx) if err != nil { @@ -62,12 +73,22 @@ func (b *builder) ListLedgerGrantsByLedger() ListLedgerGrantsByLedgerHandler { Namespace: ns, LedgerIDs: []credit.LedgerID{queryIn.LedgerID}, FromHighWatermark: true, - IncludeVoid: true, + IncludeVoid: defaultx.WithDefault(queryIn.Params.IncludeVoids, false), Limit: defaultx.WithDefault(queryIn.Params.Limit, DefaultLedgerQueryLimit), } return request, nil }, - b.CreditConnector.ListGrants, + func(ctx context.Context, request credit.ListGrantsParams) ([]api.LedgerGrantResponse, error) { + grants, err := b.CreditConnector.ListGrants(ctx, request) + if err != nil { + return nil, err + } + resp := make([]api.LedgerGrantResponse, 0, len(grants)) + for _, grant := range grants { + resp = append(resp, mapGrantToAPI(grant)) + } + return resp, nil + }, commonhttp.JSONResponseEncoder, httptransport.AppendOptions( b.Options, @@ -91,7 +112,7 @@ func (b *builder) CreateLedgerGrant() CreateLedgerGrantHandler { return grant, err } - _, err = b.CreditConnector.GetFeature(ctx, credit.NewNamespacedFeatureID(ns, *grant.FeatureID)) + feature, err := b.CreditConnector.GetFeature(ctx, credit.NewNamespacedFeatureID(ns, *grant.FeatureID)) if err != nil { if _, ok := err.(*credit.FeatureNotFoundError); ok { return grant, commonhttp.NewHTTPError( @@ -102,6 +123,13 @@ func (b *builder) CreateLedgerGrant() CreateLedgerGrantHandler { return grant, err } + if feature.Archived != nil && *feature.Archived { + return grant, commonhttp.NewHTTPError( + http.StatusBadRequest, + fmt.Errorf("feature is archived: %s", *grant.FeatureID), + ) + } + grant.LedgerID = arg grant.Namespace = ns return grant, nil @@ -249,3 +277,31 @@ func (b *builder) GetLedgerGrant() GetLedgerGrantHandler { )..., ) } + +func mapGrantToAPI(grant credit.Grant) api.LedgerGrantResponse { + return api.LedgerGrantResponse{ + Amount: grant.Amount, + CreatedAt: grant.CreatedAt, + EffectiveAt: grant.EffectiveAt, + Expiration: &api.LedgerGrantExpirationPeriod{ + Count: int(grant.Expiration.Count), + Duration: api.LedgerGrantExpirationPeriodDuration(grant.Expiration.Duration), + }, + ExpiresAt: &grant.ExpiresAt, + FeatureID: string(defaultx.WithDefault(grant.FeatureID, credit.FeatureID(""))), + Id: (*string)(grant.ID), + Metadata: &grant.Metadata, + ParentId: (*string)(grant.ParentID), + Priority: convert.ToPointer(int(grant.Priority)), + Rollover: grant.Rollover, + Type: api.LedgerGrantType(grant.Type), + UpdatedAt: grant.UpdatedAt, + Void: &grant.Void, + } +} + +func mapGrantWithBalanceToAPI(grant credit.Grant) api.LedgerGrantResponse { + res := mapGrantToAPI(grant) + res.LedgerID = string(grant.LedgerID) + return res +} diff --git a/internal/credit/creditdriver/ledger.go b/internal/credit/creditdriver/ledger.go index 166f11ebc..6d6ff04d7 100644 --- a/internal/credit/creditdriver/ledger.go +++ b/internal/credit/creditdriver/ledger.go @@ -8,6 +8,7 @@ import ( "github.com/openmeterio/openmeter/api" "github.com/openmeterio/openmeter/internal/credit" + "github.com/openmeterio/openmeter/pkg/convert" "github.com/openmeterio/openmeter/pkg/defaultx" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport" @@ -94,10 +95,10 @@ type GetLedgerHistoryRequest struct { LedgerID api.LedgerID } -type GetLedgerHistoryHandler httptransport.HandlerWithArgs[GetLedgerHistoryRequest, credit.LedgerEntryList, GetLedgerHistoryRequest] +type GetLedgerHistoryHandler httptransport.HandlerWithArgs[GetLedgerHistoryRequest, []api.LedgerEntry, GetLedgerHistoryRequest] func (b *builder) GetLedgerHistory() GetLedgerHistoryHandler { - return httptransport.NewHandlerWithArgs[GetLedgerHistoryRequest, credit.LedgerEntryList, GetLedgerHistoryRequest]( + return httptransport.NewHandlerWithArgs[GetLedgerHistoryRequest, []api.LedgerEntry, GetLedgerHistoryRequest]( func(ctx context.Context, r *http.Request, in GetLedgerHistoryRequest) (GetLedgerHistoryRequest, error) { ns, err := b.resolveNamespace(ctx) if err != nil { @@ -107,8 +108,8 @@ func (b *builder) GetLedgerHistory() GetLedgerHistoryHandler { in.Namespace = ns return in, nil }, - func(ctx context.Context, req GetLedgerHistoryRequest) (credit.LedgerEntryList, error) { - return b.CreditConnector.GetHistory( + func(ctx context.Context, req GetLedgerHistoryRequest) ([]api.LedgerEntry, error) { + ledgerEntryList, err := b.CreditConnector.GetHistory( ctx, credit.NewNamespacedLedgerID(req.Namespace, req.LedgerID), req.From, @@ -118,6 +119,14 @@ func (b *builder) GetLedgerHistory() GetLedgerHistoryHandler { Offset: defaultx.WithDefault(req.Offset, 0), }, ) + if err != nil { + return nil, err + } + res := make([]api.LedgerEntry, 0, ledgerEntryList.Len()) + for _, entry := range ledgerEntryList.GetEntries() { + res = append(res, mapLedgerEntry(entry)) + } + return res, nil }, commonhttp.JSONResponseEncoder, httptransport.AppendOptions( @@ -126,3 +135,21 @@ func (b *builder) GetLedgerHistory() GetLedgerHistoryHandler { )..., ) } + +func mapLedgerEntry(entry credit.LedgerEntry) api.LedgerEntry { + var period *api.Period + if entry.Period != nil { + period = &api.Period{ + From: entry.Period.From, + To: entry.Period.To, + } + } + return api.LedgerEntry{ + Id: convert.ToStringLike[credit.GrantID, string](entry.ID), + Type: api.LedgerEntryType(entry.Type), + Time: entry.Time, + FeatureID: string(defaultx.WithDefault(entry.FeatureID, credit.FeatureID(""))), + Amount: defaultx.WithDefault(entry.Amount, 0), + Period: period, + } +} diff --git a/internal/credit/feature.go b/internal/credit/feature.go index 4c952579f..132b1c7f5 100644 --- a/internal/credit/feature.go +++ b/internal/credit/feature.go @@ -28,6 +28,26 @@ func (e *FeatureNotFoundError) Error() string { return fmt.Sprintf("feature not found: %s", e.ID) } +type FeatureInvalidFiltersError struct { + RequestedFilters map[string]string + MeterGroupByColumns []string +} + +func (e *FeatureInvalidFiltersError) Error() string { + return fmt.Sprintf("invalid filters for feature: %v, available columns: %v", e.RequestedFilters, e.MeterGroupByColumns) +} + +type FeatureWithNameAlreadyExistsError struct { + Name string + ID FeatureID +} + +func (e *FeatureWithNameAlreadyExistsError) Error() string { + // Is it an issue that we leak ID on another Feature here? + // Shouldn't be an isue as it's namespaced. + return fmt.Sprintf("feature %s with name %s already exists", e.ID, e.Name) +} + // Feature is a feature or service offered to a customer. // For example: CPU-Hours, Tokens, API Calls, etc. type Feature struct { diff --git a/internal/credit/postgres_connector/balance.go b/internal/credit/postgres_connector/balance.go index d8ae974c8..d6bb794da 100644 --- a/internal/credit/postgres_connector/balance.go +++ b/internal/credit/postgres_connector/balance.go @@ -297,11 +297,13 @@ func (a *PostgresConnector) getBalance( if featureBalance, ok := featureBalancesMap[featureId]; ok { featureBalance.Balance += grantBalance.Balance + featureBalance.Usage += grantBalance.Amount - grantBalance.Balance featureBalancesMap[featureId] = featureBalance } else { featureBalancesMap[featureId] = credit.FeatureBalance{ Feature: feature, Balance: grantBalance.Balance, + Usage: grantBalance.Amount - grantBalance.Balance, } } } diff --git a/internal/credit/postgres_connector/balance_test.go b/internal/credit/postgres_connector/balance_test.go index 2245de254..9736a4725 100644 --- a/internal/credit/postgres_connector/balance_test.go +++ b/internal/credit/postgres_connector/balance_test.go @@ -12,6 +12,7 @@ import ( "github.com/openmeterio/openmeter/api" "github.com/openmeterio/openmeter/internal/credit" "github.com/openmeterio/openmeter/internal/credit/postgres_connector/ent/db" + "github.com/openmeterio/openmeter/internal/credit/postgres_connector/test_helpers" meter_model "github.com/openmeterio/openmeter/internal/meter" "github.com/openmeterio/openmeter/internal/streaming" "github.com/openmeterio/openmeter/pkg/models" @@ -53,7 +54,7 @@ func TestPostgresConnectorBalances(t *testing.T) { description: "Should return balance", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn1) + feature := test_helpers.CreateFeature(t, connector, featureIn1) // We need to truncate the time to workaround pgx driver timezone issue // We also move it to the past to avoid timezone issues t1 := time.Now().Truncate(time.Hour * 24).Add(-time.Hour * 24) @@ -94,6 +95,7 @@ func TestPostgresConnectorBalances(t *testing.T) { { Feature: feature, Balance: 99, + Usage: 1, }, }, GrantBalances: []credit.GrantBalance{ @@ -112,7 +114,7 @@ func TestPostgresConnectorBalances(t *testing.T) { description: "Should return balance after reset", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn1) + feature := test_helpers.CreateFeature(t, connector, featureIn1) t1, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:01:00Z", time.UTC) t2, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:02:00Z", time.UTC) t3, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:03:00Z", time.UTC) @@ -164,6 +166,7 @@ func TestPostgresConnectorBalances(t *testing.T) { { Feature: feature, Balance: 99, + Usage: 1, }, }, GrantBalances: []credit.GrantBalance{ @@ -182,7 +185,7 @@ func TestPostgresConnectorBalances(t *testing.T) { description: "Should exclude voided grant from balance", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn1) + feature := test_helpers.CreateFeature(t, connector, featureIn1) t1, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:01:00Z", time.UTC) t2, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:02:00Z", time.UTC) @@ -237,7 +240,7 @@ func TestPostgresConnectorBalances(t *testing.T) { description: "Should burn down grant with highest priority first", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn1) + feature := test_helpers.CreateFeature(t, connector, featureIn1) t1, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:01:00Z", time.UTC) t2, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:02:00Z", time.UTC) @@ -296,6 +299,7 @@ func TestPostgresConnectorBalances(t *testing.T) { { Feature: feature, Balance: 90, + Usage: 20, }, }, GrantBalances: []credit.GrantBalance{ @@ -318,7 +322,7 @@ func TestPostgresConnectorBalances(t *testing.T) { description: "Should burn down grant that expires first", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn1) + feature := test_helpers.CreateFeature(t, connector, featureIn1) t1, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:01:00Z", time.UTC) t2, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:02:00Z", time.UTC) @@ -377,6 +381,7 @@ func TestPostgresConnectorBalances(t *testing.T) { { Feature: feature, Balance: 90, + Usage: 20, }, }, GrantBalances: []credit.GrantBalance{ @@ -399,8 +404,8 @@ func TestPostgresConnectorBalances(t *testing.T) { description: "Should burn down the right feature", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature1 := createFeature(t, connector, featureIn1) - feature2 := createFeature(t, connector, featureIn2) + feature1 := test_helpers.CreateFeature(t, connector, featureIn1) + feature2 := test_helpers.CreateFeature(t, connector, featureIn2) t1, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:01:00Z", time.UTC) t2, _ := time.ParseInLocation(time.RFC3339, "2024-01-01T00:02:00Z", time.UTC) @@ -462,10 +467,12 @@ func TestPostgresConnectorBalances(t *testing.T) { { Feature: feature1, Balance: 99, + Usage: 1, }, { Feature: feature2, Balance: 90, + Usage: 10, }, }), removeTimestampsFromFeatureBalances(balance.FeatureBalances), diff --git a/internal/credit/postgres_connector/feature.go b/internal/credit/postgres_connector/feature.go index 2f1606679..3c3893f53 100644 --- a/internal/credit/postgres_connector/feature.go +++ b/internal/credit/postgres_connector/feature.go @@ -9,6 +9,7 @@ import ( "github.com/openmeterio/openmeter/internal/credit/postgres_connector/ent/db" db_feature "github.com/openmeterio/openmeter/internal/credit/postgres_connector/ent/db/feature" "github.com/openmeterio/openmeter/pkg/convert" + "github.com/openmeterio/openmeter/pkg/models" ) // CreateFeature creates a feature. @@ -19,10 +20,46 @@ func (c *PostgresConnector) CreateFeature(ctx context.Context, featureIn credit. SetNamespace(featureIn.Namespace). SetMeterSlug(featureIn.MeterSlug) + // validate that the meter exists + meter, err := c.meterRepository.GetMeterByIDOrSlug(ctx, featureIn.Namespace, featureIn.MeterSlug) + if err != nil { + return credit.Feature{}, &models.MeterNotFoundError{MeterSlug: featureIn.MeterSlug} + } + if featureIn.MeterGroupByFilters != nil { + // validate that the MeterGroupByFilters point to actual meter groupbys + for filterProp := range *featureIn.MeterGroupByFilters { + if _, ok := meter.GroupBy[filterProp]; !ok { + meterGroupByColumns := make([]string, 0, len(meter.GroupBy)) + for k := range meter.GroupBy { + meterGroupByColumns = append(meterGroupByColumns, k) + } + return credit.Feature{}, &credit.FeatureInvalidFiltersError{ + RequestedFilters: *featureIn.MeterGroupByFilters, + MeterGroupByColumns: meterGroupByColumns, + } + } + } query.SetMeterGroupByFilters(*featureIn.MeterGroupByFilters) } + // validate that the feature name is uniq among active features + r, err := c.db.Feature.Query(). + Where(db_feature.Namespace(featureIn.Namespace)). + Where(db_feature.Name(featureIn.Name)). + Where(db_feature.Archived(false)). + All(ctx) + + if err != nil { + return credit.Feature{}, fmt.Errorf("failed to query for existing features: %w", err) + } + + if len(r) > 0 { + foundFeature := r[0] + return credit.Feature{}, + &credit.FeatureWithNameAlreadyExistsError{Name: featureIn.Name, ID: credit.FeatureID(foundFeature.ID)} + } + entity, err := query.Save(ctx) if err != nil { return credit.Feature{}, fmt.Errorf("failed to create feature: %w", err) @@ -81,7 +118,7 @@ func (c *PostgresConnector) ListFeatures(ctx context.Context, params credit.List return nil, fmt.Errorf("failed to list entities: %w", err) } - var list []credit.Feature + list := make([]credit.Feature, 0, len(entities)) for _, entity := range entities { feature := mapFeatureEntity(entity) list = append(list, feature) diff --git a/internal/credit/postgres_connector/grant.go b/internal/credit/postgres_connector/grant.go index 1480f85b9..2980e3d05 100644 --- a/internal/credit/postgres_connector/grant.go +++ b/internal/credit/postgres_connector/grant.go @@ -222,7 +222,7 @@ func (c *PostgresConnector) ListGrants(ctx context.Context, params credit.ListGr return nil, fmt.Errorf("failed to list grants: %w", err) } - var list []credit.Grant + list := make([]credit.Grant, 0, len(entities)) for _, entity := range entities { grant, err := mapGrantEntity(entity) if err != nil { @@ -272,6 +272,19 @@ func mapGrantEntity(entry *db.CreditEntry) (credit.Grant, error) { return credit.Grant{}, fmt.Errorf("entry type must be grant: %s", entry.EntryType) } + var rollover *credit.GrantRollover + if entry.RolloverType != nil && entry.RolloverMaxAmount != nil { + rollover = &credit.GrantRollover{ + Type: *entry.RolloverType, + MaxAmount: entry.RolloverMaxAmount, + } + } + + expiresAt := time.Time{} + if entry.ExpirationAt != nil { + expiresAt = entry.ExpirationAt.In(time.UTC) + } + grant := credit.Grant{ Namespace: entry.Namespace, ID: (*credit.GrantID)(&entry.ID), @@ -286,6 +299,8 @@ func mapGrantEntity(entry *db.CreditEntry) (credit.Grant, error) { Duration: *entry.ExpirationPeriodDuration, Count: *entry.ExpirationPeriodCount, }, + ExpiresAt: expiresAt, + Rollover: rollover, Metadata: entry.Metadata, Void: entry.EntryType == credit.EntryTypeVoidGrant, CreatedAt: convert.ToPointer(entry.CreatedAt.In(time.UTC)), diff --git a/internal/credit/postgres_connector/grant_test.go b/internal/credit/postgres_connector/grant_test.go index d9cb1a3e0..5cdb3003e 100644 --- a/internal/credit/postgres_connector/grant_test.go +++ b/internal/credit/postgres_connector/grant_test.go @@ -11,6 +11,7 @@ import ( "github.com/openmeterio/openmeter/internal/credit" "github.com/openmeterio/openmeter/internal/credit/postgres_connector/ent/db" + "github.com/openmeterio/openmeter/internal/credit/postgres_connector/test_helpers" "github.com/openmeterio/openmeter/internal/meter" "github.com/openmeterio/openmeter/pkg/models" ) @@ -44,7 +45,7 @@ func TestPostgresConnectorGrants(t *testing.T) { description: "Create a grant in the database", test: func(t *testing.T, connector credit.Connector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - p := createFeature(t, connector, features[0]) + p := test_helpers.CreateFeature(t, connector, features[0]) grant := credit.Grant{ Namespace: namespace, LedgerID: ledger.ID, @@ -68,7 +69,11 @@ func TestPostgresConnectorGrants(t *testing.T) { g.ID = nil assert.NotEmpty(t, *g.CreatedAt) assert.NotEmpty(t, *g.UpdatedAt) - assert.Equal(t, removeTimestampsFromGrant(g), grant) + + // Calculate ExpirationAt + grant.ExpiresAt = grant.Expiration.GetExpiration(grant.EffectiveAt) + + assert.Equal(t, test_helpers.RemoveTimestampsFromGrant(g), grant) }, }, { @@ -76,7 +81,7 @@ func TestPostgresConnectorGrants(t *testing.T) { description: "Void a grant in the database and get the latest grant for an ID", test: func(t *testing.T, connector credit.Connector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - p := createFeature(t, connector, features[0]) + p := test_helpers.CreateFeature(t, connector, features[0]) grant := credit.Grant{ Namespace: namespace, LedgerID: ledger.ID, @@ -95,7 +100,7 @@ func TestPostgresConnectorGrants(t *testing.T) { // should return the grant g2, err := connector.GetGrant(ctx, credit.NewNamespacedGrantID(namespace, *g.ID)) assert.NoError(t, err) - assertGrantsEqual(t, g, g2) + test_helpers.AssertGrantsEqual(t, g, g2) // So that in postgres the created_at and updated_at are different time.Sleep(1 * time.Millisecond) @@ -104,7 +109,7 @@ func TestPostgresConnectorGrants(t *testing.T) { // should return the void grant g3, err := connector.GetGrant(ctx, credit.NewNamespacedGrantID(namespace, *g.ID)) assert.NoError(t, err) - assertGrantsEqual(t, v, g3) + test_helpers.AssertGrantsEqual(t, v, g3) // assert count assert.Equal(t, 2, db_client.CreditEntry.Query().CountX(ctx)) // assert fields @@ -118,7 +123,7 @@ func TestPostgresConnectorGrants(t *testing.T) { assert.NotEmpty(t, *v.CreatedAt) assert.NotEmpty(t, *v.UpdatedAt) - assertGrantsEqual(t, v, grant) + test_helpers.AssertGrantsEqual(t, v, grant) }, }, { @@ -126,7 +131,7 @@ func TestPostgresConnectorGrants(t *testing.T) { description: "Void a grant that does not exist", test: func(t *testing.T, connector credit.Connector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - p := createFeature(t, connector, features[0]) + p := test_helpers.CreateFeature(t, connector, features[0]) id := credit.GrantID(ulid.MustNew(ulid.Now(), nil).String()) grant := credit.Grant{ Namespace: namespace, @@ -159,7 +164,7 @@ func TestPostgresConnectorGrants(t *testing.T) { assert.NoError(t, err) ctx := context.Background() - p := createFeature(t, connector, features[0]) + p := test_helpers.CreateFeature(t, connector, features[0]) grant_s1_1 := credit.Grant{ Namespace: namespace, LedgerID: ledger1.ID, @@ -215,8 +220,8 @@ func TestPostgresConnectorGrants(t *testing.T) { }) assert.NoError(t, err) assert.ElementsMatch(t, - removeTimestampsFromGrants([]credit.Grant{grant_s1_2, grant_s2_1}), - removeTimestampsFromGrants(gs), + test_helpers.RemoveTimestampsFromGrants([]credit.Grant{grant_s1_2, grant_s2_1}), + test_helpers.RemoveTimestampsFromGrants(gs), ) // ledger-1's non-void grants gs, err = connector.ListGrants(ctx, credit.ListGrantsParams{ @@ -225,8 +230,8 @@ func TestPostgresConnectorGrants(t *testing.T) { }) assert.NoError(t, err) assert.ElementsMatch(t, - removeTimestampsFromGrants([]credit.Grant{grant_s1_2}), - removeTimestampsFromGrants(gs), + test_helpers.RemoveTimestampsFromGrants([]credit.Grant{grant_s1_2}), + test_helpers.RemoveTimestampsFromGrants(gs), ) // all ledger' grants, including void grants gs, err = connector.ListGrants(ctx, credit.ListGrantsParams{ @@ -235,8 +240,8 @@ func TestPostgresConnectorGrants(t *testing.T) { }) assert.NoError(t, err) assert.ElementsMatch(t, - removeTimestampsFromGrants([]credit.Grant{grant_s1_2, grant_s2_1, void_grant_s1_1}), - removeTimestampsFromGrants(gs), + test_helpers.RemoveTimestampsFromGrants([]credit.Grant{grant_s1_2, grant_s2_1, void_grant_s1_1}), + test_helpers.RemoveTimestampsFromGrants(gs), ) }, }, @@ -270,29 +275,3 @@ func TestPostgresConnectorGrants(t *testing.T) { }) } } - -func createFeature(t *testing.T, connector credit.Connector, feature credit.Feature) credit.Feature { - ctx := context.Background() - p, err := connector.CreateFeature(ctx, feature) - if err != nil { - t.Error(err) - } - return p -} - -func removeTimestampsFromGrant(g credit.Grant) credit.Grant { - g.CreatedAt = nil - g.UpdatedAt = nil - return g -} - -func removeTimestampsFromGrants(gs []credit.Grant) []credit.Grant { - for i := range gs { - gs[i] = removeTimestampsFromGrant(gs[i]) - } - return gs -} - -func assertGrantsEqual(t *testing.T, expected, actual credit.Grant) { - assert.Equal(t, removeTimestampsFromGrant(expected), removeTimestampsFromGrant(actual)) -} diff --git a/internal/credit/postgres_connector/history_test.go b/internal/credit/postgres_connector/history_test.go index 4b8ef6e23..d603e9052 100644 --- a/internal/credit/postgres_connector/history_test.go +++ b/internal/credit/postgres_connector/history_test.go @@ -11,6 +11,7 @@ import ( "github.com/openmeterio/openmeter/internal/credit" "github.com/openmeterio/openmeter/internal/credit/postgres_connector/ent/db" + "github.com/openmeterio/openmeter/internal/credit/postgres_connector/test_helpers" meter_model "github.com/openmeterio/openmeter/internal/meter" "github.com/openmeterio/openmeter/pkg/models" ) @@ -41,7 +42,7 @@ func TestPostgresConnectorLedger(t *testing.T) { description: "Should return ledger entries", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn) + feature := test_helpers.CreateFeature(t, connector, featureIn) // We need to truncate the time to workaround pgx driver timezone issue // We also move it to the past to avoid timezone issues t1 := time.Now().Truncate(time.Hour * 24).Add(-time.Hour * 24).In(time.UTC) diff --git a/internal/credit/postgres_connector/reset_test.go b/internal/credit/postgres_connector/reset_test.go index 9adb92f08..5fffef2bd 100644 --- a/internal/credit/postgres_connector/reset_test.go +++ b/internal/credit/postgres_connector/reset_test.go @@ -11,6 +11,7 @@ import ( "github.com/openmeterio/openmeter/internal/credit" "github.com/openmeterio/openmeter/internal/credit/postgres_connector/ent/db" + "github.com/openmeterio/openmeter/internal/credit/postgres_connector/test_helpers" meter_model "github.com/openmeterio/openmeter/internal/meter" "github.com/openmeterio/openmeter/pkg/models" ) @@ -40,7 +41,7 @@ func TestPostgresConnectorReset(t *testing.T) { description: "Should move high watermark ahead", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn) + feature := test_helpers.CreateFeature(t, connector, featureIn) // We need to truncate the time to workaround pgx driver timezone issue // We also move it to the past to avoid timezone issues t1 := time.Now().In(time.UTC).Truncate(time.Hour * 24).Add(-time.Hour * 24) @@ -104,7 +105,7 @@ func TestPostgresConnectorReset(t *testing.T) { description: "Should rollover grants with original amount", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn) + feature := test_helpers.CreateFeature(t, connector, featureIn) // We need to truncate the time to workaround pgx driver timezone issue t1 := time.Now().Truncate(time.Hour * 24) t2 := t1.Add(time.Hour).Truncate(0) @@ -152,8 +153,8 @@ func TestPostgresConnectorReset(t *testing.T) { // Grants after reset should be the same as rollover grants assert.Equal(t, - removeTimestampsFromGrants(rolloverGrants), - removeTimestampsFromGrants(grants), + test_helpers.RemoveTimestampsFromGrants(rolloverGrants), + test_helpers.RemoveTimestampsFromGrants(grants), ) }, }, @@ -162,7 +163,7 @@ func TestPostgresConnectorReset(t *testing.T) { description: "Should rollover grants with remaining amount", test: func(t *testing.T, connector credit.Connector, streamingConnector *mockStreamingConnector, db_client *db.Client, ledger credit.Ledger) { ctx := context.Background() - feature := createFeature(t, connector, featureIn) + feature := test_helpers.CreateFeature(t, connector, featureIn) // We need to truncate the time to workaround pgx driver timezone issue t1 := time.Now().Truncate(time.Hour * 24) t2 := t1.Add(time.Hour).Truncate(0) @@ -217,8 +218,8 @@ func TestPostgresConnectorReset(t *testing.T) { // Assert: grants after reset should be the same as rollover grants assert.Equal(t, - removeTimestampsFromGrants(rolloverGrants), - removeTimestampsFromGrants(grants), + test_helpers.RemoveTimestampsFromGrants(rolloverGrants), + test_helpers.RemoveTimestampsFromGrants(grants), ) }, }, diff --git a/internal/credit/postgres_connector/test_helpers/helpers.go b/internal/credit/postgres_connector/test_helpers/helpers.go new file mode 100644 index 000000000..4f05fc04e --- /dev/null +++ b/internal/credit/postgres_connector/test_helpers/helpers.go @@ -0,0 +1,36 @@ +package test_helpers + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/openmeterio/openmeter/internal/credit" +) + +func CreateFeature(t *testing.T, connector credit.Connector, feature credit.Feature) credit.Feature { + ctx := context.Background() + p, err := connector.CreateFeature(ctx, feature) + if err != nil { + t.Error(err) + } + return p +} + +func RemoveTimestampsFromGrant(g credit.Grant) credit.Grant { + g.CreatedAt = nil + g.UpdatedAt = nil + return g +} + +func RemoveTimestampsFromGrants(gs []credit.Grant) []credit.Grant { + for i := range gs { + gs[i] = RemoveTimestampsFromGrant(gs[i]) + } + return gs +} + +func AssertGrantsEqual(t *testing.T, expected, actual credit.Grant) { + assert.Equal(t, RemoveTimestampsFromGrant(expected), RemoveTimestampsFromGrant(actual)) +} diff --git a/pkg/convert/ptr.go b/pkg/convert/ptr.go index 8ba073ed9..68f2e1e67 100644 --- a/pkg/convert/ptr.go +++ b/pkg/convert/ptr.go @@ -3,3 +3,10 @@ package convert func ToPointer[T any](value T) *T { return &value } + +func ToStringLike[Source, Dest ~string](value *Source) *Dest { + if value == nil { + return nil + } + return ToPointer(Dest(*value)) +}