Skip to content

Commit 2c62b1f

Browse files
feat: enable Org search joined with billing Details (#877)
* feat: update protobuf files * feat: setup service and repo for aggregated org search * fix: update protobuf files * chore: update protobuf files * feat: add rql tags * refactor: change package name from orgaggregate to orgbilling * feat: prepare org data joined with their billing subscription data * feat: fetch latest subscription entry for each org * feat: fetch billing data for each org from db * feat: add country of org in response * feat: add avatar of org in response * refactor: rename * feat: add rql filters * feat: add rql sort * feat: add rql pagination * feat: add rql search * feat: handle empty string in filter * refactor * feat: update response schema to have page and group info * feat: parse group by data * feat: combine group_by aggregated count with raw data rows * refactor: for readability * refactor: for readability * refactor: for readability and handle error * feat: add support for in and not in operators * feat: add support for like and not like in string type * feat: add support for empty and non empty string check * refactor: remove redundant if else check * refactor: for readability * feat: enable search on timestamp columns * refactor: for readability * refactor: for readability * refactor * enable transation * change tx isolation level * test: add unit test for query generation * refactor * refactor: rename package * update proto * fix: linting
1 parent 892bd32 commit 2c62b1f

18 files changed

+2486
-1231
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ buf.yaml
2020
/resources_config
2121
resource_config
2222
/rules
23-
/dist
23+
/dist
24+
__debug_*

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
44
VERSION := $(shell git describe --tags ${TAG})
55
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto ui compose-up-dev
66
.DEFAULT_GOAL := build
7-
PROTON_COMMIT := "86b10eaddc8786d4f27aa5b639a3506218118659"
7+
PROTON_COMMIT := "fd8e1af945b1cef163ad5fc5341c05025689f620"
88

99
ui:
1010
@echo " > generating ui build"

cmd/serve.go

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"syscall"
1313
"time"
1414

15+
"github.com/raystack/frontier/core/aggregates/orgbilling"
16+
1517
"github.com/raystack/frontier/core/kyc"
1618
"github.com/raystack/frontier/core/prospect"
1719

@@ -409,6 +411,9 @@ func buildAPIDependencies(
409411
orgKycRepository := postgres.NewOrgKycRepository(dbc)
410412
orgKycService := kyc.NewService(orgKycRepository)
411413

414+
orgBillingRepository := postgres.NewOrgBillingRepository(dbc)
415+
orgBillingService := orgbilling.NewService(orgBillingRepository)
416+
412417
domainRepository := postgres.NewDomainRepository(logger, dbc)
413418
domainService := domain.NewService(logger, domainRepository, userService, organizationService)
414419

@@ -548,6 +553,7 @@ func buildAPIDependencies(
548553
WebhookService: webhookService,
549554
EventService: eventProcessor,
550555
ProspectService: prospectService,
556+
OrgBillingService: orgBillingService,
551557
}
552558
return dependencies, nil
553559
}

core/aggregates/orgbilling/service.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package orgbilling
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/raystack/frontier/core/organization"
8+
"github.com/raystack/salt/rql"
9+
)
10+
11+
type Repository interface {
12+
Search(ctx context.Context, query *rql.Query) (OrgBilling, error)
13+
}
14+
15+
type Service struct {
16+
repository Repository
17+
}
18+
19+
func NewService(repository Repository) *Service {
20+
return &Service{
21+
repository: repository,
22+
}
23+
}
24+
25+
type OrgBilling struct {
26+
Organizations []AggregatedOrganization `json:"organization"`
27+
Group Group `json:"group"`
28+
Pagination Page `json:"pagination"`
29+
}
30+
31+
type Group struct {
32+
Name string `json:"name"`
33+
Data []GroupData `json:"data"`
34+
}
35+
36+
type GroupData struct {
37+
Name string `json:"name"`
38+
Count int `json:"count"`
39+
}
40+
41+
type Page struct {
42+
Limit int `json:"limit"`
43+
Offset int `json:"offset"`
44+
}
45+
46+
type AggregatedOrganization struct {
47+
ID string `rql:"name=id,type=string"`
48+
Name string `rql:"name=name,type=string"`
49+
Title string `rql:"name=title,type=string"`
50+
CreatedBy string `rql:"name=created_by,type=string"`
51+
PlanName string `rql:"name=plan_name,type=string"`
52+
PaymentMode string `rql:"name=payment_mode,type=string"`
53+
Country string `rql:"name=country,type=string"`
54+
Avatar string `rql:"name=avatar,type=string"`
55+
State organization.State `rql:"name=state,type=string"`
56+
CreatedAt time.Time `rql:"name=created_at,type=datetime"`
57+
UpdatedAt time.Time `rql:"name=updated_at,type=datetime"`
58+
SubscriptionCycleEndAt time.Time `rql:"name=subscription_cycle_end_at,type=datetime"`
59+
SubscriptionState string `rql:"name=subscription_state,type=string"`
60+
PlanInterval string `rql:"name=plan_interval,type=string"`
61+
PlanID string `rql:"name=plan_id,type=string"`
62+
}
63+
64+
func (s Service) Search(ctx context.Context, query *rql.Query) (OrgBilling, error) {
65+
return s.repository.Search(ctx, query)
66+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ require (
3636
github.com/ory/dockertest/v3 v3.10.0
3737
github.com/pkg/errors v0.9.1
3838
github.com/pkg/profile v1.7.0
39-
github.com/raystack/salt v0.3.1
39+
github.com/raystack/salt v0.3.8
4040
github.com/robfig/cron/v3 v3.0.1
4141
github.com/spf13/cobra v1.8.0
4242
github.com/stretchr/testify v1.9.0

go.sum

+4-57
Large diffs are not rendered by default.

internal/api/api.go

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/raystack/frontier/billing/product"
1111
"github.com/raystack/frontier/billing/subscription"
1212
"github.com/raystack/frontier/billing/usage"
13+
"github.com/raystack/frontier/core/aggregates/orgbilling"
1314
"github.com/raystack/frontier/core/audit"
1415
"github.com/raystack/frontier/core/authenticate"
1516
"github.com/raystack/frontier/core/authenticate/session"
@@ -70,6 +71,7 @@ type Deps struct {
7071
InvoiceService *invoice.Service
7172
WebhookService *webhook.Service
7273
EventService *event.Service
74+
OrgBillingService *orgbilling.Service
7375

7476
LogListener *event.ChanListener
7577

internal/api/v1beta1/org.go

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"go.uber.org/zap"
2020

2121
"github.com/raystack/frontier/core/organization"
22-
2322
"google.golang.org/grpc/codes"
2423
"google.golang.org/grpc/status"
2524
"google.golang.org/protobuf/types/known/timestamppb"

internal/api/v1beta1/orgbilling.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package v1beta1
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/raystack/frontier/core/aggregates/orgbilling"
8+
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
9+
"github.com/raystack/salt/rql"
10+
"google.golang.org/grpc/codes"
11+
"google.golang.org/grpc/status"
12+
"google.golang.org/protobuf/types/known/timestamppb"
13+
)
14+
15+
type OrgBillingService interface {
16+
Search(ctx context.Context, query *rql.Query) (orgbilling.OrgBilling, error)
17+
}
18+
19+
func (h Handler) SearchOrganizations(ctx context.Context, request *frontierv1beta1.SearchOrganizationsRequest) (*frontierv1beta1.SearchOrganizationsResponse, error) {
20+
var orgs []*frontierv1beta1.SearchOrganizationsResponse_OrganizationResult
21+
22+
rqlQuery, err := transformProtoToRQL(request.GetQuery())
23+
if err != nil {
24+
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("failed to read rql query: %v", err))
25+
}
26+
27+
err = rql.ValidateQuery(rqlQuery, orgbilling.AggregatedOrganization{})
28+
if err != nil {
29+
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("failed to validate rql query: %v", err))
30+
}
31+
32+
orgBillingData, err := h.orgBillingService.Search(ctx, rqlQuery)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
for _, v := range orgBillingData.Organizations {
38+
orgs = append(orgs, transformAggregatedOrgToPB(v))
39+
}
40+
41+
groupResponse := make([]*frontierv1beta1.RQLQueryGroupData, 0)
42+
for _, groupItem := range orgBillingData.Group.Data {
43+
groupResponse = append(groupResponse, &frontierv1beta1.RQLQueryGroupData{
44+
Name: groupItem.Name,
45+
Count: uint32(groupItem.Count),
46+
})
47+
}
48+
return &frontierv1beta1.SearchOrganizationsResponse{
49+
Organizations: orgs,
50+
Pagination: &frontierv1beta1.RQLQueryPaginationResponse{
51+
Offset: uint32(orgBillingData.Pagination.Offset),
52+
Limit: uint32(orgBillingData.Pagination.Limit),
53+
},
54+
Group: &frontierv1beta1.RQLQueryGroupResponse{
55+
Name: orgBillingData.Group.Name,
56+
Data: groupResponse,
57+
},
58+
}, nil
59+
}
60+
61+
func transformProtoToRQL(q *frontierv1beta1.RQLRequest) (*rql.Query, error) {
62+
filters := make([]rql.Filter, 0)
63+
for _, filter := range q.GetFilters() {
64+
datatype, err := rql.GetDataTypeOfField(filter.GetName(), orgbilling.AggregatedOrganization{})
65+
if err != nil {
66+
return nil, err
67+
}
68+
switch datatype {
69+
case "string":
70+
filters = append(filters, rql.Filter{
71+
Name: filter.GetName(),
72+
Operator: filter.GetOperator(),
73+
Value: filter.GetStringValue(),
74+
})
75+
case "number":
76+
filters = append(filters, rql.Filter{
77+
Name: filter.GetName(),
78+
Operator: filter.GetOperator(),
79+
Value: filter.GetNumberValue(),
80+
})
81+
case "bool":
82+
filters = append(filters, rql.Filter{
83+
Name: filter.GetName(),
84+
Operator: filter.GetOperator(),
85+
Value: filter.GetBoolValue(),
86+
})
87+
case "datetime":
88+
filters = append(filters, rql.Filter{
89+
Name: filter.GetName(),
90+
Operator: filter.GetOperator(),
91+
Value: filter.GetStringValue(),
92+
})
93+
}
94+
}
95+
96+
sortItems := make([]rql.Sort, 0)
97+
for _, sortItem := range q.GetSort() {
98+
sortItems = append(sortItems, rql.Sort{Name: sortItem.GetName(), Order: sortItem.GetOrder()})
99+
}
100+
101+
return &rql.Query{
102+
Search: q.GetSearch(),
103+
Offset: int(q.GetOffset()),
104+
Limit: int(q.GetLimit()),
105+
Filters: filters,
106+
Sort: sortItems,
107+
GroupBy: q.GetGroupBy(),
108+
}, nil
109+
}
110+
111+
func transformAggregatedOrgToPB(v orgbilling.AggregatedOrganization) *frontierv1beta1.SearchOrganizationsResponse_OrganizationResult {
112+
return &frontierv1beta1.SearchOrganizationsResponse_OrganizationResult{
113+
Id: v.ID,
114+
Name: v.Name,
115+
Title: v.Title,
116+
Avatar: v.Avatar,
117+
CreatedAt: timestamppb.New(v.CreatedAt),
118+
UpdatedAt: timestamppb.New(v.UpdatedAt),
119+
CreatedBy: v.CreatedBy,
120+
State: string(v.State),
121+
Country: v.Country,
122+
PaymentMode: v.PaymentMode,
123+
PlanName: v.PlanName,
124+
PlanId: v.PlanID,
125+
SubscriptionState: v.SubscriptionState,
126+
PlanInterval: v.PlanInterval,
127+
SubscriptionCycleEndAt: timestamppb.New(v.SubscriptionCycleEndAt),
128+
}
129+
}

internal/api/v1beta1/v1beta1.go

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type Handler struct {
4545
webhookService WebhookService
4646
eventService EventService
4747
prospectService ProspectService
48+
orgBillingService OrgBillingService
4849
}
4950

5051
func Register(s *grpc.Server, deps api.Deps, authConf authenticate.Config) {
@@ -83,6 +84,7 @@ func Register(s *grpc.Server, deps api.Deps, authConf authenticate.Config) {
8384
webhookService: deps.WebhookService,
8485
eventService: deps.EventService,
8586
prospectService: deps.ProspectService,
87+
orgBillingService: deps.OrgBillingService,
8688
}
8789
s.RegisterService(&frontierv1beta1.FrontierService_ServiceDesc, handler)
8890
s.RegisterService(&frontierv1beta1.AdminService_ServiceDesc, handler)

0 commit comments

Comments
 (0)