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
1 change: 1 addition & 0 deletions api/spec/packages/aip/src/subscriptions/index.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import "./subscription.tsp";
import "./subscriptionaddon.tsp";
import "./operations.tsp";
import "./reference.tsp";
import "./subscriptionaddon.tsp";
14 changes: 14 additions & 0 deletions api/spec/packages/aip/src/subscriptions/operations.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,18 @@ interface SubscriptionAddonOperations {
@query(#{ name: "sort" })
sort?: Common.SortQuery,
): Shared.PagePaginatedResponse<SubscriptionAddon> | Common.NotFound | Common.ErrorResponses;

/**
* Get an add-on association for a subscription.
*/
@get
@route("/{subscriptionAddonId}")
@operationId("get-subscription-addon")
@summary("Get add-on association for subscription")
@extension(Shared.UnstableExtension, true)
@extension(Shared.InternalExtension, true)
getAddon(
@path subscriptionId: Shared.ULID,
@path subscriptionAddonId: Shared.ULID,
): Shared.GetResponse<SubscriptionAddon> | Common.ErrorResponses | Common.NotFound;
}
461 changes: 254 additions & 207 deletions api/v3/api.gen.go

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions api/v3/handlers/subscriptions/subscriptionaddons/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@ package subscriptionaddons

import (
"errors"
"fmt"
"time"

"github.com/samber/lo"

apiv3 "github.com/openmeterio/openmeter/api/v3"
"github.com/openmeterio/openmeter/api/v3/labels"
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
"github.com/openmeterio/openmeter/pkg/clock"
"github.com/openmeterio/openmeter/pkg/models"
"github.com/openmeterio/openmeter/pkg/timeutil"
)

func toAPISubscriptionAddon(addon subscriptionaddon.SubscriptionAddon) (apiv3.SubscriptionAddon, error) {
now := clock.Now()

// If no instance is active at `now`, quantity stays 0.
inst, _ := addon.GetInstanceAt(now)
inst, found := addon.GetInstanceAt(now)
if !found {
return apiv3.SubscriptionAddon{}, models.NewGenericNotFoundError(fmt.Errorf("no instance is active at %s", now.Format(time.RFC3339)))
}

pers := lo.Map(addon.GetInstances(), func(i subscriptionaddon.SubscriptionAddonInstance, _ int) timeutil.OpenPeriod {
return i.AsPeriod()
})

if len(pers) == 0 {
return apiv3.SubscriptionAddon{}, errors.New("no instances found for subscription addon")
return apiv3.SubscriptionAddon{}, models.NewGenericNotFoundError(errors.New("no instances found for subscription addon"))
}

union := lo.Reduce(pers, func(agg timeutil.OpenPeriod, item timeutil.OpenPeriod, _ int) timeutil.OpenPeriod {
Expand Down
59 changes: 59 additions & 0 deletions api/v3/handlers/subscriptions/subscriptionaddons/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package subscriptionaddons

import (
"context"
"errors"
"net/http"

apiv3 "github.com/openmeterio/openmeter/api/v3"
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
"github.com/openmeterio/openmeter/pkg/framework/commonhttp"
"github.com/openmeterio/openmeter/pkg/framework/transport/httptransport"
"github.com/openmeterio/openmeter/pkg/models"
)

type (
GetSubscriptionAddonRequest = subscriptionaddon.GetSubscriptionAddonInput
GetSubscriptionAddonParams struct {
SubscriptionID string
SubscriptionAddonID string
}
GetSubscriptionAddonResponse = apiv3.SubscriptionAddon
GetSubscriptionAddonHandler httptransport.HandlerWithArgs[GetSubscriptionAddonRequest, GetSubscriptionAddonResponse, GetSubscriptionAddonParams]
)

func (h *handler) GetSubscriptionAddon() GetSubscriptionAddonHandler {
return httptransport.NewHandlerWithArgs(
func(ctx context.Context, r *http.Request, params GetSubscriptionAddonParams) (GetSubscriptionAddonRequest, error) {
ns, err := h.resolveNamespace(ctx)
if err != nil {
return GetSubscriptionAddonRequest{}, err
}

return GetSubscriptionAddonRequest{
NamespacedID: models.NamespacedID{
Namespace: ns,
ID: params.SubscriptionAddonID,
},
SubscriptionID: params.SubscriptionID,
Comment thread
borosr marked this conversation as resolved.
}, nil
},
func(ctx context.Context, request GetSubscriptionAddonRequest) (GetSubscriptionAddonResponse, error) {
a, err := h.addonService.Get(ctx, request)
if err != nil {
return GetSubscriptionAddonResponse{}, err
}

if a == nil {
return GetSubscriptionAddonResponse{}, models.NewGenericNotFoundError(errors.New("subscription addon not found"))
}

return toAPISubscriptionAddon(*a)
},
commonhttp.JSONResponseEncoderWithStatus[GetSubscriptionAddonResponse](http.StatusOK),
httptransport.AppendOptions(
h.options,
httptransport.WithOperationName("get-subscription-addon"),
)...,
)
}
15 changes: 8 additions & 7 deletions api/v3/handlers/subscriptions/subscriptionaddons/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ import (

type Handler interface {
ListSubscriptionAddons() ListSubscriptionAddonsHandler
GetSubscriptionAddon() GetSubscriptionAddonHandler
}

type handler struct {
resolveNamespace func(ctx context.Context) (string, error)
subscriptionAddonService subscriptionaddon.Service
options []httptransport.HandlerOption
resolveNamespace func(ctx context.Context) (string, error)
addonService subscriptionaddon.Service
options []httptransport.HandlerOption
}

func New(
resolveNamespace func(ctx context.Context) (string, error),
subscriptionAddonService subscriptionaddon.Service,
addonService subscriptionaddon.Service,
options ...httptransport.HandlerOption,
) Handler {
return &handler{
resolveNamespace: resolveNamespace,
subscriptionAddonService: subscriptionAddonService,
options: options,
resolveNamespace: resolveNamespace,
addonService: addonService,
options: options,
}
}
6 changes: 3 additions & 3 deletions api/v3/handlers/subscriptions/subscriptionaddons/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,16 @@ func (h *handler) ListSubscriptionAddons() ListSubscriptionAddonsHandler {
}, nil
},
func(ctx context.Context, req ListSubscriptionAddonsRequest) (ListSubscriptionAddonsResponse, error) {
res, err := h.subscriptionAddonService.List(ctx, req.SubscriptionID.Namespace, req.Input)
res, err := h.addonService.List(ctx, req.SubscriptionID.Namespace, req.Input)
if err != nil {
return ListSubscriptionAddonsResponse{}, fmt.Errorf("failed to list subscription addons: %w", err)
return ListSubscriptionAddonsResponse{}, err
}

items := make([]apiv3.SubscriptionAddon, 0, len(res.Items))
for _, item := range res.Items {
converted, err := toAPISubscriptionAddon(item)
if err != nil {
return ListSubscriptionAddonsResponse{}, fmt.Errorf("failed to convert subscription addon: %w", err)
return ListSubscriptionAddonsResponse{}, err
}
items = append(items, converted)
}
Expand Down
35 changes: 35 additions & 0 deletions api/v3/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,41 @@ paths:
$ref: '#/components/responses/NotFound'
tags:
- OpenMeter Subscriptions
/openmeter/subscriptions/{subscriptionId}/addons/{subscriptionAddonId}:
get:
operationId: get-subscription-addon
summary: Get add-on association for subscription
description: Get an add-on association for a subscription.
parameters:
- name: subscriptionId
in: path
required: true
schema:
$ref: '#/components/schemas/ULID'
- name: subscriptionAddonId
in: path
required: true
schema:
$ref: '#/components/schemas/ULID'
responses:
'200':
description: SubscriptionAddon response.
content:
application/json:
schema:
$ref: '#/components/schemas/SubscriptionAddon'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
tags:
- OpenMeter Subscriptions
x-internal: true
x-unstable: true
/openmeter/subscriptions/{subscriptionId}/cancel:
post:
operationId: cancel-subscription
Expand Down
8 changes: 8 additions & 0 deletions api/v3/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,21 @@ func (s *Server) ChangeSubscription(w http.ResponseWriter, r *http.Request, subs
s.subscriptionsHandler.ChangeSubscription().With(subscriptionId).ServeHTTP(w, r)
}

// Subscription Addons
func (s *Server) ListSubscriptionAddons(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID, params api.ListSubscriptionAddonsParams) {
s.subscriptionAddonsHandler.ListSubscriptionAddons().With(subscriptionaddonshandler.ListSubscriptionAddonsParams{
SubscriptionID: subscriptionId,
Params: params,
}).ServeHTTP(w, r)
}

func (s *Server) GetSubscriptionAddon(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID, subscriptionAddonId api.ULID) {
s.subscriptionAddonsHandler.GetSubscriptionAddon().With(subscriptionaddonshandler.GetSubscriptionAddonParams{
SubscriptionID: subscriptionId,
SubscriptionAddonID: subscriptionAddonId,
}).ServeHTTP(w, r)
}

// Apps

func (s *Server) ListApps(w http.ResponseWriter, r *http.Request, params api.ListAppsParams) {
Expand Down
4 changes: 4 additions & 0 deletions api/v3/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ func (c *Config) Validate() error {
errs = append(errs, errors.New("addon service is required"))
}

if c.SubscriptionAddonService == nil {
errs = append(errs, errors.New("subscription addon service is required"))
}

return errors.Join(errs...)
}

Expand Down
2 changes: 1 addition & 1 deletion openmeter/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1680,7 +1680,7 @@ func (n NoopSubscriptionAddonService) ChangeQuantity(ctx context.Context, subscr
return nil, nil
}

func (n NoopSubscriptionAddonService) Get(ctx context.Context, addonId models.NamespacedID) (*subscriptionaddon.SubscriptionAddon, error) {
func (n NoopSubscriptionAddonService) Get(ctx context.Context, params subscriptionaddon.GetSubscriptionAddonInput) (*subscriptionaddon.SubscriptionAddon, error) {
return nil, nil
}

Expand Down
5 changes: 4 additions & 1 deletion openmeter/subscription/addon/http/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"github.com/openmeterio/openmeter/api"
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
"github.com/openmeterio/openmeter/pkg/framework/commonhttp"
"github.com/openmeterio/openmeter/pkg/framework/transport/httptransport"
"github.com/openmeterio/openmeter/pkg/models"
Expand Down Expand Up @@ -38,7 +39,9 @@ func (h *handler) GetSubscriptionAddon() GetSubscriptionAddonHandler {
}, nil
},
func(ctx context.Context, req GetSubscriptionAddonRequest) (GetSubscriptionAddonResponse, error) {
res, err := h.SubscriptionAddonService.Get(ctx, req.SubscriptionAddonID)
res, err := h.SubscriptionAddonService.Get(ctx, subscriptionaddon.GetSubscriptionAddonInput{
NamespacedID: req.SubscriptionAddonID,
})
if err != nil {
return GetSubscriptionAddonResponse{}, err
}
Expand Down
21 changes: 13 additions & 8 deletions openmeter/subscription/addon/repo/subscriptionaddon.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,23 @@ func (r *subscriptionAddonRepo) Create(ctx context.Context, namespace string, in
}

// Get retrieves a subscription addon by ID
func (r *subscriptionAddonRepo) Get(ctx context.Context, id models.NamespacedID) (*subscriptionaddon.SubscriptionAddon, error) {
func (r *subscriptionAddonRepo) Get(ctx context.Context, params subscriptionaddon.GetSubscriptionAddonInput) (*subscriptionaddon.SubscriptionAddon, error) {
return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionAddonRepo) (*subscriptionaddon.SubscriptionAddon, error) {
entity, err := querySubscriptionAddon(repo.db.SubscriptionAddon.Query()).
Where(
dbsubscriptionaddon.ID(id.ID),
dbsubscriptionaddon.Namespace(id.Namespace),
).
Only(ctx)
query := querySubscriptionAddon(repo.db.SubscriptionAddon.Query())

query = query.Where(
dbsubscriptionaddon.ID(params.NamespacedID.ID),
dbsubscriptionaddon.Namespace(params.NamespacedID.Namespace),
)
if params.SubscriptionID != "" {
query = query.Where(dbsubscriptionaddon.SubscriptionID(params.SubscriptionID))
}

entity, err := query.Only(ctx)
if err != nil {
if db.IsNotFound(err) {
return nil, models.NewGenericNotFoundError(
fmt.Errorf("subscription addon %s not found", id.ID),
fmt.Errorf("subscription addon %s not found", params.NamespacedID.ID),
)
}

Expand Down
2 changes: 1 addition & 1 deletion openmeter/subscription/addon/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type ListSubscriptionAddonRepositoryInput struct {

type SubscriptionAddonRepository interface {
Create(ctx context.Context, namespace string, input CreateSubscriptionAddonRepositoryInput) (*models.NamespacedID, error)
Get(ctx context.Context, id models.NamespacedID) (*SubscriptionAddon, error)
Get(ctx context.Context, params GetSubscriptionAddonInput) (*SubscriptionAddon, error)
List(ctx context.Context, namespace string, filter ListSubscriptionAddonRepositoryInput) (pagination.Result[SubscriptionAddon], error)
}

Expand Down
21 changes: 20 additions & 1 deletion openmeter/subscription/addon/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (f OrderBy) Validate() error {

type Service interface {
Create(ctx context.Context, namespace string, input CreateSubscriptionAddonInput) (*SubscriptionAddon, error)
Get(ctx context.Context, id models.NamespacedID) (*SubscriptionAddon, error)
Get(ctx context.Context, id GetSubscriptionAddonInput) (*SubscriptionAddon, error)
List(ctx context.Context, namespace string, input ListSubscriptionAddonsInput) (pagination.Result[SubscriptionAddon], error)

ChangeQuantity(ctx context.Context, id models.NamespacedID, input CreateSubscriptionAddonQuantityInput) (*SubscriptionAddon, error)
Expand Down Expand Up @@ -67,3 +67,22 @@ func (i ListSubscriptionAddonsInput) Validate() error {

return errors.Join(errs...)
}

// GetSubscriptionAddonInput defines the input parameters for fetching subscription add-on assignment either by SubscriptionAddon.ID or
// by the subscription and add-on identifiers.
type GetSubscriptionAddonInput struct {
models.NamespacedID

// SubscriptionID
SubscriptionID string `json:"subscriptionId"`
}
Comment thread
borosr marked this conversation as resolved.

func (i GetSubscriptionAddonInput) Validate() error {
var errs []error

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

return models.NewNillableGenericValidationError(errors.Join(errs...))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4 changes: 3 additions & 1 deletion openmeter/subscription/addon/service/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,9 @@ func TestAddonServiceCreate(t *testing.T) {
require.Nil(t, err)

// Now, let's fetch the subscription addon
subAdd2, err := deps.SubscriptionAddonService.Get(context.Background(), subAdd1.NamespacedID)
subAdd2, err := deps.SubscriptionAddonService.Get(t.Context(), subscriptionaddon.GetSubscriptionAddonInput{
NamespacedID: subAdd1.NamespacedID,
})
require.Nil(t, err)

t.Run("Should create addon as specified", func(t *testing.T) {
Expand Down
Loading
Loading