feat(billing): add SearchOrgInvoices RQL endpoint#1536
feat(billing): add SearchOrgInvoices RQL endpoint#1536paanSinghCoder wants to merge 20 commits intomainfrom
Conversation
Introduce a new SearchOrgInvoices endpoint with RQL support while keeping ListInvoices behavior unchanged for existing consumers, and wire backend auth/service/repository plus regenerated frontier protobuf bindings. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an organization-scoped invoice search feature across API handler, service, repository, models, mocks, tests, and authorization; also updates the Makefile PROTON_COMMIT used for proto generation. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 29242d54-19b4-4ba1-872f-e89c7f7065a6
⛔ Files ignored due to path filters (2)
proto/v1beta1/frontier.pb.gois excluded by!**/*.pb.go,!proto/**proto/v1beta1/frontierv1beta1connect/frontier.connect.gois excluded by!proto/**
📒 Files selected for processing (8)
Makefilebilling/invoice/invoice.gobilling/invoice/service.gointernal/api/v1beta1connect/billing_invoice.gointernal/api/v1beta1connect/interfaces.gointernal/api/v1beta1connect/mocks/invoice_service.gointernal/store/postgres/billing_invoice_repository.gopkg/server/connect_interceptors/authorization.go
Coverage Report for CI Build 24556335814Coverage increased (+0.02%) to 41.834%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/store/postgres/billing_invoice_repository.go (1)
314-333:⚠️ Potential issue | 🟠 MajorAdd a deterministic default order before paginating.
Line 330 paginates
withSorteven when the request does not specify any sort, so page boundaries are nondeterministic and clients can see duplicates/missing invoices across pages.Suggested fix
withSort, err := frontierutils.AddRQLSortInQuery(withSearch, rqlQuery) if err != nil { return nil, 0, fmt.Errorf("%w: %w", invoice.ErrBadInput, err) } + + if len(rqlQuery.Sort) == 0 { + withSort = withSort.OrderAppend( + goqu.I(TABLE_BILLING_INVOICES+".created_at").Desc(), + goqu.I(TABLE_BILLING_INVOICES+".id").Desc(), + ) + } countQuery, countParams, err := withSearch.Select(goqu.COUNT("*")).ToSQL()
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 04d90ada-f210-4e7d-a6e5-293e01496ae8
📒 Files selected for processing (3)
billing/invoice/service.gointernal/api/v1beta1connect/billing_invoice.gointernal/store/postgres/billing_invoice_repository.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/api/v1beta1connect/billing_invoice.go
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
billing/invoice/service_search_org_invoices_test.go (1)
84-93: PreferErrorIsfor wrapped validation errors.For the nil-query and
group_bycases, asserting only message substrings is brittle. Addassert.ErrorIs(t, err, ErrBadInput)to validate the contract and keep the message checks as secondary.Proposed test hardening
_, _, err = svc.SearchOrgInvoices(context.Background(), "cust-1", true, nil) assert.Error(t, err) + assert.ErrorIs(t, err, ErrBadInput) assert.Contains(t, err.Error(), "query is required") @@ _, _, err = svc.SearchOrgInvoices(context.Background(), "cust-1", true, &rql.Query{ GroupBy: []string{"state"}, }) assert.Error(t, err) + assert.ErrorIs(t, err, ErrBadInput) assert.Contains(t, err.Error(), "group_by is not supported")internal/api/v1beta1connect/billing_invoice_test.go (1)
750-754: Avoid early return before mock expectation checks.Line 753 returns before
AssertExpectations, so error-path cases can skip mock verification.Suggested restructuring
got, err := handler.SearchOrgInvoices(context.Background(), tt.request) if tt.wantCode != 0 { assert.Error(t, err) assert.Equal(t, tt.wantCode, connect.CodeOf(err)) - return - } - - assert.NoError(t, err) - if tt.assertResp != nil { - tt.assertResp(t, got) + assert.Nil(t, got) + } else { + assert.NoError(t, err) + if tt.assertResp != nil { + tt.assertResp(t, got) + } } mockInvoiceService.AssertExpectations(t) mockCustomerService.AssertExpectations(t)
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b292a96f-57e4-4776-9c10-9c5151698a4e
📒 Files selected for processing (2)
billing/invoice/service_search_org_invoices_test.gointernal/api/v1beta1connect/billing_invoice_test.go
There was a problem hiding this comment.
♻️ Duplicate comments (1)
internal/api/v1beta1connect/billing_invoice.go (1)
104-135:⚠️ Potential issue | 🟠 MajorValidate and normalize RQL before
GetByOrgIDearly-return paths.Right now, invalid RQL can return
200 OKforcustomer.ErrNotFound, and default pagination is skipped in that branch because validation/defaulting happens later.🛠️ Proposed fix
func (h *ConnectHandler) SearchOrgInvoices(ctx context.Context, request *connect.Request[frontierv1beta1.SearchOrgInvoicesRequest]) (*connect.Response[frontierv1beta1.SearchOrgInvoicesResponse], error) { errorLogger := NewErrorLogger() queryMsg := request.Msg.GetQuery() if queryMsg == nil { queryMsg = &frontierv1beta1.RQLRequest{} } + rqlQuery, err := utils.TransformProtoToRQL(queryMsg, invoice.SearchOrgInvoice{}) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("failed to read rql query: %v", err)) + } + if err = rql.ValidateQuery(rqlQuery, invoice.SearchOrgInvoice{}); err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("failed to validate rql query: %v", err)) + } + if rqlQuery.Limit <= 0 { + rqlQuery.Limit = utils.DefaultLimit + } + if rqlQuery.Offset < 0 { + rqlQuery.Offset = utils.DefaultOffset + } + cust, err := h.customerService.GetByOrgID(ctx, request.Msg.GetOrgId()) if err != nil { if errors.Is(err, customer.ErrNotFound) { return connect.NewResponse(&frontierv1beta1.SearchOrgInvoicesResponse{ Invoices: []*frontierv1beta1.Invoice{}, Pagination: &frontierv1beta1.RQLQueryPaginationResponse{ - Offset: queryMsg.GetOffset(), - Limit: queryMsg.GetLimit(), + Offset: uint32(rqlQuery.Offset), + Limit: uint32(rqlQuery.Limit), TotalCount: 0, }, }), nil } @@ - rqlQuery, err := utils.TransformProtoToRQL(queryMsg, invoice.SearchOrgInvoice{}) - if err != nil { - return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("failed to read rql query: %v", err)) - } - if err = rql.ValidateQuery(rqlQuery, invoice.SearchOrgInvoice{}); err != nil { - return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("failed to validate rql query: %v", err)) - }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1302b844-eedb-4f66-b9e1-83a8b3ad8be5
📒 Files selected for processing (9)
billing/invoice/invoice.gobilling/invoice/service.gobilling/invoice/service_search_org_invoices_test.gointernal/api/v1beta1connect/billing_invoice.gointernal/api/v1beta1connect/billing_invoice_test.gointernal/api/v1beta1connect/interfaces.gointernal/api/v1beta1connect/mocks/invoice_service.gointernal/store/postgres/billing_invoice_repository.gopkg/server/connect_interceptors/authorization.go
✅ Files skipped from review due to trivial changes (2)
- billing/invoice/service_search_org_invoices_test.go
- billing/invoice/invoice.go
🚧 Files skipped from review as they are similar to previous changes (5)
- pkg/server/connect_interceptors/authorization.go
- internal/store/postgres/billing_invoice_repository.go
- internal/api/v1beta1connect/billing_invoice_test.go
- internal/api/v1beta1connect/interfaces.go
- billing/invoice/service.go
|
Rename |
…sistent pagination handling
…id org ID formats and service errors
…consistency across the codebase
Made-with: Cursor
Summary
SearchOrgInvoicesRPC in FrontierService that supportsRQLRequestand pagination responseProton: PR-471
SearchOrgInvoices RQL Support Matrix
org_idnonzero_amount_onlytrueappliesamount > 0filter.query.limit50when missing/invalid.query.offset0when missing/invalid.query.searchquery.sortquery.filtersquery.group_bygroup_by is not supported).Filterable / Sortable Fields
idprovider_idcustomer_idstatecurrencyamounthosted_urldue_ateffective_atcreated_atperiod_start_atperiod_end_atSearchable Fields
idprovider_idstatecurrencyhosted_urlOperator Support (practical)
stringeq,neq,like,notlike,in,notin,empty,notemptynumbereq,neq,gt,gte,lt,ltedatetimeeq,neq,gt,gte,lt,lteTest plan
make protoand verify no generated-file diffs remainListInvoicesconsumers still behave unchangedSearchOrgInvoiceswith filters/sort/search/limit/offset