You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/daemon/controller/handlers/billing/billing_service.go

126 lines
3.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package billing
import (
"context"
"time"
"github.com/hashicorp/boundary/internal/billing"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/billing"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
// IdActions contains the set of actions that can be performed on
// individual resources
IdActions = action.NewActionSet()
// CollectionActions contains the set of actions that can be performed on
// this collection
CollectionActions = action.NewActionSet(
action.MonthlyActiveUsers,
)
)
func init() {
// TODO: refactor to remove IdActions and CollectionActions package variables
action.RegisterResource(resource.Billing, IdActions, CollectionActions)
}
type Service struct {
pbs.UnsafeBillingServiceServer
repoFn common.BillingRepoFactory
}
var _ pbs.BillingServiceServer = (*Service)(nil)
// NewService returns a billing service which handles billing related requests to boundary.
func NewService(
ctx context.Context,
repoFn common.BillingRepoFactory,
) (Service, error) {
const op = "billing.NewService"
if repoFn == nil {
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing billing repository")
}
return Service{
repoFn: repoFn,
}, nil
}
func (s Service) MonthlyActiveUsers(ctx context.Context, req *pbs.MonthlyActiveUsersRequest) (*pbs.MonthlyActiveUsersResponse, error) {
const op = "billing.(Service).MonthlyActiveUsers"
authResults := s.authResult(ctx, action.MonthlyActiveUsers)
if authResults.Error != nil {
return nil, errors.Wrap(ctx, authResults.Error, op)
}
// Get the billing information
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var startTime, endTime *time.Time
if req.GetStartTime() != "" {
st, err := time.Parse("2006-01", req.GetStartTime())
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "start time is in an invalid format")
}
startTime = &st
}
if req.GetEndTime() != "" {
et, err := time.Parse("2006-01", req.GetEndTime())
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "end time is in an invalid format")
}
endTime = &et
}
if startTime != nil && endTime != nil && !endTime.After(*startTime) {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "start time is not before end time")
}
months, err := repo.MonthlyActiveUsers(
ctx,
billing.WithStartTime(startTime),
billing.WithEndTime(endTime),
)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, err.Error())
}
var activeUsers []*pb.ActiveUsers
for _, month := range months {
maud := &pb.ActiveUsers{
StartTime: timestamppb.New(month.StartTime),
EndTime: timestamppb.New(month.EndTime),
Count: month.ActiveUsersCount,
}
activeUsers = append(activeUsers, maud)
}
return &pbs.MonthlyActiveUsersResponse{Items: activeUsers}, nil
}
func (s Service) authResult(ctx context.Context, a action.Type) auth.VerifyResults {
opts := []auth.Option{
auth.WithType(resource.Billing),
auth.WithAction(a),
auth.WithScopeId(scope.Global.String()),
}
return auth.Verify(ctx, opts...)
}