satellite/console: get all bucket names endpoint and service method
WHAT: new endpoint for fetching all bucket names WHY: used by new access grant flow Change-Id: I356a3381359665fd2726120139b34b1e611fe3c4
This commit is contained in:
parent
db480e6e1b
commit
51a712f9e8
@ -744,5 +744,4 @@ func TestProjectUsage_BandwidthDownloadLimit(t *testing.T) {
|
||||
_, err = planet.Uplinks[0].Download(ctx, planet.Satellites[0], "testbucket", "test/path1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
89
satellite/console/consoleweb/consoleapi/buckets.go
Normal file
89
satellite/console/consoleweb/consoleapi/buckets.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package consoleapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite/console"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBucketsAPI - console buckets api error type.
|
||||
ErrBucketsAPI = errs.Class("console buckets api error")
|
||||
)
|
||||
|
||||
// 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")
|
||||
|
||||
projectID, err := uuid.FromString(projectIDString)
|
||||
if err != nil {
|
||||
b.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
bucketNames, err := b.service.GetAllBucketNames(ctx, projectID)
|
||||
if err != nil {
|
||||
if console.ErrUnauthorized.Has(err) {
|
||||
b.serveJSONError(w, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
b.serveJSONError(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)))
|
||||
}
|
||||
}
|
||||
|
||||
// serveJSONError writes JSON error to response output stream.
|
||||
func (b *Buckets) serveJSONError(w http.ResponseWriter, status int, err error) {
|
||||
if status == http.StatusInternalServerError {
|
||||
b.log.Error("returning error to client", zap.Int("code", status), zap.Error(err))
|
||||
} else {
|
||||
b.log.Debug("returning error to client", zap.Int("code", status), zap.Error(err))
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
|
||||
var response struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
response.Error = err.Error()
|
||||
|
||||
err = json.NewEncoder(w).Encode(response)
|
||||
if err != nil {
|
||||
b.log.Error("failed to write json error response", zap.Error(ErrBucketsAPI.Wrap(err)))
|
||||
}
|
||||
}
|
114
satellite/console/consoleweb/consoleapi/buckets_test.go
Normal file
114
satellite/console/consoleweb/consoleapi/buckets_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package consoleapi_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/console"
|
||||
)
|
||||
|
||||
func Test_AllBucketNames(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Console.OpenRegistrationEnabled = true
|
||||
config.Console.RateLimit.Burst = 10
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
project := planet.Uplinks[0].Projects[0]
|
||||
service := sat.API.Console.Service
|
||||
|
||||
bucket1 := storj.Bucket{
|
||||
ID: testrand.UUID(),
|
||||
Name: "testBucket1",
|
||||
ProjectID: project.ID,
|
||||
}
|
||||
|
||||
bucket2 := storj.Bucket{
|
||||
ID: testrand.UUID(),
|
||||
Name: "testBucket2",
|
||||
ProjectID: project.ID,
|
||||
}
|
||||
|
||||
_, err := sat.DB.Buckets().CreateBucket(ctx, bucket1)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sat.DB.Buckets().CreateBucket(ctx, bucket2)
|
||||
require.NoError(t, err)
|
||||
|
||||
user := console.CreateUser{
|
||||
FullName: "Jack",
|
||||
ShortName: "",
|
||||
Email: "bucketest@test.test",
|
||||
Password: "123a123",
|
||||
}
|
||||
refUserID := ""
|
||||
|
||||
regToken, err := service.CreateRegToken(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
createdUser, err := service.CreateUser(ctx, user, regToken.Secret, refUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
activationToken, err := service.GenerateActivationToken(ctx, createdUser.ID, createdUser.Email)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.ActivateAccount(ctx, activationToken)
|
||||
require.NoError(t, err)
|
||||
|
||||
token, err := service.Token(ctx, user.Email, user.Password)
|
||||
require.NoError(t, err)
|
||||
|
||||
client := http.Client{}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+planet.Satellites[0].API.Console.Listener.Addr().String()+"/api/v0/buckets/bucket-names?projectID="+project.ID.String(), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expire := time.Now().AddDate(0, 0, 1)
|
||||
cookie := http.Cookie{
|
||||
Name: "_tokenKey",
|
||||
Path: "/",
|
||||
Value: token,
|
||||
Expires: expire,
|
||||
}
|
||||
|
||||
req.AddCookie(&cookie)
|
||||
|
||||
result, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, result.StatusCode)
|
||||
|
||||
body, err := ioutil.ReadAll(result.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var output []string
|
||||
|
||||
err = json.Unmarshal(body, &output)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, bucket1.Name, output[0])
|
||||
require.Equal(t, bucket2.Name, output[1])
|
||||
|
||||
defer func() {
|
||||
err = result.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
})
|
||||
}
|
@ -196,6 +196,11 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
|
||||
paymentsRouter.HandleFunc("/tokens/deposit", paymentController.TokenDeposit).Methods(http.MethodPost)
|
||||
paymentsRouter.HandleFunc("/paywall-enabled/{userId}", paymentController.PaywallEnabled).Methods(http.MethodGet)
|
||||
|
||||
bucketsController := consoleapi.NewBuckets(logger, service)
|
||||
bucketsRouter := router.PathPrefix("/api/v0/buckets").Subrouter()
|
||||
bucketsRouter.Use(server.withAuth)
|
||||
bucketsRouter.HandleFunc("/bucket-names", bucketsController.AllBucketNames).Methods(http.MethodGet)
|
||||
|
||||
if server.config.StaticDir != "" {
|
||||
router.HandleFunc("/activation/", server.accountActivationHandler)
|
||||
router.HandleFunc("/password-recovery/", server.passwordRecoveryHandler)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
|
||||
"storj.io/common/macaroon"
|
||||
"storj.io/common/memory"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite/accounting"
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
@ -1455,6 +1456,36 @@ func (s *Service) GetBucketTotals(ctx context.Context, projectID uuid.UUID, curs
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// GetAllBucketNames retrieves all bucket names of a specific project.
|
||||
func (s *Service) GetAllBucketNames(ctx context.Context, projectID uuid.UUID) (_ []string, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = s.getAuthAndAuditLog(ctx, "get all bucket names", zap.String("projectID", projectID.String()))
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
listOptions := storj.BucketListOptions{
|
||||
Direction: storj.Forward,
|
||||
}
|
||||
|
||||
allowedBuckets := macaroon.AllowedBuckets{
|
||||
All: true,
|
||||
}
|
||||
|
||||
bucketsList, err := s.buckets.ListBuckets(ctx, projectID, listOptions, allowedBuckets)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
var list []string
|
||||
for _, bucket := range bucketsList.Items {
|
||||
list = append(list, bucket.Name)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// GetBucketUsageRollups retrieves summed usage rollups for every bucket of particular project for a given period.
|
||||
func (s *Service) GetBucketUsageRollups(ctx context.Context, projectID uuid.UUID, since, before time.Time) (_ []accounting.BucketUsageRollup, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
@ -8,7 +8,9 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/satellite/console"
|
||||
@ -144,5 +146,30 @@ func TestService(t *testing.T) {
|
||||
err = service.ChangeEmail(authCtx2, newEmail)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("TestGetAllBucketNames", func(t *testing.T) {
|
||||
bucket1 := storj.Bucket{
|
||||
ID: testrand.UUID(),
|
||||
Name: "testBucket1",
|
||||
ProjectID: up2Pro1.ID,
|
||||
}
|
||||
|
||||
bucket2 := storj.Bucket{
|
||||
ID: testrand.UUID(),
|
||||
Name: "testBucket2",
|
||||
ProjectID: up2Pro1.ID,
|
||||
}
|
||||
|
||||
_, err := sat.DB.Buckets().CreateBucket(authCtx1, bucket1)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sat.DB.Buckets().CreateBucket(authCtx1, bucket2)
|
||||
require.NoError(t, err)
|
||||
|
||||
bucketNames, err := service.GetAllBucketNames(authCtx1, up2Pro1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, bucket1.Name, bucketNames[0])
|
||||
require.Equal(t, bucket2.Name, bucketNames[1])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -2,13 +2,18 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { BaseGql } from '@/api/baseGql';
|
||||
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
|
||||
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
|
||||
import { HttpClient } from '@/utils/httpClient';
|
||||
|
||||
/**
|
||||
* BucketsApiGql is a graphql implementation of Buckets API.
|
||||
* Exposes all bucket-related functionality.
|
||||
*/
|
||||
export class BucketsApiGql extends BaseGql implements BucketsApi {
|
||||
private readonly client: HttpClient = new HttpClient();
|
||||
private readonly ROOT_PATH: string = '/api/v0/buckets';
|
||||
|
||||
/**
|
||||
* Fetch buckets.
|
||||
*
|
||||
@ -53,6 +58,27 @@ export class BucketsApiGql extends BaseGql implements BucketsApi {
|
||||
return this.getBucketPage(response.data.project.bucketUsages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all bucket names.
|
||||
*
|
||||
* @returns string[]
|
||||
* @throws Error
|
||||
*/
|
||||
public async getAllBucketNames(projectId: string): Promise<string[]> {
|
||||
const path = `${this.ROOT_PATH}/bucket-names?projectID=${projectId}`;
|
||||
const response = await this.client.get(path);
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new ErrorUnauthorized();
|
||||
}
|
||||
|
||||
throw new Error('Can not get account balance');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for mapping buckets page from json to BucketPage type.
|
||||
*
|
||||
|
@ -6,12 +6,14 @@ import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
|
||||
|
||||
export const BUCKET_ACTIONS = {
|
||||
FETCH: 'setBuckets',
|
||||
FETCH_ALL_BUCKET_NAMES: 'getAllBucketNames',
|
||||
SET_SEARCH: 'setBucketSearch',
|
||||
CLEAR: 'clearBuckets',
|
||||
};
|
||||
|
||||
export const BUCKET_MUTATIONS = {
|
||||
SET: 'setBuckets',
|
||||
SET_ALL_BUCKET_NAMES: 'setAllBucketNames',
|
||||
SET_SEARCH: 'setBucketSearch',
|
||||
SET_PAGE: 'setBucketPage',
|
||||
CLEAR: 'clearBuckets',
|
||||
@ -19,9 +21,11 @@ export const BUCKET_MUTATIONS = {
|
||||
|
||||
const {
|
||||
FETCH,
|
||||
FETCH_ALL_BUCKET_NAMES,
|
||||
} = BUCKET_ACTIONS;
|
||||
const {
|
||||
SET,
|
||||
SET_ALL_BUCKET_NAMES,
|
||||
SET_PAGE,
|
||||
SET_SEARCH,
|
||||
CLEAR,
|
||||
@ -30,6 +34,7 @@ const bucketPageLimit = 7;
|
||||
const firstPage = 1;
|
||||
|
||||
export class BucketsState {
|
||||
public allBucketNames: string[] = [];
|
||||
public cursor: BucketCursor = { limit: bucketPageLimit, search: '', page: firstPage };
|
||||
public page: BucketPage = { buckets: new Array<Bucket>(), currentPage: 1, pageCount: 1, offset: 0, limit: bucketPageLimit, search: '', totalCount: 0 };
|
||||
}
|
||||
@ -47,6 +52,9 @@ export function makeBucketsModule(api: BucketsApi): StoreModule<BucketsState> {
|
||||
[SET](state: BucketsState, page: BucketPage) {
|
||||
state.page = page;
|
||||
},
|
||||
[SET_ALL_BUCKET_NAMES](state: BucketsState, allBucketNames: string[]) {
|
||||
state.allBucketNames = allBucketNames;
|
||||
},
|
||||
[SET_PAGE](state: BucketsState, page: number) {
|
||||
state.cursor.page = page;
|
||||
},
|
||||
@ -54,6 +62,7 @@ export function makeBucketsModule(api: BucketsApi): StoreModule<BucketsState> {
|
||||
state.cursor.search = search;
|
||||
},
|
||||
[CLEAR](state: BucketsState) {
|
||||
state.allBucketNames = [];
|
||||
state.cursor = new BucketCursor('', bucketPageLimit, firstPage);
|
||||
state.page = new BucketPage([], '', bucketPageLimit, 0, 1, 1, 0);
|
||||
},
|
||||
@ -72,6 +81,13 @@ export function makeBucketsModule(api: BucketsApi): StoreModule<BucketsState> {
|
||||
|
||||
return result;
|
||||
},
|
||||
[FETCH_ALL_BUCKET_NAMES]: async function({commit, rootGetters}: any): Promise<string[]> {
|
||||
const result: string[] = await api.getAllBucketNames(rootGetters.selectedProject.id);
|
||||
|
||||
commit(SET_ALL_BUCKET_NAMES, result);
|
||||
|
||||
return result;
|
||||
},
|
||||
[BUCKET_ACTIONS.SET_SEARCH]: function({commit}, search: string) {
|
||||
commit(SET_SEARCH, search);
|
||||
},
|
||||
|
@ -12,6 +12,14 @@ export interface BucketsApi {
|
||||
* @throws Error
|
||||
*/
|
||||
get(projectId: string, before: Date, cursor: BucketCursor): Promise<BucketPage>;
|
||||
|
||||
/**
|
||||
* Fetch all bucket names
|
||||
*
|
||||
* @returns string[]
|
||||
* @throws Error
|
||||
*/
|
||||
getAllBucketNames(projectId: string): Promise<string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,4 +10,8 @@ export class BucketsMock implements BucketsApi {
|
||||
get(projectId: string, before: Date, cursor: BucketCursor): Promise<BucketPage> {
|
||||
return Promise.resolve(new BucketPage());
|
||||
}
|
||||
|
||||
getAllBucketNames(projectId: string): Promise<string[]> {
|
||||
return Promise.resolve(['test']);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user