Skip to content
Open
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
35 changes: 35 additions & 0 deletions api/spec/packages/aip/src/apps/operations.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ using TypeSpec.OpenAPI;

namespace Apps;

/**
* Filter options for listing apps.
*/
@friendlyName("ListAppsParamsFilter")
model ListAppsParamsFilter {
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
id?: Common.ULIDFieldFilter;
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
name?: Common.StringFieldFilter;
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
type?: Common.StringFieldFilterExact;
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
status?: Common.StringFieldFilterExact;
}

interface AppsOperations {
/**
* List installed apps.
Expand All @@ -24,6 +39,26 @@ interface AppsOperations {
@summary("List apps")
list(
...Common.PagePaginationQuery,

/**
* Sort apps returned in the response. Supported sort attributes are:
*
* - `id`
* - `created_at` (default)
*
* 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 apps returned in the response.
*
* To filter apps by name add the following query param: filter[name]=my-app
*/
Comment thread
borosr marked this conversation as resolved.
@query(#{ style: "deepObject", explode: true })
filter?: ListAppsParamsFilter,
): Shared.PagePaginatedResponse<App> | Common.ErrorResponses;

/**
Expand Down
1,341 changes: 696 additions & 645 deletions api/v3/api.gen.go

Large diffs are not rendered by default.

57 changes: 55 additions & 2 deletions api/v3/handlers/apps/list_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

api "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/app"
"github.com/openmeterio/openmeter/pkg/framework/commonhttp"
Expand Down Expand Up @@ -52,10 +54,61 @@ func (h *handler) ListApps() ListAppsHandler {
})
}

return ListAppsRequest{
req := ListAppsRequest{
Namespace: namespace,
Page: page,
}, nil
}

if params.Filter != nil {
id, err := filters.FromAPIFilterULID(params.Filter.Id)
if err != nil {
return ListAppsRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
{Field: "filter[id]", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
})
}
req.ID = id

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

appType, err := filters.FromAPIFilterStringExact(params.Filter.Type)
if err != nil {
return ListAppsRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
{Field: "filter[type]", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
})
}
req.Type = appType

status, err := filters.FromAPIFilterStringExact(params.Filter.Status)
if err != nil {
return ListAppsRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
{Field: "filter[status]", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
})
}
req.Status = status
}

if params.Sort != nil {
sort, err := request.ParseSortBy(*params.Sort)
if err != nil {
return ListAppsRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
apierrors.InvalidParameter{
Field: "sort",
Reason: err.Error(),
Source: apierrors.InvalidParamSourceQuery,
},
})
}
req.OrderBy = app.AppOrderBy(sort.Field)
req.Order = sort.Order.ToSortxOrder()
}

return req, nil
},
func(ctx context.Context, request ListAppsRequest) (ListAppsResponse, error) {
result, err := h.appService.ListApps(ctx, request)
Expand Down
38 changes: 38 additions & 0 deletions api/v3/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,31 @@ paths:
description: List installed apps.
parameters:
- $ref: '#/components/parameters/PagePaginationQuery'
- name: sort
in: query
required: false
description: |-
Sort apps returned in the response. Supported sort attributes are:

- `id`
- `created_at` (default)

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
description: |-
Filter apps returned in the response.

To filter apps by name add the following query param: filter[name]=my-app
schema:
$ref: '#/components/schemas/ListAppsParamsFilter'
style: deepObject
responses:
'200':
description: Page paginated response.
Expand Down Expand Up @@ -8198,6 +8223,19 @@ components:
$ref: '#/components/schemas/StringFieldFilterExact'
additionalProperties: false
description: Filter options for listing add-ons.
ListAppsParamsFilter:
type: object
properties:
id:
$ref: '#/components/schemas/ULIDFieldFilter'
name:
$ref: '#/components/schemas/StringFieldFilter'
type:
$ref: '#/components/schemas/StringFieldFilterExact'
status:
$ref: '#/components/schemas/StringFieldFilterExact'
additionalProperties: false
description: Filter options for listing apps.
ListChargesParamsFilter:
type: object
properties:
Expand Down
26 changes: 22 additions & 4 deletions openmeter/app/adapter/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
"github.com/openmeterio/openmeter/openmeter/ent/db"
appdb "github.com/openmeterio/openmeter/openmeter/ent/db/app"
appcustomerdb "github.com/openmeterio/openmeter/openmeter/ent/db/appcustomer"
"github.com/openmeterio/openmeter/pkg/filter"
"github.com/openmeterio/openmeter/pkg/framework/entutils"
"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 _ app.AppAdapter = (*adapter)(nil)
Expand Down Expand Up @@ -81,10 +83,6 @@ func (a *adapter) ListApps(ctx context.Context, params app.ListAppInput) (pagina
Query().
Where(appdb.Namespace(params.Namespace))

if params.Type != nil {
query = query.Where(appdb.Type(*params.Type))
}

// Do not return deleted apps by default
if !params.IncludeDeleted {
query = query.Where(appdb.DeletedAtIsNil())
Expand All @@ -107,6 +105,26 @@ func (a *adapter) ListApps(ctx context.Context, params app.ListAppInput) (pagina
query = query.Where(appdb.IDIn(appIDs...))
}

// Apply API filters
query = filter.ApplyToQuery(query, params.ID, appdb.FieldID)
query = filter.ApplyToQuery(query, params.Name, appdb.FieldName)
query = filter.ApplyToQuery(query, params.Type, appdb.FieldType)
query = filter.ApplyToQuery(query, params.Status, appdb.FieldStatus)

// Ordering
order := entutils.GetOrdering(sortx.OrderDefault)
if !params.Order.IsDefaultValue() {
order = entutils.GetOrdering(params.Order)
}
switch params.OrderBy {
case app.AppOrderByID:
query = query.Order(appdb.ByID(order...))
case app.AppOrderByCreatedAt:
fallthrough
default:
query = query.Order(appdb.ByCreatedAt(order...))
}
Comment thread
borosr marked this conversation as resolved.

response := pagination.Result[app.App]{
Page: params.Page,
}
Expand Down
6 changes: 5 additions & 1 deletion openmeter/app/adapter/customer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"time"

"entgo.io/ent/dialect/sql"
"github.com/samber/lo"

"github.com/openmeterio/openmeter/openmeter/app"
"github.com/openmeterio/openmeter/openmeter/ent/db"
appcustomerdb "github.com/openmeterio/openmeter/openmeter/ent/db/appcustomer"
"github.com/openmeterio/openmeter/pkg/filter"
"github.com/openmeterio/openmeter/pkg/framework/entutils"
"github.com/openmeterio/openmeter/pkg/framework/transaction"
"github.com/openmeterio/openmeter/pkg/models"
Expand All @@ -30,7 +32,9 @@ func (a *adapter) ListCustomerData(ctx context.Context, input app.ListCustomerIn
Page: input.Page,
Namespace: input.CustomerID.Namespace,
CustomerID: &input.CustomerID,
Type: input.Type,
}
if input.Type != nil {
listInput.Type = &filter.FilterString{Eq: lo.ToPtr(string(*input.Type))}
}

if input.AppID != nil {
Expand Down
71 changes: 67 additions & 4 deletions openmeter/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"context"
"errors"
"fmt"
"slices"

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

// App represents an installed app
Expand Down Expand Up @@ -145,16 +148,46 @@ func (i CreateAppInput) Validate() error {
return nil
}

// AppOrderBy represents the field to sort apps by
type AppOrderBy string

const (
AppOrderByID AppOrderBy = "id"
AppOrderByCreatedAt AppOrderBy = "created_at"
AppOrderByDefault AppOrderBy = AppOrderByCreatedAt
)

func (o AppOrderBy) Values() []AppOrderBy {
return []AppOrderBy{AppOrderByID, AppOrderByCreatedAt}
}

func (o AppOrderBy) Validate() error {
if !slices.Contains(o.Values(), o) {
return fmt.Errorf("invalid order by value: %s", o)
}

return nil
}

// ListAppInput is the input for listing installed apps
type ListAppInput struct {
Namespace string
pagination.Page

// Sort
OrderBy AppOrderBy
Order sortx.Order

// Internal-only narrowing
AppIDs []AppID
Type *AppType
IncludeDeleted bool
// Only list apps that has data for the given customer
CustomerID *customer.CustomerID
CustomerID *customer.CustomerID

// API filters
ID *filter.FilterULID
Name *filter.FilterString
Type *filter.FilterString
Status *filter.FilterString
}

func (i ListAppInput) Validate() error {
Expand Down Expand Up @@ -196,7 +229,37 @@ func (i ListAppInput) Validate() error {
}
}

return errors.Join(errs...)
if i.OrderBy != "" {
if err := i.OrderBy.Validate(); err != nil {
errs = append(errs, models.NewGenericValidationError(fmt.Errorf("invalid order by: %w", err)))
}
}

if i.ID != nil {
if err := i.ID.Validate(); err != nil {
errs = append(errs, models.NewGenericValidationError(fmt.Errorf("invalid id filter: %w", err)))
}
}

if i.Name != nil {
if err := i.Name.Validate(); err != nil {
errs = append(errs, models.NewGenericValidationError(fmt.Errorf("invalid name filter: %w", err)))
}
}

if i.Type != nil {
if err := i.Type.Validate(); err != nil {
errs = append(errs, models.NewGenericValidationError(fmt.Errorf("invalid type filter: %w", err)))
}
}

if i.Status != nil {
if err := i.Status.Validate(); err != nil {
errs = append(errs, models.NewGenericValidationError(fmt.Errorf("invalid status filter: %w", err)))
}
}

return models.NewNillableGenericValidationError(errors.Join(errs...))
}

// UpdateAppStatusInput is the input for updating an app status
Expand Down
3 changes: 2 additions & 1 deletion openmeter/app/sandbox/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/samber/lo"

"github.com/openmeterio/openmeter/openmeter/app"
"github.com/openmeterio/openmeter/pkg/filter"
"github.com/openmeterio/openmeter/pkg/models"
)

Expand Down Expand Up @@ -40,7 +41,7 @@ func AutoProvision(ctx context.Context, input AutoProvisionInput) (app.App, erro
// Get the sandbox app list
sandboxAppList, err := input.AppService.ListApps(ctx, app.ListAppInput{
Namespace: input.Namespace,
Type: lo.ToPtr(app.AppTypeSandbox),
Type: &filter.FilterString{Eq: lo.ToPtr(string(app.AppTypeSandbox))},
})
if err != nil {
return nil, fmt.Errorf("cannot list apps: %w", err)
Expand Down
Loading
Loading