storj/satellite/console/consoleweb/consoleapi/buckets.go
Jeremy Wharton a00ec7af40 satellite/console: create http endpoint for getting bucket usage totals
This change introduces an HTTP endpoint for retrieving bucket usage
totals. In the future, this will replace its GraphQL counterpart.

References #6141

Change-Id: Ic6a0069a7e58b90dc2b6c55f164393f036c6acf4
2023-08-08 21:26:17 +00:00

165 lines
4.5 KiB
Go

// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package consoleapi
import (
"context"
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/common/uuid"
"storj.io/storj/private/web"
"storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/console"
)
const (
missingParamErrMsg = "missing '%s' query parameter"
invalidParamErrMsg = "invalid value '%s' for query parameter '%s': %w"
)
var (
// ErrBucketsAPI - console buckets api error type.
ErrBucketsAPI = errs.Class("console api buckets")
)
// Buckets is an api controller that exposes all buckets related functionality.
type Buckets struct {
log *zap.Logger
service *console.Service
}
// NewBuckets is a constructor for api buckets controller.
func NewBuckets(log *zap.Logger, service *console.Service) *Buckets {
return &Buckets{
log: log,
service: service,
}
}
// AllBucketNames returns all bucket names for a specific project.
func (b *Buckets) AllBucketNames(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
w.Header().Set("Content-Type", "application/json")
projectIDString := r.URL.Query().Get("projectID")
publicIDString := r.URL.Query().Get("publicID")
var projectID uuid.UUID
if projectIDString != "" {
projectID, err = uuid.FromString(projectIDString)
if err != nil {
b.serveJSONError(ctx, w, http.StatusBadRequest, err)
return
}
} else if publicIDString != "" {
projectID, err = uuid.FromString(publicIDString)
if err != nil {
b.serveJSONError(ctx, w, http.StatusBadRequest, err)
return
}
} else {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("Project ID was not provided."))
return
}
bucketNames, err := b.service.GetAllBucketNames(ctx, projectID)
if err != nil {
if console.ErrUnauthorized.Has(err) {
b.serveJSONError(ctx, w, http.StatusUnauthorized, err)
return
}
b.serveJSONError(ctx, w, http.StatusInternalServerError, err)
return
}
err = json.NewEncoder(w).Encode(bucketNames)
if err != nil {
b.log.Error("failed to write json all bucket names response", zap.Error(ErrBucketsAPI.Wrap(err)))
}
}
// GetBucketTotals returns a page of bucket usage totals since project creation.
func (b *Buckets) GetBucketTotals(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
w.Header().Set("Content-Type", "application/json")
projectIDString := r.URL.Query().Get("projectID")
if projectIDString == "" {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(missingParamErrMsg, "projectID"))
return
}
projectID, err := uuid.FromString(projectIDString)
if err != nil {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(invalidParamErrMsg, projectIDString, "projectID", err))
return
}
beforeString := r.URL.Query().Get("before")
if beforeString == "" {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(missingParamErrMsg, "before"))
return
}
before, err := time.Parse(dateLayout, beforeString)
if err != nil {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(invalidParamErrMsg, beforeString, "before", err))
return
}
limitString := r.URL.Query().Get("limit")
if limitString == "" {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(missingParamErrMsg, "limit"))
return
}
limitU64, err := strconv.ParseUint(limitString, 10, 32)
if err != nil {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(invalidParamErrMsg, limitString, "limit", err))
return
}
limit := uint(limitU64)
pageString := r.URL.Query().Get("page")
if pageString == "" {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(missingParamErrMsg, "page"))
return
}
pageU64, err := strconv.ParseUint(pageString, 10, 32)
if err != nil {
b.serveJSONError(ctx, w, http.StatusBadRequest, errs.New(invalidParamErrMsg, pageString, "page", err))
return
}
page := uint(pageU64)
totals, err := b.service.GetBucketTotals(ctx, projectID, accounting.BucketUsageCursor{
Limit: limit,
Search: r.URL.Query().Get("search"),
Page: page,
}, before)
if err != nil {
b.serveJSONError(ctx, w, http.StatusInternalServerError, err)
}
err = json.NewEncoder(w).Encode(totals)
if err != nil {
b.log.Error("failed to write json bucket totals response", zap.Error(ErrBucketsAPI.Wrap(err)))
}
}
// serveJSONError writes JSON error to response output stream.
func (b *Buckets) serveJSONError(ctx context.Context, w http.ResponseWriter, status int, err error) {
web.ServeJSONError(ctx, b.log, w, status, err)
}