add cost service swagger (#4474)

* fix

* add costs{GetRechargeAmount/GetConsumptionAmount/GetPropertiesUsedAmount} api
This commit is contained in:
Jiahui
2024-01-10 12:15:43 +08:00
committed by GitHub
parent e6bf29b3c5
commit e45e3b79bd
12 changed files with 708 additions and 14 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ clean:
rm -f $(SERVICE_NAME)
.PHONY: build
build: clean ## Build service-hub binary.
build: ## Build service-hub binary.
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) go build $(GO_BUILD_FLAGS) -o bin/manager main.go
.PHONY: docker-build
+94 -1
View File
@@ -80,6 +80,99 @@ func GetProperties(c *gin.Context) {
})
}
// GetConsumptionAmount
// @Summary Get user consumption amount
// @Description Get user consumption amount within a specified time range
// @Tags ConsumptionAmount
// @Accept json
// @Produce json
// @Param request body helper.UserCostsAmountReq true "User consumption amount request"
// @Success 200 {object} map[string]interface{} "successfully retrieved user consumption amount"
// @Failure 400 {object} map[string]interface{} "failed to parse user consumption amount request"
// @Failure 401 {object} map[string]interface{} "authenticate error"
// @Failure 500 {object} map[string]interface{} "failed to get user consumption amount"
// @Router /account/v1alpha1/costs/consumption [post]
func GetConsumptionAmount(c *gin.Context) {
req, err := helper.ParseUserCostsAmountReq(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user consumption amount request: %v", err)})
return
}
if err := helper.Authenticate(req.Auth); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)})
return
}
amount, err := dao.DBClient.GetConsumptionAmount(req.Owner, req.TimeRange.StartTime, req.TimeRange.EndTime)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get consumption amount : %v", err)})
}
c.JSON(http.StatusOK, gin.H{
"amount": amount,
})
}
// GetRechargeAmount
// @Summary Get user recharge amount
// @Description Get user recharge amount within a specified time range
// @Tags RechargeAmount
// @Accept json
// @Produce json
// @Param request body helper.UserCostsAmountReq true "User recharge amount request"
// @Success 200 {object} map[string]interface{} "successfully retrieved user recharge amount"
// @Failure 400 {object} map[string]interface{} "failed to parse user recharge amount request"
// @Failure 401 {object} map[string]interface{} "authenticate error"
// @Failure 500 {object} map[string]interface{} "failed to get user recharge amount"
// @Router /account/v1alpha1/costs/recharge [post]
func GetRechargeAmount(c *gin.Context) {
req, err := helper.ParseUserCostsAmountReq(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user recharge amount request: %v", err)})
return
}
if err := helper.Authenticate(req.Auth); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)})
return
}
amount, err := dao.DBClient.GetRechargeAmount(req.Owner, req.TimeRange.StartTime, req.TimeRange.EndTime)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get recharge amount : %v", err)})
}
c.JSON(http.StatusOK, gin.H{
"amount": amount,
})
}
// GetPropertiesUsedAmount
// @Summary Get user properties used amount
// @Description Get user properties used amount within a specified time range
// @Tags PropertiesUsedAmount
// @Accept json
// @Produce json
// @Param request body helper.UserCostsAmountReq true "User properties used amount request"
// @Success 200 {object} map[string]interface{} "successfully retrieved user properties used amount"
// @Failure 400 {object} map[string]interface{} "failed to parse user properties used amount request"
// @Failure 401 {object} map[string]interface{} "authenticate error"
// @Failure 500 {object} map[string]interface{} "failed to get user properties used amount"
// @Router /account/v1alpha1/costs/properties [post]
func GetPropertiesUsedAmount(c *gin.Context) {
req, err := helper.ParseUserCostsAmountReq(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user properties used amount request: %v", err)})
return
}
if err := helper.Authenticate(req.Auth); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)})
return
}
amount, err := dao.DBClient.GetPropertiesUsedAmount(req.Owner, req.TimeRange.StartTime, req.TimeRange.EndTime)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get properties used amount : %v", err)})
}
c.JSON(http.StatusOK, gin.H{
"amount": amount,
})
}
type CostsResult struct {
Data CostsResultData `json:"data" bson:"data"`
Message string `json:"message" bson:"message"`
@@ -110,7 +203,7 @@ func GetCosts(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)})
return
}
costs, err := dao.DBClient.GetCostAmount(req.Auth.Owner, req.TimeRange.StartTime, req.TimeRange.EndTime)
costs, err := dao.DBClient.GetCosts(req.Auth.Owner, req.TimeRange.StartTime, req.TimeRange.EndTime)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get cost : %v", err)})
}
+4
View File
@@ -11,5 +11,9 @@ var DBClient Interface
func InitDB() error {
var err error
DBClient, err = NewMongoInterface(os.Getenv(helper.EnvMongoURI))
if err != nil {
return err
}
_, err = DBClient.GetProperties()
return err
}
+91 -3
View File
@@ -20,7 +20,10 @@ import (
type Interface interface {
GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHistoryReq) ([]string, error)
GetProperties() ([]common.PropertyQuery, error)
GetCostAmount(user string, startTime, endTime time.Time) (common.TimeCostsMap, error)
GetCosts(user string, startTime, endTime time.Time) (common.TimeCostsMap, error)
GetConsumptionAmount(user string, startTime, endTime time.Time) (int64, error)
GetRechargeAmount(user string, startTime, endTime time.Time) (int64, error)
GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error)
}
type MongoDB struct {
@@ -41,9 +44,13 @@ func (m *MongoDB) GetProperties() ([]common.PropertyQuery, error) {
m.Properties = properties
}
for _, types := range m.Properties.Types {
price := types.ViewPrice
if price == 0 {
price = types.UnitPrice
}
property := common.PropertyQuery{
Name: types.Name,
UnitPrice: types.ViewPrice,
UnitPrice: price,
Unit: types.UnitString,
Alias: types.Alias,
}
@@ -52,7 +59,7 @@ func (m *MongoDB) GetProperties() ([]common.PropertyQuery, error) {
return propertiesQuery, nil
}
func (m *MongoDB) GetCostAmount(user string, startTime, endTime time.Time) (common.TimeCostsMap, error) {
func (m *MongoDB) GetCosts(user string, startTime, endTime time.Time) (common.TimeCostsMap, error) {
filter := bson.M{
"type": 0,
"time": bson.M{
@@ -84,6 +91,87 @@ func (m *MongoDB) GetCostAmount(user string, startTime, endTime time.Time) (comm
return costsMap, nil
}
func (m *MongoDB) GetConsumptionAmount(user string, startTime, endTime time.Time) (int64, error) {
return m.getAmountWithType(0, user, startTime, endTime)
}
func (m *MongoDB) GetRechargeAmount(user string, startTime, endTime time.Time) (int64, error) {
return m.getAmountWithType(1, user, startTime, endTime)
}
func (m *MongoDB) getAmountWithType(_type int64, user string, startTime, endTime time.Time) (int64, error) {
pipeline := bson.A{
bson.D{{Key: "$match", Value: bson.M{
"type": _type,
"time": bson.M{"$gte": startTime, "$lte": endTime},
"owner": user,
}}},
bson.D{{Key: "$group", Value: bson.M{
"_id": nil,
"total": bson.M{"$sum": "$amount"},
}}},
}
cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline)
if err != nil {
return 0, fmt.Errorf("failed to aggregate billing collection: %v", err)
}
defer cursor.Close(context.Background())
var result struct {
Total int64 `bson:"total"`
}
if cursor.Next(context.Background()) {
if err := cursor.Decode(&result); err != nil {
return 0, fmt.Errorf("failed to decode result: %v", err)
}
}
return result.Total, nil
}
func (m *MongoDB) GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) {
propertiesUsedAmount := make(map[string]int64)
for _, property := range m.Properties.Types {
amount, err := m.getSumOfUsedAmount(property.Enum, user, startTime, endTime)
if err != nil {
return nil, fmt.Errorf("failed to get sum of used amount: %v", err)
}
propertiesUsedAmount[property.Name] = amount
}
return propertiesUsedAmount, nil
}
func (m *MongoDB) getSumOfUsedAmount(propertyType uint8, user string, startTime, endTime time.Time) (int64, error) {
pipeline := bson.A{
bson.D{{Key: "$match", Value: bson.M{
"time": bson.M{"$gte": startTime, "$lte": endTime},
"owner": user,
"app_costs.used_amount.0": bson.M{"$exists": true},
}}},
bson.D{{Key: "$unwind", Value: "$app_costs"}},
bson.D{{Key: "$group", Value: bson.M{
"_id": nil,
"totalAmount": bson.M{"$sum": "$app_costs.used_amount." + strconv.Itoa(int(propertyType))},
}}},
}
cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline)
if err != nil {
return 0, fmt.Errorf("failed to get billing collection: %v", err)
}
defer cursor.Close(context.Background())
var result struct {
TotalAmount int64 `bson:"totalAmount"`
}
if cursor.Next(context.Background()) {
if err := cursor.Decode(&result); err != nil {
return 0, fmt.Errorf("failed to decode result: %v", err)
}
}
return result.TotalAmount, nil
}
func NewMongoInterface(url string) (Interface, error) {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(url))
if err != nil {
+52
View File
@@ -0,0 +1,52 @@
package dao
import (
"testing"
"time"
"github.com/labring/sealos/controllers/pkg/resources"
"go.mongodb.org/mongo-driver/mongo"
)
func TestMongoDB_GetRechargeAmount(t *testing.T) {
type fields struct {
Client *mongo.Client
AccountDBName string
BillingConn string
PropertiesConn string
Properties *resources.PropertyTypeLS
}
type args struct {
user string
startTime time.Time
endTime time.Time
}
tests := []struct {
name string
fields fields
args args
want int64
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &MongoDB{
Client: tt.fields.Client,
AccountDBName: tt.fields.AccountDBName,
BillingConn: tt.fields.BillingConn,
PropertiesConn: tt.fields.PropertiesConn,
Properties: tt.fields.Properties,
}
got, err := m.GetRechargeAmount(tt.args.user, tt.args.startTime, tt.args.endTime)
if (err != nil) != tt.wantErr {
t.Errorf("GetRechargeAmount() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("GetRechargeAmount() got = %v, want %v", got, tt.want)
}
})
}
}
+169 -1
View File
@@ -74,6 +74,174 @@ const docTemplate = `{
}
}
},
"/account/v1alpha1/costs/consumption": {
"post": {
"description": "Get user consumption amount within a specified time range",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"ConsumptionAmount"
],
"summary": "Get user consumption amount",
"parameters": [
{
"description": "User consumption amount request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UserCostsAmountReq"
}
}
],
"responses": {
"200": {
"description": "successfully retrieved user consumption amount",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "failed to parse user consumption amount request",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "authenticate error",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "failed to get user consumption amount",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/account/v1alpha1/costs/properties": {
"post": {
"description": "Get user properties used amount within a specified time range",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PropertiesUsedAmount"
],
"summary": "Get user properties used amount",
"parameters": [
{
"description": "User properties used amount request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UserCostsAmountReq"
}
}
],
"responses": {
"200": {
"description": "successfully retrieved user properties used amount",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "failed to parse user properties used amount request",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "authenticate error",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "failed to get user properties used amount",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/account/v1alpha1/costs/recharge": {
"post": {
"description": "Get user recharge amount within a specified time range",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"RechargeAmount"
],
"summary": "Get user recharge amount",
"parameters": [
{
"description": "User recharge amount request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UserCostsAmountReq"
}
}
],
"responses": {
"200": {
"description": "successfully retrieved user recharge amount",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "failed to parse user recharge amount request",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "authenticate error",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "failed to get user recharge amount",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/account/v1alpha1/namespaces": {
"post": {
"description": "Get the billing history namespace list from the database",
@@ -317,7 +485,7 @@ const docTemplate = `{
var SwaggerInfo = &swag.Spec{
Version: "v1alpha1",
Host: "localhost:2333",
BasePath: "/account/v1alpha1",
BasePath: "",
Schemes: []string{},
Title: "sealos account service",
Description: "Your API description.",
+168 -1
View File
@@ -10,7 +10,6 @@
"version": "v1alpha1"
},
"host": "localhost:2333",
"basePath": "/account/v1alpha1",
"paths": {
"/account/v1alpha1/costs": {
"post": {
@@ -68,6 +67,174 @@
}
}
},
"/account/v1alpha1/costs/consumption": {
"post": {
"description": "Get user consumption amount within a specified time range",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"ConsumptionAmount"
],
"summary": "Get user consumption amount",
"parameters": [
{
"description": "User consumption amount request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UserCostsAmountReq"
}
}
],
"responses": {
"200": {
"description": "successfully retrieved user consumption amount",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "failed to parse user consumption amount request",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "authenticate error",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "failed to get user consumption amount",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/account/v1alpha1/costs/properties": {
"post": {
"description": "Get user properties used amount within a specified time range",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PropertiesUsedAmount"
],
"summary": "Get user properties used amount",
"parameters": [
{
"description": "User properties used amount request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UserCostsAmountReq"
}
}
],
"responses": {
"200": {
"description": "successfully retrieved user properties used amount",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "failed to parse user properties used amount request",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "authenticate error",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "failed to get user properties used amount",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/account/v1alpha1/costs/recharge": {
"post": {
"description": "Get user recharge amount within a specified time range",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"RechargeAmount"
],
"summary": "Get user recharge amount",
"parameters": [
{
"description": "User recharge amount request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UserCostsAmountReq"
}
}
],
"responses": {
"200": {
"description": "successfully retrieved user recharge amount",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "failed to parse user recharge amount request",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"401": {
"description": "authenticate error",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "failed to get user recharge amount",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/account/v1alpha1/namespaces": {
"post": {
"description": "Get the billing history namespace list from the database",
+114 -1
View File
@@ -1,4 +1,3 @@
basePath: /account/v1alpha1
definitions:
common.PropertyQuery:
properties:
@@ -144,6 +143,120 @@ paths:
summary: Get user costs
tags:
- Costs
/account/v1alpha1/costs/consumption:
post:
consumes:
- application/json
description: Get user consumption amount within a specified time range
parameters:
- description: User consumption amount request
in: body
name: request
required: true
schema:
$ref: '#/definitions/helper.UserCostsAmountReq'
produces:
- application/json
responses:
"200":
description: successfully retrieved user consumption amount
schema:
additionalProperties: true
type: object
"400":
description: failed to parse user consumption amount request
schema:
additionalProperties: true
type: object
"401":
description: authenticate error
schema:
additionalProperties: true
type: object
"500":
description: failed to get user consumption amount
schema:
additionalProperties: true
type: object
summary: Get user consumption amount
tags:
- ConsumptionAmount
/account/v1alpha1/costs/properties:
post:
consumes:
- application/json
description: Get user properties used amount within a specified time range
parameters:
- description: User properties used amount request
in: body
name: request
required: true
schema:
$ref: '#/definitions/helper.UserCostsAmountReq'
produces:
- application/json
responses:
"200":
description: successfully retrieved user properties used amount
schema:
additionalProperties: true
type: object
"400":
description: failed to parse user properties used amount request
schema:
additionalProperties: true
type: object
"401":
description: authenticate error
schema:
additionalProperties: true
type: object
"500":
description: failed to get user properties used amount
schema:
additionalProperties: true
type: object
summary: Get user properties used amount
tags:
- PropertiesUsedAmount
/account/v1alpha1/costs/recharge:
post:
consumes:
- application/json
description: Get user recharge amount within a specified time range
parameters:
- description: User recharge amount request
in: body
name: request
required: true
schema:
$ref: '#/definitions/helper.UserCostsAmountReq'
produces:
- application/json
responses:
"200":
description: successfully retrieved user recharge amount
schema:
additionalProperties: true
type: object
"400":
description: failed to parse user recharge amount request
schema:
additionalProperties: true
type: object
"401":
description: authenticate error
schema:
additionalProperties: true
type: object
"500":
description: failed to get user recharge amount
schema:
additionalProperties: true
type: object
summary: Get user recharge amount
tags:
- RechargeAmount
/account/v1alpha1/namespaces:
post:
consumes:
+4 -1
View File
@@ -4,7 +4,10 @@ const (
GROUP = "/account/v1alpha1"
GetHistoryNamespaces = "/namespaces"
GetProperties = "/properties"
GetUserCosts = "/usercosts"
GetRechargeAmount = "/costs/recharge"
GetConsumptionAmount = "/costs/consumption"
GetPropertiesUsed = "/costs/properties"
GetUserCosts = "/costs"
)
// env
+2
View File
@@ -2,6 +2,7 @@ package helper
import (
"fmt"
"strings"
"time"
"github.com/labring/sealos/service/account/common"
@@ -89,5 +90,6 @@ func ParseUserCostsAmountReq(c *gin.Context) (*UserCostsAmountReq, error) {
if userCosts.TimeRange.EndTime.After(time.Now()) {
userCosts.TimeRange.EndTime = time.Now()
}
userCosts.Owner = strings.TrimPrefix(userCosts.Owner, "ns-")
return userCosts, nil
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// @termsOfService https://cloud.sealos.io
// @contact.email bxy4543@gmail.com
// @host localhost:2333
// @BasePath /account/v1alpha1
// @BasePath
func main() {
router.RegisterPayRouter()
}
+8 -4
View File
@@ -7,6 +7,8 @@ import (
"os/signal"
"syscall"
"github.com/labring/sealos/controllers/pkg/utils/env"
"github.com/labring/sealos/service/account/docs"
"github.com/labring/sealos/service/account/dao"
@@ -27,13 +29,15 @@ func RegisterPayRouter() {
if err := dao.InitDB(); err != nil {
log.Fatalf("Error initializing database: %v", err)
}
// /account/v1alpha1/{/namespaces | /properties | /costs}
// /account/v1alpha1/{/namespaces | /properties | {/costs | /costs/recharge | /costs/consumption | /costs/properties}}
router.Group(helper.GROUP).
POST(helper.GetHistoryNamespaces, api.GetBillingHistoryNamespaceList).
POST(helper.GetProperties, api.GetProperties).
POST(helper.GetUserCosts, api.GetCosts)
docs.SwaggerInfo.BasePath = helper.GROUP
POST(helper.GetUserCosts, api.GetCosts).
POST(helper.GetRechargeAmount, api.GetRechargeAmount).
POST(helper.GetConsumptionAmount, api.GetConsumptionAmount).
POST(helper.GetPropertiesUsed, api.GetPropertiesUsedAmount)
docs.SwaggerInfo.Host = env.GetEnvWithDefault("SWAGGER_HOST", "localhost:2333")
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
// Create a buffered channel interrupt and use the signal.