Skip to content

feat(subscription addon): v3 assign add-on to a subscription endpoint#4392

Open
borosr wants to merge 2 commits into
mainfrom
feat/attach-subscription-addon-v3
Open

feat(subscription addon): v3 assign add-on to a subscription endpoint#4392
borosr wants to merge 2 commits into
mainfrom
feat/attach-subscription-addon-v3

Conversation

@borosr
Copy link
Copy Markdown
Collaborator

@borosr borosr commented May 19, 2026

Add new v3 endpoint to assign an add-on to a subscription

What

Add new V3 endpoint for assign an add-on to a subscription POST /api/v3/openmeter/subscriptions/{subscriptionID}/addons.


Testing

curl --request POST \
  --url http://localhost:8888/api/v3/openmeter/subscriptions/{subscriptionID}/addons \
  --header 'Content-Type: application/json' \
  --data '{
	"name": "addon",
	"quantity": 1,
	"timing": "immediate",
	"addon": {
		"id": "<addonID>"
	}
}'

Summary by CodeRabbit

  • New Features
    • New API endpoint to create subscription add-ons with configurable timing for scheduling.
    • Subscription add-ons now include timing information to control when add-on operations occur.
    • Creating an add-on that already exists updates its quantity instead of failing, simplifying management.
  • Documentation
    • Listing wording updated to use "add-ons" consistently.

Review Change Stack

@borosr borosr requested a review from tothandras May 19, 2026 22:17
@borosr borosr self-assigned this May 19, 2026
@borosr borosr requested a review from a team as a code owner May 19, 2026 22:17
@borosr borosr added the release-note/ignore Ignore this change when generating release notes label May 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

Adds a POST API to create subscription add‑ons (TypeSpec contract + timing field), maps requests to the subscription workflow input, implements a create-or-update handler, and wires the SubscriptionWorkflowService into server routing and config.

Changes

Create Subscription Add-on Feature

Layer / File(s) Summary
API contract and data model
api/spec/packages/aip/src/subscriptions/operations.tsp, api/spec/packages/aip/src/subscriptions/subscriptionaddon.tsp
TypeSpec adds create-subscription-addon POST operation and a timing: SubscriptionEditTiming field on SubscriptionAddon (visible on create/update).
Request and response conversion
api/v3/handlers/subscriptions/subscriptionaddons/convert.go
Adds mapCreateSubscriptionAddonRequestToInput (timing cast, labels→metadata) and updates toAPISubscriptionAddon to use apiv3.AddonReference.
Create handler implementation
api/v3/handlers/subscriptions/subscriptionaddons/create.go
Defines handler types, decodes JSON body and namespace, maps request to workflow input, lists existing add-ons, updates quantity if present or creates new via workflow, and responds 201 with the API model.
Handler interface and dependencies
api/v3/handlers/subscriptions/subscriptionaddons/handler.go
Adds CreateSubscriptionAddon() to the exported Handler; internal handler stores SubscriptionWorkflowService and New now accepts that dependency.
Server routing and configuration
api/v3/server/server.go, api/v3/server/routes.go
Config gains SubscriptionWorkflowService with validation; handler construction now passes the service; new server route method delegates requests to the create handler.

Sequence Diagram

sequenceDiagram
  participant Client
  participant CreateAddonHandler
  participant SubscriptionAddonService
  participant SubscriptionWorkflowService
  participant Client2 as Client (response)
  Client->>CreateAddonHandler: POST /subscriptions/{id}/addons
  CreateAddonHandler->>CreateAddonHandler: Parse request + resolve namespace
  CreateAddonHandler->>SubscriptionAddonService: List existing add-ons
  alt Add-on exists
    CreateAddonHandler->>SubscriptionWorkflowService: ChangeAddonQuantity
  else Add-on is new
    CreateAddonHandler->>SubscriptionWorkflowService: AddAddon
  end
  SubscriptionWorkflowService-->>CreateAddonHandler: Created/updated add-on
  CreateAddonHandler->>Client2: 201 Created + SubscriptionAddon JSON
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • openmeterio/openmeter#4337: Adds/extends subscription add-ons API surface (list operation) and shares spec/handler areas related to add-ons.
  • openmeterio/openmeter#4318: Adds complementary GET operation for subscription add-ons in the same API/handler surface.

Suggested Labels

release-note/feature

Suggested Reviewers

  • tothandras
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main feature added: a new V3 API endpoint for assigning add-ons to subscriptions, matching the core changes across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/attach-subscription-addon-v3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/v3/handlers/subscriptions/subscriptionaddons/create.go`:
- Around line 64-76: The handler currently performs an implicit upsert: when an
existing addon (found via lo.Find on subsAdds.Items matching
request.AddonInput.AddonID) is present it calls
SubscriptionWorkflowService.ChangeAddonQuantity but still behaves like a Create
(returns 201). Change this to strict-create behavior: if sAdd is found, do NOT
call ChangeAddonQuantity and instead return a 409 Conflict (with a clear error
message) so clients know the addon already exists; otherwise proceed to call
SubscriptionWorkflowService.AddAddon as before. Ensure the branch that
previously called ChangeAddonQuantity (using
SubscriptionWorkflowService.ChangeAddonQuantity and variables sAdd,
request.SubscriptionID, subscriptionworkflow.ChangeAddonQuantityWorkflowInput)
is removed and replaced by the conflict response, and update any tests or
documentation to reflect the create-only contract.
- Line 87: The operation name passed to httptransport.WithOperationName is
incorrect ("create-plan-addon")—update the call in the subscription addon
handler to use the correct operation name "create-subscription-addon" so
traces/metrics align with the endpoint; locate the
httptransport.WithOperationName invocation in the create subscription addon
handler (the call shown as httptransport.WithOperationName("create-plan-addon"))
and replace the string accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1559f8b3-689b-4674-adb5-8920d50634b3

📥 Commits

Reviewing files that changed from the base of the PR and between 7fc84c4 and abb1276.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (9)
  • api/spec/packages/aip/src/subscriptions/operations.tsp
  • api/spec/packages/aip/src/subscriptions/subscriptionaddon.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/subscriptions/subscriptionaddons/convert.go
  • api/v3/handlers/subscriptions/subscriptionaddons/create.go
  • api/v3/handlers/subscriptions/subscriptionaddons/handler.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • openmeter/server/server.go

Comment on lines +64 to +76
// If the addon is already present, we'll change the quantity instead as a convenience
if sAdd, ok := lo.Find(subsAdds.Items, func(subAdd subscriptionaddon.SubscriptionAddon) bool {
return subAdd.Addon.ID == request.AddonInput.AddonID
}); ok {
_, added, err = h.SubscriptionWorkflowService.ChangeAddonQuantity(ctx, request.SubscriptionID, subscriptionworkflow.ChangeAddonQuantityWorkflowInput{
SubscriptionAddonID: sAdd.NamespacedID,
Quantity: request.AddonInput.InitialQuantity,
Timing: request.AddonInput.Timing,
})
} else {
// Otherwise, we'll create it as per usual
_, added, err = h.SubscriptionWorkflowService.AddAddon(ctx, request.SubscriptionID, request.AddonInput)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Create endpoint currently behaves as implicit upsert.

This path updates quantity when the add-on already exists, but the API contract/documentation describes a create operation. It also still responds with 201 Created for the update branch, which is pretty confusing for clients. Please either make this endpoint strictly create (e.g., conflict on existing add-on) or formally model/document upsert semantics and status codes.

As per coding guidelines: "The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user."

Also applies to: 84-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/v3/handlers/subscriptions/subscriptionaddons/create.go` around lines 64 -
76, The handler currently performs an implicit upsert: when an existing addon
(found via lo.Find on subsAdds.Items matching request.AddonInput.AddonID) is
present it calls SubscriptionWorkflowService.ChangeAddonQuantity but still
behaves like a Create (returns 201). Change this to strict-create behavior: if
sAdd is found, do NOT call ChangeAddonQuantity and instead return a 409 Conflict
(with a clear error message) so clients know the addon already exists; otherwise
proceed to call SubscriptionWorkflowService.AddAddon as before. Ensure the
branch that previously called ChangeAddonQuantity (using
SubscriptionWorkflowService.ChangeAddonQuantity and variables sAdd,
request.SubscriptionID, subscriptionworkflow.ChangeAddonQuantityWorkflowInput)
is removed and replaced by the conflict response, and update any tests or
documentation to reflect the create-only contract.

Comment thread api/v3/handlers/subscriptions/subscriptionaddons/create.go Outdated
@borosr borosr force-pushed the feat/attach-subscription-addon-v3 branch from abb1276 to 5b8f77e Compare May 20, 2026 07:32
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
api/v3/handlers/subscriptions/subscriptionaddons/create.go (1)

64-76: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Create endpoint still behaves like upsert, which conflicts with the declared contract.

Right now, when the add-on already exists, this path updates quantity (ChangeAddonQuantity) and still responds as 201 Created. That makes create-subscription-addon ambiguous for clients. Please either make this strict-create (conflict on existing add-on) or explicitly model upsert semantics and status codes in both spec and handler.

As per coding guidelines, "The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user."

Also applies to: 84-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/v3/handlers/subscriptions/subscriptionaddons/create.go` around lines 64 -
76, The handler currently upserts by calling
SubscriptionWorkflowService.ChangeAddonQuantity when an existing addon is found;
change this to strict-create semantics by detecting existing addon (the lo.Find
check that returns sAdd) and returning an HTTP 409 Conflict (with a clear
message) instead of calling SubscriptionWorkflowService.ChangeAddonQuantity,
leaving SubscriptionWorkflowService.AddAddon to be called only in the else
branch and returning 201 Created on successful AddAddon; alternatively, if you
prefer upsert semantics, update the API spec and this handler to document/model
upsert and return the appropriate status (e.g., 200/201) consistently—pick one
approach and make the codepaths around ChangeAddonQuantity and AddAddon match
that choice.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@api/v3/handlers/subscriptions/subscriptionaddons/create.go`:
- Around line 64-76: The handler currently upserts by calling
SubscriptionWorkflowService.ChangeAddonQuantity when an existing addon is found;
change this to strict-create semantics by detecting existing addon (the lo.Find
check that returns sAdd) and returning an HTTP 409 Conflict (with a clear
message) instead of calling SubscriptionWorkflowService.ChangeAddonQuantity,
leaving SubscriptionWorkflowService.AddAddon to be called only in the else
branch and returning 201 Created on successful AddAddon; alternatively, if you
prefer upsert semantics, update the API spec and this handler to document/model
upsert and return the appropriate status (e.g., 200/201) consistently—pick one
approach and make the codepaths around ChangeAddonQuantity and AddAddon match
that choice.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a87d802d-712f-436c-9fb8-df1ed4447bea

📥 Commits

Reviewing files that changed from the base of the PR and between abb1276 and 5b8f77e.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (9)
  • api/spec/packages/aip/src/subscriptions/operations.tsp
  • api/spec/packages/aip/src/subscriptions/subscriptionaddon.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/subscriptions/subscriptionaddons/convert.go
  • api/v3/handlers/subscriptions/subscriptionaddons/create.go
  • api/v3/handlers/subscriptions/subscriptionaddons/handler.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • openmeter/server/server.go
✅ Files skipped from review due to trivial changes (1)
  • openmeter/server/server.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/ignore Ignore this change when generating release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant