Skip to content

Commit f095902

Browse files
committed
feat(subscription_addon): assign addon to a subscription endpoint
1 parent 1175288 commit f095902

10 files changed

Lines changed: 1026 additions & 720 deletions

File tree

api/spec/packages/aip/src/subscriptions/operations.tsp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,20 @@ interface SubscriptionsOperations {
165165

166166
interface SubscriptionAddonOperations {
167167
/**
168-
* List the addons of a subscription.
168+
* Add add-on to a subscription.
169+
*/
170+
@post
171+
@operationId("create-subscription-addon")
172+
@summary("Create a new subscription add-on")
173+
@extension(Shared.UnstableExtension, true)
174+
@extension(Shared.InternalExtension, true)
175+
create(
176+
@path subscriptionId: Shared.ULID,
177+
@body request: Shared.CreateRequest<SubscriptionAddon>,
178+
): Shared.CreateResponse<SubscriptionAddon> | Common.ErrorResponses | Common.NotFound;
179+
180+
/**
181+
* List the add-ons of a subscription.
169182
*/
170183
@get
171184
@operationId("list-subscription-addons")

api/spec/packages/aip/src/subscriptions/subscriptionaddon.tsp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,14 @@ model SubscriptionAddon {
4343
*/
4444
@visibility(Lifecycle.Read)
4545
active_to?: Shared.DateTime;
46+
47+
/**
48+
* The timing of the operation. After the create or update, a new entry will be
49+
* created in the timeline.
50+
*/
51+
@summary("Timing")
52+
@visibility(Lifecycle.Create, Lifecycle.Update)
53+
timing: SubscriptionEditTiming;
54+
55+
// TODO consider adding rate cards
4656
}

api/v3/api.gen.go

Lines changed: 723 additions & 651 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v3/handlers/subscriptions/subscriptionaddons/convert.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,38 @@ import (
88
"github.com/samber/lo"
99

1010
apiv3 "github.com/openmeterio/openmeter/api/v3"
11+
"github.com/openmeterio/openmeter/api/v3/handlers/subscriptions"
1112
"github.com/openmeterio/openmeter/api/v3/labels"
1213
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
14+
subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow"
1315
"github.com/openmeterio/openmeter/pkg/clock"
1416
"github.com/openmeterio/openmeter/pkg/models"
1517
"github.com/openmeterio/openmeter/pkg/timeutil"
1618
)
1719

20+
func mapCreateSubscriptionAddonRequestToInput(req apiv3.CreateSubscriptionAddonRequest) (subscriptionworkflow.AddAddonWorkflowInput, error) {
21+
timing, err := subscriptions.FromAPIBillingSubscriptionEditTiming(req.Timing)
22+
if err != nil {
23+
return subscriptionworkflow.AddAddonWorkflowInput{}, fmt.Errorf("failed to cast Timing: %w", err)
24+
}
25+
26+
meta, err := labels.ToMetadata(req.Labels)
27+
if err != nil {
28+
return subscriptionworkflow.AddAddonWorkflowInput{}, err
29+
}
30+
31+
r := subscriptionworkflow.AddAddonWorkflowInput{
32+
AddonID: req.Addon.Id,
33+
InitialQuantity: req.Quantity,
34+
Timing: timing,
35+
MetadataModel: models.MetadataModel{
36+
Metadata: meta,
37+
},
38+
}
39+
40+
return r, nil
41+
}
42+
1843
func toAPISubscriptionAddon(addon subscriptionaddon.SubscriptionAddon) (apiv3.SubscriptionAddon, error) {
1944
now := clock.Now()
2045

@@ -42,7 +67,7 @@ func toAPISubscriptionAddon(addon subscriptionaddon.SubscriptionAddon) (apiv3.Su
4267
CreatedAt: addon.CreatedAt,
4368
UpdatedAt: addon.UpdatedAt,
4469
DeletedAt: addon.DeletedAt,
45-
Addon: apiv3.AddonReferenceItem{
70+
Addon: apiv3.AddonReference{
4671
Id: addon.Addon.ID,
4772
},
4873
Labels: labels.FromMetadata(addon.Metadata),
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package subscriptionaddons
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
apiv3 "github.com/openmeterio/openmeter/api/v3"
8+
"github.com/openmeterio/openmeter/api/v3/apierrors"
9+
"github.com/openmeterio/openmeter/api/v3/request"
10+
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
11+
subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow"
12+
"github.com/openmeterio/openmeter/pkg/framework/commonhttp"
13+
"github.com/openmeterio/openmeter/pkg/framework/transport/httptransport"
14+
"github.com/openmeterio/openmeter/pkg/models"
15+
"github.com/samber/lo"
16+
)
17+
18+
type (
19+
CreateSubscriptionAddonRequest = struct {
20+
SubscriptionID models.NamespacedID
21+
AddonInput subscriptionworkflow.AddAddonWorkflowInput
22+
}
23+
CreateSubscriptionAddonResponse = apiv3.SubscriptionAddon
24+
CreateSubscriptionAddonParams = string
25+
CreateSubscriptionAddonHandler = httptransport.HandlerWithArgs[CreateSubscriptionAddonRequest, CreateSubscriptionAddonResponse, CreateSubscriptionAddonParams]
26+
)
27+
28+
func (h *handler) CreateSubscriptionAddon() CreateSubscriptionAddonHandler {
29+
return httptransport.NewHandlerWithArgs(
30+
func(ctx context.Context, r *http.Request, subscriptionID CreateSubscriptionAddonParams) (CreateSubscriptionAddonRequest, error) {
31+
body := apiv3.CreateSubscriptionAddonRequest{}
32+
if err := request.ParseBody(r, &body); err != nil {
33+
return CreateSubscriptionAddonRequest{}, err
34+
}
35+
36+
ns, err := h.resolveNamespace(ctx)
37+
if err != nil {
38+
return CreateSubscriptionAddonRequest{}, err
39+
}
40+
41+
addonInput, err := mapCreateSubscriptionAddonRequestToInput(body)
42+
if err != nil {
43+
return CreateSubscriptionAddonRequest{}, err
44+
}
45+
46+
return CreateSubscriptionAddonRequest{
47+
SubscriptionID: models.NamespacedID{
48+
Namespace: ns,
49+
ID: subscriptionID,
50+
},
51+
AddonInput: addonInput,
52+
}, nil
53+
},
54+
func(ctx context.Context, request CreateSubscriptionAddonRequest) (CreateSubscriptionAddonResponse, error) {
55+
subsAdds, err := h.addonService.List(ctx, request.SubscriptionID.Namespace, subscriptionaddon.ListSubscriptionAddonsInput{
56+
SubscriptionID: request.SubscriptionID.ID,
57+
})
58+
if err != nil {
59+
return CreateSubscriptionAddonResponse{}, err
60+
}
61+
62+
var added subscriptionaddon.SubscriptionAddon
63+
64+
// If the addon is already present, we'll change the quantity instead as a convenience
65+
if sAdd, ok := lo.Find(subsAdds.Items, func(subAdd subscriptionaddon.SubscriptionAddon) bool {
66+
return subAdd.Addon.ID == request.AddonInput.AddonID
67+
}); ok {
68+
_, added, err = h.SubscriptionWorkflowService.ChangeAddonQuantity(ctx, request.SubscriptionID, subscriptionworkflow.ChangeAddonQuantityWorkflowInput{
69+
SubscriptionAddonID: sAdd.NamespacedID,
70+
Quantity: request.AddonInput.InitialQuantity,
71+
Timing: request.AddonInput.Timing,
72+
})
73+
} else {
74+
// Otherwise, we'll create it as per usual
75+
_, added, err = h.SubscriptionWorkflowService.AddAddon(ctx, request.SubscriptionID, request.AddonInput)
76+
}
77+
78+
if err != nil {
79+
return CreateSubscriptionAddonResponse{}, err
80+
}
81+
82+
return toAPISubscriptionAddon(added)
83+
},
84+
commonhttp.JSONResponseEncoderWithStatus[CreateSubscriptionAddonResponse](http.StatusCreated),
85+
httptransport.AppendOptions(
86+
h.options,
87+
httptransport.WithOperationName("create-plan-addon"),
88+
httptransport.WithErrorEncoder(apierrors.GenericErrorEncoder()),
89+
)...,
90+
)
91+
}

api/v3/handlers/subscriptions/subscriptionaddons/handler.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,33 @@ import (
44
"context"
55

66
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
7+
subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow"
78
"github.com/openmeterio/openmeter/pkg/framework/transport/httptransport"
89
)
910

1011
type Handler interface {
12+
CreateSubscriptionAddon() CreateSubscriptionAddonHandler
1113
ListSubscriptionAddons() ListSubscriptionAddonsHandler
1214
GetSubscriptionAddon() GetSubscriptionAddonHandler
1315
}
1416

1517
type handler struct {
16-
resolveNamespace func(ctx context.Context) (string, error)
17-
addonService subscriptionaddon.Service
18-
options []httptransport.HandlerOption
18+
resolveNamespace func(ctx context.Context) (string, error)
19+
addonService subscriptionaddon.Service
20+
SubscriptionWorkflowService subscriptionworkflow.Service
21+
options []httptransport.HandlerOption
1922
}
2023

2124
func New(
2225
resolveNamespace func(ctx context.Context) (string, error),
2326
addonService subscriptionaddon.Service,
27+
subscriptionWorkflowService subscriptionworkflow.Service,
2428
options ...httptransport.HandlerOption,
2529
) Handler {
2630
return &handler{
27-
resolveNamespace: resolveNamespace,
28-
addonService: addonService,
29-
options: options,
31+
resolveNamespace: resolveNamespace,
32+
addonService: addonService,
33+
SubscriptionWorkflowService: subscriptionWorkflowService,
34+
options: options,
3035
}
3136
}

api/v3/openapi.yaml

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,10 +2601,45 @@ paths:
26012601
tags:
26022602
- OpenMeter Subscriptions
26032603
/openmeter/subscriptions/{subscriptionId}/addons:
2604+
post:
2605+
operationId: create-subscription-addon
2606+
summary: Create a new subscription add-on
2607+
description: Add add-on to a subscription.
2608+
parameters:
2609+
- name: subscriptionId
2610+
in: path
2611+
required: true
2612+
schema:
2613+
$ref: '#/components/schemas/ULID'
2614+
responses:
2615+
'201':
2616+
description: SubscriptionAddon created response.
2617+
content:
2618+
application/json:
2619+
schema:
2620+
$ref: '#/components/schemas/SubscriptionAddon'
2621+
'400':
2622+
$ref: '#/components/responses/BadRequest'
2623+
'401':
2624+
$ref: '#/components/responses/Unauthorized'
2625+
'403':
2626+
$ref: '#/components/responses/Forbidden'
2627+
'404':
2628+
$ref: '#/components/responses/NotFound'
2629+
tags:
2630+
- OpenMeter Subscriptions
2631+
requestBody:
2632+
required: true
2633+
content:
2634+
application/json:
2635+
schema:
2636+
$ref: '#/components/schemas/CreateSubscriptionAddonRequest'
2637+
x-internal: true
2638+
x-unstable: true
26042639
get:
26052640
operationId: list-subscription-addons
26062641
summary: List subscription addons
2607-
description: List the addons of a subscription.
2642+
description: List the add-ons of a subscription.
26082643
parameters:
26092644
- name: subscriptionId
26102645
in: path
@@ -7427,6 +7462,50 @@ components:
74277462
$ref: '#/components/schemas/ULID'
74287463
additionalProperties: false
74297464
description: TaxCode reference.
7465+
CreateSubscriptionAddonRequest:
7466+
type: object
7467+
required:
7468+
- name
7469+
- addon
7470+
- quantity
7471+
- timing
7472+
properties:
7473+
name:
7474+
type: string
7475+
minLength: 1
7476+
maxLength: 256
7477+
description: |-
7478+
Display name of the resource.
7479+
7480+
Between 1 and 256 characters.
7481+
description:
7482+
type: string
7483+
maxLength: 1024
7484+
description: |-
7485+
Optional description of the resource.
7486+
7487+
Maximum 1024 characters.
7488+
labels:
7489+
$ref: '#/components/schemas/Labels'
7490+
addon:
7491+
allOf:
7492+
- $ref: '#/components/schemas/AddonReference'
7493+
description: The add-on associated with the subscription.
7494+
title: Add-on
7495+
quantity:
7496+
type: integer
7497+
minimum: 1
7498+
description: The quantity of the add-on. Always 1 for single instance add-ons.
7499+
title: Quantity
7500+
timing:
7501+
allOf:
7502+
- $ref: '#/components/schemas/BillingSubscriptionEditTiming'
7503+
description: |-
7504+
The timing of the operation. After the create or update, a new entry will be
7505+
created in the timeline.
7506+
title: Timing
7507+
additionalProperties: false
7508+
description: SubscriptionAddon create request.
74307509
CreateTaxCodeRequest:
74317510
type: object
74327511
required:
@@ -9345,7 +9424,7 @@ components:
93459424
readOnly: true
93469425
addon:
93479426
allOf:
9348-
- $ref: '#/components/schemas/AddonReferenceItem'
9427+
- $ref: '#/components/schemas/AddonReference'
93499428
description: The add-on associated with the subscription.
93509429
title: Add-on
93519430
quantity:

api/v3/server/routes.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ func (s *Server) ChangeSubscription(w http.ResponseWriter, r *http.Request, subs
110110
}
111111

112112
// Subscription Addons
113+
func (s *Server) CreateSubscriptionAddon(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID) {
114+
s.subscriptionAddonsHandler.CreateSubscriptionAddon().With(subscriptionId).ServeHTTP(w, r)
115+
}
116+
113117
func (s *Server) ListSubscriptionAddons(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID, params api.ListSubscriptionAddonsParams) {
114118
s.subscriptionAddonsHandler.ListSubscriptionAddons().With(subscriptionaddonshandler.ListSubscriptionAddonsParams{
115119
SubscriptionID: subscriptionId,

0 commit comments

Comments
 (0)