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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions api/spec/packages/aip/src/currencies/operations.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ namespace Currencies;
*/
@friendlyName("ListCurrenciesParamsFilter")
model ListCurrenciesParamsFilter {
/**
* Filter currencies by type.
*/
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
type?: CurrencyType;
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
code?: Common.StringFieldFilter;
}

interface CurrenciesOperations {
Expand All @@ -35,6 +35,18 @@ interface CurrenciesOperations {
list(
...Common.PagePaginationQuery,

/**
* Sort currencies returned in the response. Supported sort attributes are:
*
* - `code` (default)
* - `name`
*
* The `asc` suffix is optional as the default sort order is ascending. The `desc`
* suffix is used to specify a descending order.
*/
@query(#{ name: "sort" })
sort?: Common.SortQuery,

/**
* Filter currencies returned in the response.
*
Expand Down
458 changes: 240 additions & 218 deletions api/v3/api.gen.go

Large diffs are not rendered by default.

46 changes: 37 additions & 9 deletions api/v3/handlers/currencies/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import (

v3 "github.com/openmeterio/openmeter/api/v3"
"github.com/openmeterio/openmeter/api/v3/apierrors"
"github.com/openmeterio/openmeter/api/v3/filters"
"github.com/openmeterio/openmeter/api/v3/request"
"github.com/openmeterio/openmeter/api/v3/response"
"github.com/openmeterio/openmeter/openmeter/currencies"
"github.com/openmeterio/openmeter/pkg/framework/commonhttp"
"github.com/openmeterio/openmeter/pkg/framework/transport/httptransport"
"github.com/openmeterio/openmeter/pkg/pagination"
"github.com/openmeterio/openmeter/pkg/sortx"
)

type (
Expand Down Expand Up @@ -49,17 +52,42 @@ func (h *handler) ListCurrencies() ListCurrenciesHandler {
})
}

var filterType *currencies.CurrencyType
if params.Filter != nil && params.Filter.Type != nil {
ft := FromAPIBillingCurrencyType(*params.Filter.Type)
filterType = &ft
var orderBy string
var order sortx.Order
if params.Sort != nil {
sort, err := request.ParseSortBy(*params.Sort)
if err != nil {
return ListCurrenciesRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
{Field: "sort", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
})
}
orderBy = sort.Field
order = sort.Order.ToSortxOrder()
}

req := ListCurrenciesRequest{
Page: page,
Namespace: ns,
OrderBy: currencies.OrderBy(orderBy),
Order: order,
}

if params.Filter != nil {
if params.Filter.Type != nil {
ft := FromAPIBillingCurrencyType(*params.Filter.Type)
req.FilterType = &ft
}

code, err := filters.FromAPIFilterString(params.Filter.Code)
if err != nil {
return ListCurrenciesRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
{Field: "filter[code]", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
})
}
req.Code = code
}

return ListCurrenciesRequest{
Page: page,
Namespace: ns,
FilterType: filterType,
}, nil
return req, nil
},
func(ctx context.Context, request ListCurrenciesRequest) (ListCurrenciesResponse, error) {
result, err := h.currencyService.ListCurrencies(ctx, request)
Expand Down
21 changes: 18 additions & 3 deletions api/v3/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,21 @@ paths:
description: List currencies supported by the billing system.
parameters:
- $ref: '#/components/parameters/PagePaginationQuery'
- name: sort
in: query
required: false
description: |-
Sort currencies returned in the response. Supported sort attributes are:

- `code` (default)
- `name`

The `asc` suffix is optional as the default sort order is ascending. The `desc`
suffix is used to specify a descending order.
schema:
$ref: '#/components/schemas/SortQuery'
explode: false
style: form
- name: filter
in: query
required: false
Expand Down Expand Up @@ -8232,9 +8247,9 @@ components:
type: object
properties:
type:
allOf:
- $ref: '#/components/schemas/BillingCurrencyType'
description: Filter currencies by type.
$ref: '#/components/schemas/BillingCurrencyType'
code:
$ref: '#/components/schemas/StringFieldFilter'
additionalProperties: false
description: Filter options for listing currencies.
ListCustomerEntitlementAccessResponseData:
Expand Down
18 changes: 16 additions & 2 deletions openmeter/currencies/adapter/currencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import (
"github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis"
"github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency"
"github.com/openmeterio/openmeter/pkg/currencyx"
"github.com/openmeterio/openmeter/pkg/filter"
"github.com/openmeterio/openmeter/pkg/framework/entutils"
"github.com/openmeterio/openmeter/pkg/models"
"github.com/openmeterio/openmeter/pkg/pagination"
"github.com/openmeterio/openmeter/pkg/sortx"
)

var _ currencies.Adapter = (*adapter)(nil)
Expand Down Expand Up @@ -44,8 +46,20 @@ func mapCostBasisFromDB(c *entdb.CurrencyCostBasis) currencies.CostBasis {
func (a *adapter) ListCustomCurrencies(ctx context.Context, params currencies.ListCurrenciesInput) (pagination.Result[currencies.Currency], error) {
return entutils.TransactingRepo(ctx, a, func(ctx context.Context, tx *adapter) (pagination.Result[currencies.Currency], error) {
q := a.db.CustomCurrency.Query().
Where(customcurrency.Namespace(params.Namespace)).
Order(entdb.Asc(customcurrency.FieldCode))
Where(customcurrency.Namespace(params.Namespace))

q = filter.ApplyToQuery(q, params.Code, customcurrency.FieldCode)

order := entutils.GetOrdering(sortx.OrderDefault)
if !params.Order.IsDefaultValue() {
order = entutils.GetOrdering(params.Order)
}
switch params.OrderBy {
case currencies.OrderByName:
q = q.Order(customcurrency.ByName(order...))
default:
q = q.Order(customcurrency.ByCode(order...))
}

total, err := q.Count(ctx)
if err != nil {
Expand Down
33 changes: 33 additions & 0 deletions openmeter/currencies/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (

"github.com/alpacahq/alpacadecimal"

"github.com/openmeterio/openmeter/pkg/filter"
"github.com/openmeterio/openmeter/pkg/models"
"github.com/openmeterio/openmeter/pkg/pagination"
"github.com/openmeterio/openmeter/pkg/sortx"
)

type Currency struct {
Expand All @@ -19,6 +21,22 @@ type Currency struct {
Symbol string `json:"symbol"`
}

// OrderBy specifies the field to sort currencies by.
type OrderBy string

const (
OrderByCode OrderBy = "code"
OrderByName OrderBy = "name"
)

func (o OrderBy) Validate() error {
switch o {
case OrderByCode, OrderByName, "":
return nil
}
return fmt.Errorf("invalid order by: %s", o)
}

var _ models.Validator = (*ListCurrenciesInput)(nil)

type ListCurrenciesInput struct {
Expand All @@ -28,6 +46,11 @@ type ListCurrenciesInput struct {

// FilterType filters currencies by type: "custom" or "fiat". Nil means no filter.
FilterType *CurrencyType `json:"filter_type,omitempty"`
// Code filters currencies by code field. Nil means no filter.
Code *filter.FilterString `json:"code,omitempty"`

OrderBy OrderBy
Order sortx.Order
}

func (i ListCurrenciesInput) Validate() error {
Expand All @@ -43,6 +66,16 @@ func (i ListCurrenciesInput) Validate() error {
}
}

if i.Code != nil {
if err := i.Code.Validate(); err != nil {
errs = append(errs, fmt.Errorf("code: %w", err))
}
}

if err := i.OrderBy.Validate(); err != nil {
errs = append(errs, err)
}

return errors.Join(errs...)
}

Expand Down
30 changes: 27 additions & 3 deletions openmeter/currencies/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package service
import (
"context"
"fmt"
"slices"
"strings"
"time"

"github.com/invopop/gobl/currency"
Expand All @@ -12,6 +14,7 @@ import (
"github.com/openmeterio/openmeter/pkg/framework/transaction"
"github.com/openmeterio/openmeter/pkg/models"
"github.com/openmeterio/openmeter/pkg/pagination"
"github.com/openmeterio/openmeter/pkg/sortx"
)

var _ currencies.CurrencyService = (*Service)(nil)
Expand Down Expand Up @@ -55,10 +58,18 @@ func (s *Service) ListCurrencies(ctx context.Context, params currencies.ListCurr
}

if includeFiat {
for _, def := range lo.Filter(currency.Definitions(), func(def *currency.Def, _ int) bool {
matchCode := params.Code.LoFilterPredicate()
filteredMatchCode, err := lo.FilterErr(currency.Definitions(), func(def *currency.Def, _ int) (bool, error) {
// NOTE: this filters out non-iso currencies such as crypto
return def.ISONumeric != ""
}) {
if def.ISONumeric == "" {
return false, nil
}
return matchCode(def.ISOCode.String(), 0)
})
if err != nil {
return pagination.Result[currencies.Currency]{}, fmt.Errorf("filtering fiat currencies by code: %w", err)
}
for _, def := range filteredMatchCode {
items = append(items, currencies.Currency{
Code: def.ISOCode.String(),
Name: def.Name,
Expand All @@ -67,6 +78,19 @@ func (s *Service) ListCurrencies(ctx context.Context, params currencies.ListCurr
}
}

slices.SortFunc(items, func(a, b currencies.Currency) int {
result := 0
if params.OrderBy == currencies.OrderByName {
result = strings.Compare(a.Name, b.Name)
} else {
result = strings.Compare(a.Code, b.Code)
}
if params.Order == sortx.OrderDesc {
return -result
}
return result
})

total := len(items)

pageSize := params.Page.PageSize
Expand Down
Loading
Loading