web/satellite: API keys paged backend (#2839)
This commit is contained in:
parent
0c1798407c
commit
bb6086aeab
@ -14,8 +14,8 @@ import (
|
||||
//
|
||||
// architecture: Database
|
||||
type APIKeys interface {
|
||||
// GetByProjectID retrieves list of APIKeys for given projectID
|
||||
GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]APIKeyInfo, error)
|
||||
// GetPagedByProjectID is a method for querying API keys from the database by projectID and cursor
|
||||
GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor APIKeyCursor) (akp *APIKeyPage, err error)
|
||||
// Get retrieves APIKeyInfo with given ID
|
||||
Get(ctx context.Context, id uuid.UUID) (*APIKeyInfo, error)
|
||||
// GetByHead retrieves APIKeyInfo for given key head
|
||||
@ -37,3 +37,37 @@ type APIKeyInfo struct {
|
||||
Secret []byte `json:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// APIKeyCursor holds info for api keys cursor pagination
|
||||
type APIKeyCursor struct {
|
||||
Search string
|
||||
Limit uint
|
||||
Page uint
|
||||
Order APIKeyOrder
|
||||
OrderDirection OrderDirection
|
||||
}
|
||||
|
||||
// APIKeyPage represent api key page result
|
||||
type APIKeyPage struct {
|
||||
APIKeys []APIKeyInfo
|
||||
|
||||
Search string
|
||||
Limit uint
|
||||
Order APIKeyOrder
|
||||
OrderDirection OrderDirection
|
||||
Offset uint64
|
||||
|
||||
PageCount uint
|
||||
CurrentPage uint
|
||||
TotalCount uint64
|
||||
}
|
||||
|
||||
// APIKeyOrder is used for querying api keys in specified order
|
||||
type APIKeyOrder uint8
|
||||
|
||||
const (
|
||||
// KeyName indicates that we should order by key name
|
||||
KeyName APIKeyOrder = 1
|
||||
// CreationDate indicates that we should order by creation date
|
||||
CreationDate APIKeyOrder = 2
|
||||
)
|
||||
|
@ -48,34 +48,65 @@ func TestApiKeysRepository(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetByProjectID success", func(t *testing.T) {
|
||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
||||
assert.NotNil(t, keys)
|
||||
assert.Equal(t, len(keys), 10)
|
||||
t.Run("GetPagedByProjectID success", func(t *testing.T) {
|
||||
cursor := console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
Search: "",
|
||||
}
|
||||
page, err := apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
|
||||
assert.NotNil(t, page)
|
||||
assert.Equal(t, len(page.APIKeys), 10)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("GetPagedByProjectID with limit success", func(t *testing.T) {
|
||||
cursor := console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 2,
|
||||
Search: "",
|
||||
}
|
||||
page, err := apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
|
||||
assert.NotNil(t, page)
|
||||
assert.Equal(t, len(page.APIKeys), 2)
|
||||
assert.Equal(t, page.PageCount, uint(5))
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get By ID success", func(t *testing.T) {
|
||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
||||
assert.NotNil(t, keys)
|
||||
assert.Equal(t, len(keys), 10)
|
||||
cursor := console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
Search: "",
|
||||
}
|
||||
page, err := apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
|
||||
assert.NotNil(t, page)
|
||||
assert.Equal(t, len(page.APIKeys), 10)
|
||||
assert.NoError(t, err)
|
||||
|
||||
key, err := apikeys.Get(ctx, keys[0].ID)
|
||||
key, err := apikeys.Get(ctx, page.APIKeys[0].ID)
|
||||
assert.NotNil(t, key)
|
||||
assert.Equal(t, keys[0].ID, key.ID)
|
||||
assert.Equal(t, page.APIKeys[0].ID, key.ID)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Update success", func(t *testing.T) {
|
||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
||||
assert.NotNil(t, keys)
|
||||
assert.Equal(t, len(keys), 10)
|
||||
cursor := console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
Search: "",
|
||||
}
|
||||
page, err := apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
assert.NotNil(t, page)
|
||||
assert.Equal(t, len(page.APIKeys), 10)
|
||||
assert.NoError(t, err)
|
||||
|
||||
key, err := apikeys.Get(ctx, keys[0].ID)
|
||||
key, err := apikeys.Get(ctx, page.APIKeys[0].ID)
|
||||
assert.NotNil(t, key)
|
||||
assert.Equal(t, keys[0].ID, key.ID)
|
||||
assert.Equal(t, page.APIKeys[0].ID, key.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
key.Name = "some new name"
|
||||
@ -83,21 +114,26 @@ func TestApiKeysRepository(t *testing.T) {
|
||||
err = apikeys.Update(ctx, *key)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updatedKey, err := apikeys.Get(ctx, keys[0].ID)
|
||||
updatedKey, err := apikeys.Get(ctx, page.APIKeys[0].ID)
|
||||
assert.NotNil(t, key)
|
||||
assert.Equal(t, key.Name, updatedKey.Name)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Delete success", func(t *testing.T) {
|
||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
||||
assert.NotNil(t, keys)
|
||||
assert.Equal(t, len(keys), 10)
|
||||
cursor := console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
Search: "",
|
||||
}
|
||||
page, err := apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
assert.NotNil(t, page)
|
||||
assert.Equal(t, len(page.APIKeys), 10)
|
||||
assert.NoError(t, err)
|
||||
|
||||
key, err := apikeys.Get(ctx, keys[0].ID)
|
||||
key, err := apikeys.Get(ctx, page.APIKeys[0].ID)
|
||||
assert.NotNil(t, key)
|
||||
assert.Equal(t, keys[0].ID, key.ID)
|
||||
assert.Equal(t, page.APIKeys[0].ID, key.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
key.Name = "some new name"
|
||||
@ -105,10 +141,23 @@ func TestApiKeysRepository(t *testing.T) {
|
||||
err = apikeys.Delete(ctx, key.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
keys, err = apikeys.GetByProjectID(ctx, project.ID)
|
||||
assert.NotNil(t, keys)
|
||||
assert.Equal(t, len(keys), 9)
|
||||
page, err = apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
assert.NotNil(t, page)
|
||||
assert.Equal(t, len(page.APIKeys), 9)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("GetPageByProjectID with 0 page error", func(t *testing.T) {
|
||||
cursor := console.APIKeyCursor{
|
||||
Page: 0,
|
||||
Limit: 10,
|
||||
Search: "",
|
||||
}
|
||||
page, err := apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||
|
||||
assert.Nil(t, page)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
@ -58,8 +58,80 @@ func graphqlCreateAPIKey(types *TypeCreator) *graphql.Object {
|
||||
})
|
||||
}
|
||||
|
||||
func graphqlAPIKeysCursor() *graphql.InputObject {
|
||||
return graphql.NewInputObject(graphql.InputObjectConfig{
|
||||
Name: APIKeysCursorInputType,
|
||||
Fields: graphql.InputObjectConfigFieldMap{
|
||||
SearchArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
},
|
||||
LimitArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
PageArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
OrderArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
OrderDirectionArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func graphqlAPIKeysPage(types *TypeCreator) *graphql.Object {
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: APIKeysPageType,
|
||||
Fields: graphql.Fields{
|
||||
FieldAPIKeys: &graphql.Field{
|
||||
Type: graphql.NewList(types.apiKeyInfo),
|
||||
},
|
||||
SearchArg: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
},
|
||||
LimitArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
OrderArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
OrderDirectionArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
OffsetArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
FieldPageCount: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
FieldCurrentPage: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
FieldTotalCount: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// createAPIKey holds macaroon.APIKey and console.APIKeyInfo
|
||||
type createAPIKey struct {
|
||||
Key string
|
||||
KeyInfo *console.APIKeyInfo
|
||||
}
|
||||
|
||||
type apiKeysPage struct {
|
||||
APIKeys []console.APIKeyInfo
|
||||
|
||||
Search string
|
||||
Limit uint
|
||||
Order int
|
||||
OrderDirection int
|
||||
Offset uint64
|
||||
|
||||
PageCount uint
|
||||
CurrentPage uint
|
||||
TotalCount uint64
|
||||
}
|
||||
|
@ -31,6 +31,10 @@ const (
|
||||
ProjectMembersPageType = "projectMembersPage"
|
||||
// ProjectMembersCursorInputType is a graphql type name for project members
|
||||
ProjectMembersCursorInputType = "projectMembersCursor"
|
||||
// APIKeysPageType is a field name for api keys page
|
||||
APIKeysPageType = "apiKeysPage"
|
||||
// APIKeysCursorInputType is a graphql type name for api keys
|
||||
APIKeysCursorInputType = "apiKeysCursor"
|
||||
// FieldName is a field name for "name"
|
||||
FieldName = "name"
|
||||
// FieldBucketName is a field name for "bucket name"
|
||||
@ -155,11 +159,39 @@ func graphqlProject(service *console.Service, types *TypeCreator) *graphql.Objec
|
||||
},
|
||||
},
|
||||
FieldAPIKeys: &graphql.Field{
|
||||
Type: graphql.NewList(types.apiKeyInfo),
|
||||
Type: types.apiKeyPage,
|
||||
Args: graphql.FieldConfigArgument{
|
||||
CursorArg: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(types.apiKeysCursor),
|
||||
},
|
||||
},
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
project, _ := p.Source.(*console.Project)
|
||||
|
||||
return service.GetAPIKeysInfoByProjectID(p.Context, project.ID)
|
||||
_, err := console.GetAuth(p.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cursor := cursorArgsToAPIKeysCursor(p.Args[CursorArg].(map[string]interface{}))
|
||||
page, err := service.GetAPIKeys(p.Context, project.ID, cursor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiKeysPage := apiKeysPage{
|
||||
APIKeys: page.APIKeys,
|
||||
TotalCount: page.TotalCount,
|
||||
Offset: page.Offset,
|
||||
Limit: page.Limit,
|
||||
Order: int(page.Order),
|
||||
OrderDirection: int(page.OrderDirection),
|
||||
Search: page.Search,
|
||||
CurrentPage: page.CurrentPage,
|
||||
PageCount: page.PageCount,
|
||||
}
|
||||
|
||||
return apiKeysPage, err
|
||||
},
|
||||
},
|
||||
FieldUsage: &graphql.Field{
|
||||
@ -442,7 +474,24 @@ func cursorArgsToProjectMembersCursor(args map[string]interface{}) console.Proje
|
||||
cursor.Limit = uint(limit)
|
||||
cursor.Page = uint(page)
|
||||
cursor.Order = console.ProjectMemberOrder(order)
|
||||
cursor.OrderDirection = console.ProjectMemberOrderDirection(orderDirection)
|
||||
cursor.OrderDirection = console.OrderDirection(orderDirection)
|
||||
cursor.Search, _ = args[SearchArg].(string)
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
func cursorArgsToAPIKeysCursor(args map[string]interface{}) console.APIKeyCursor {
|
||||
limit, _ := args[LimitArg].(int)
|
||||
page, _ := args[PageArg].(int)
|
||||
order, _ := args[OrderArg].(int)
|
||||
orderDirection, _ := args[OrderDirectionArg].(int)
|
||||
|
||||
var cursor console.APIKeyCursor
|
||||
|
||||
cursor.Limit = uint(limit)
|
||||
cursor.Page = uint(page)
|
||||
cursor.Order = console.APIKeyOrder(order)
|
||||
cursor.OrderDirection = console.OrderDirection(orderDirection)
|
||||
cursor.Search, _ = args[SearchArg].(string)
|
||||
|
||||
return cursor
|
||||
|
@ -301,17 +301,22 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
|
||||
t.Run("Project query api keys", func(t *testing.T) {
|
||||
query := fmt.Sprintf(
|
||||
"query {project(id:\"%s\"){apiKeys{name,id,createdAt,projectID}}}",
|
||||
"query {project(id: \"%s\") {apiKeys( cursor: { limit: %d, search: \"%s\", page: %d, order: %d, orderDirection: %d } ) { apiKeys { id, name, createdAt, projectID }, search, limit, order, offset, pageCount, currentPage, totalCount } } }",
|
||||
createdProject.ID.String(),
|
||||
)
|
||||
5,
|
||||
"",
|
||||
1,
|
||||
1,
|
||||
2)
|
||||
|
||||
result := testQuery(t, query)
|
||||
|
||||
data := result.(map[string]interface{})
|
||||
project := data[consoleql.ProjectQuery].(map[string]interface{})
|
||||
keys := project[consoleql.FieldAPIKeys].([]interface{})
|
||||
keys := project[consoleql.FieldAPIKeys].(map[string]interface{})
|
||||
apiKeys := keys[consoleql.FieldAPIKeys].([]interface{})
|
||||
|
||||
assert.Equal(t, 2, len(keys))
|
||||
assert.Equal(t, 2, len(apiKeys))
|
||||
|
||||
testAPIKey := func(t *testing.T, actual map[string]interface{}, expected *console.APIKeyInfo) {
|
||||
assert.Equal(t, expected.Name, actual[consoleql.FieldName])
|
||||
@ -326,7 +331,7 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
|
||||
var foundKey1, foundKey2 bool
|
||||
|
||||
for _, entry := range keys {
|
||||
for _, entry := range apiKeys {
|
||||
key := entry.(map[string]interface{})
|
||||
|
||||
id := key[consoleql.FieldID].(string)
|
||||
|
@ -28,6 +28,7 @@ type TypeCreator struct {
|
||||
paymentMethod *graphql.Object
|
||||
projectMember *graphql.Object
|
||||
projectMemberPage *graphql.Object
|
||||
apiKeyPage *graphql.Object
|
||||
apiKeyInfo *graphql.Object
|
||||
createAPIKey *graphql.Object
|
||||
|
||||
@ -35,6 +36,7 @@ type TypeCreator struct {
|
||||
projectInput *graphql.InputObject
|
||||
bucketUsageCursor *graphql.InputObject
|
||||
projectMembersCursor *graphql.InputObject
|
||||
apiKeysCursor *graphql.InputObject
|
||||
}
|
||||
|
||||
// Create create types and check for error
|
||||
@ -60,6 +62,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
||||
return err
|
||||
}
|
||||
|
||||
c.apiKeysCursor = graphqlAPIKeysCursor()
|
||||
if err := c.apiKeysCursor.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// entities
|
||||
c.user = graphqlUser()
|
||||
if err := c.user.Error(); err != nil {
|
||||
@ -116,6 +123,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
||||
return err
|
||||
}
|
||||
|
||||
c.apiKeyPage = graphqlAPIKeysPage(c)
|
||||
if err := c.apiKeyPage.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.project = graphqlProject(service, c)
|
||||
if err := c.project.Error(); err != nil {
|
||||
return err
|
||||
|
@ -40,7 +40,7 @@ type ProjectMembersCursor struct {
|
||||
Limit uint
|
||||
Page uint
|
||||
Order ProjectMemberOrder
|
||||
OrderDirection ProjectMemberOrderDirection
|
||||
OrderDirection OrderDirection
|
||||
}
|
||||
|
||||
// ProjectMembersPage represent project members page result
|
||||
@ -50,7 +50,7 @@ type ProjectMembersPage struct {
|
||||
Search string
|
||||
Limit uint
|
||||
Order ProjectMemberOrder
|
||||
OrderDirection ProjectMemberOrderDirection
|
||||
OrderDirection OrderDirection
|
||||
Offset uint64
|
||||
|
||||
PageCount uint
|
||||
@ -69,13 +69,3 @@ const (
|
||||
// Created indicates that we should order by created date
|
||||
Created ProjectMemberOrder = 3
|
||||
)
|
||||
|
||||
// ProjectMemberOrderDirection is used for querying project members in specific order direction
|
||||
type ProjectMemberOrderDirection uint8
|
||||
|
||||
const (
|
||||
// Ascending indicates that we should order ascending
|
||||
Ascending ProjectMemberOrderDirection = 1
|
||||
// Descending indicates that we should order descending
|
||||
Descending ProjectMemberOrderDirection = 2
|
||||
)
|
||||
|
@ -1053,9 +1053,10 @@ func (s *Service) DeleteAPIKeys(ctx context.Context, ids []uuid.UUID) (err error
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAPIKeysInfoByProjectID retrieves all api keys for a given project
|
||||
func (s *Service) GetAPIKeysInfoByProjectID(ctx context.Context, projectID uuid.UUID) (info []APIKeyInfo, err error) {
|
||||
// GetAPIKeys returns paged api key list for given Project
|
||||
func (s *Service) GetAPIKeys(ctx context.Context, projectID uuid.UUID, cursor APIKeyCursor) (page *APIKeyPage, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := GetAuth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1066,12 +1067,16 @@ func (s *Service) GetAPIKeysInfoByProjectID(ctx context.Context, projectID uuid.
|
||||
return nil, ErrUnauthorized.Wrap(err)
|
||||
}
|
||||
|
||||
info, err = s.store.APIKeys().GetByProjectID(ctx, projectID)
|
||||
if cursor.Limit > maxLimit {
|
||||
cursor.Limit = maxLimit
|
||||
}
|
||||
|
||||
page, err = s.store.APIKeys().GetPagedByProjectID(ctx, projectID, cursor)
|
||||
if err != nil {
|
||||
return nil, ErrConsoleInternal.Wrap(err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
return
|
||||
}
|
||||
|
||||
// GetProjectUsage retrieves project usage for a given period
|
||||
|
14
satellite/console/sortdirection.go
Normal file
14
satellite/console/sortdirection.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package console
|
||||
|
||||
// OrderDirection is used for members in specific order direction
|
||||
type OrderDirection uint8
|
||||
|
||||
const (
|
||||
// Ascending indicates that we should order ascending
|
||||
Ascending OrderDirection = 1
|
||||
// Descending indicates that we should order descending
|
||||
Descending OrderDirection = 2
|
||||
)
|
@ -5,6 +5,7 @@ package satellitedb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/zeebo/errs"
|
||||
@ -15,41 +16,123 @@ import (
|
||||
|
||||
// apikeys is an implementation of satellite.APIKeys
|
||||
type apikeys struct {
|
||||
db dbx.Methods
|
||||
methods dbx.Methods
|
||||
db *dbx.DB
|
||||
}
|
||||
|
||||
// GetByProjectID implements satellite.APIKeys ordered by name
|
||||
func (keys *apikeys) GetByProjectID(ctx context.Context, projectID uuid.UUID) (_ []console.APIKeyInfo, err error) {
|
||||
func (keys *apikeys) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.APIKeyCursor) (akp *console.APIKeyPage, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
dbKeys, err := keys.db.All_ApiKey_By_ProjectId_OrderBy_Asc_Name(ctx, dbx.ApiKey_ProjectId(projectID[:]))
|
||||
|
||||
search := "%" + strings.Replace(cursor.Search, " ", "%", -1) + "%"
|
||||
|
||||
if cursor.Limit > 50 {
|
||||
cursor.Limit = 50
|
||||
}
|
||||
|
||||
if cursor.Page == 0 {
|
||||
return nil, errs.New("page cannot be 0")
|
||||
}
|
||||
|
||||
page := &console.APIKeyPage{
|
||||
Search: cursor.Search,
|
||||
Limit: cursor.Limit,
|
||||
Offset: uint64((cursor.Page - 1) * cursor.Limit),
|
||||
Order: cursor.Order,
|
||||
OrderDirection: cursor.OrderDirection,
|
||||
}
|
||||
|
||||
countQuery := keys.db.Rebind(`
|
||||
SELECT COUNT(*)
|
||||
FROM api_keys ak
|
||||
WHERE ak.project_id = ?
|
||||
AND ak.name LIKE ?
|
||||
`)
|
||||
|
||||
countRow := keys.db.QueryRowContext(ctx,
|
||||
countQuery,
|
||||
projectID[:],
|
||||
search)
|
||||
|
||||
err = countRow.Scan(&page.TotalCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if page.TotalCount == 0 {
|
||||
return page, nil
|
||||
}
|
||||
if page.Offset > page.TotalCount-1 {
|
||||
return nil, errs.New("page is out of range")
|
||||
}
|
||||
|
||||
repoundQuery := keys.db.Rebind(`
|
||||
SELECT ak.id, ak.project_id, ak.name, ak.partner_id, ak.created_at
|
||||
FROM api_keys ak
|
||||
WHERE ak.project_id = ?
|
||||
AND ak.name LIKE ?
|
||||
ORDER BY ` + sanitizedAPIKeyOrderColumnName(cursor.Order) + `
|
||||
` + sanitizeOrderDirectionName(page.OrderDirection) + `
|
||||
LIMIT ? OFFSET ?`)
|
||||
|
||||
rows, err := keys.db.QueryContext(ctx,
|
||||
repoundQuery,
|
||||
projectID[:],
|
||||
search,
|
||||
page.Limit,
|
||||
page.Offset)
|
||||
|
||||
defer func() {
|
||||
err = errs.Combine(err, rows.Close())
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiKeys []console.APIKeyInfo
|
||||
var parseErr errs.Group
|
||||
for rows.Next() {
|
||||
ak := console.APIKeyInfo{}
|
||||
var partnerIDBytes []uint8
|
||||
var partnerID uuid.UUID
|
||||
|
||||
for _, key := range dbKeys {
|
||||
info, err := fromDBXAPIKey(ctx, key)
|
||||
err = rows.Scan(&uuidScan{&ak.ID}, &uuidScan{&ak.ProjectID}, &ak.Name, &partnerIDBytes, &ak.CreatedAt)
|
||||
if err != nil {
|
||||
parseErr.Add(err)
|
||||
continue
|
||||
}
|
||||
|
||||
apiKeys = append(apiKeys, *info)
|
||||
}
|
||||
|
||||
if err := parseErr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apiKeys, nil
|
||||
if partnerIDBytes != nil {
|
||||
partnerID, err = bytesToUUID(partnerIDBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ak.PartnerID = partnerID
|
||||
|
||||
apiKeys = append(apiKeys, ak)
|
||||
}
|
||||
|
||||
page.APIKeys = apiKeys
|
||||
page.Order = cursor.Order
|
||||
|
||||
page.PageCount = uint(page.TotalCount / uint64(cursor.Limit))
|
||||
if page.TotalCount%uint64(cursor.Limit) != 0 {
|
||||
page.PageCount++
|
||||
}
|
||||
|
||||
page.CurrentPage = cursor.Page
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page, err
|
||||
}
|
||||
|
||||
// Get implements satellite.APIKeys
|
||||
func (keys *apikeys) Get(ctx context.Context, id uuid.UUID) (_ *console.APIKeyInfo, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
dbKey, err := keys.db.Get_ApiKey_By_Id(ctx, dbx.ApiKey_Id(id[:]))
|
||||
dbKey, err := keys.methods.Get_ApiKey_By_Id(ctx, dbx.ApiKey_Id(id[:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -60,7 +143,7 @@ func (keys *apikeys) Get(ctx context.Context, id uuid.UUID) (_ *console.APIKeyIn
|
||||
// GetByHead implements satellite.APIKeys
|
||||
func (keys *apikeys) GetByHead(ctx context.Context, head []byte) (_ *console.APIKeyInfo, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
dbKey, err := keys.db.Get_ApiKey_By_Head(ctx, dbx.ApiKey_Head(head))
|
||||
dbKey, err := keys.methods.Get_ApiKey_By_Head(ctx, dbx.ApiKey_Head(head))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -81,7 +164,7 @@ func (keys *apikeys) Create(ctx context.Context, head []byte, info console.APIKe
|
||||
optional.PartnerId = dbx.ApiKey_PartnerId(info.PartnerID[:])
|
||||
}
|
||||
|
||||
dbKey, err := keys.db.Create_ApiKey(
|
||||
dbKey, err := keys.methods.Create_ApiKey(
|
||||
ctx,
|
||||
dbx.ApiKey_Id(id[:]),
|
||||
dbx.ApiKey_ProjectId(info.ProjectID[:]),
|
||||
@ -101,7 +184,7 @@ func (keys *apikeys) Create(ctx context.Context, head []byte, info console.APIKe
|
||||
// Update implements satellite.APIKeys
|
||||
func (keys *apikeys) Update(ctx context.Context, key console.APIKeyInfo) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
_, err = keys.db.Update_ApiKey_By_Id(
|
||||
_, err = keys.methods.Update_ApiKey_By_Id(
|
||||
ctx,
|
||||
dbx.ApiKey_Id(key.ID[:]),
|
||||
dbx.ApiKey_Update_Fields{
|
||||
@ -115,7 +198,7 @@ func (keys *apikeys) Update(ctx context.Context, key console.APIKeyInfo) (err er
|
||||
// Delete implements satellite.APIKeys
|
||||
func (keys *apikeys) Delete(ctx context.Context, id uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
_, err = keys.db.Delete_ApiKey_By_Id(ctx, dbx.ApiKey_Id(id[:]))
|
||||
_, err = keys.methods.Delete_ApiKey_By_Id(ctx, dbx.ApiKey_Id(id[:]))
|
||||
return err
|
||||
}
|
||||
|
||||
@ -149,3 +232,12 @@ func fromDBXAPIKey(ctx context.Context, key *dbx.ApiKey) (_ *console.APIKeyInfo,
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// sanitizedAPIKeyOrderColumnName return valid order by column
|
||||
func sanitizedAPIKeyOrderColumnName(pmo console.APIKeyOrder) string {
|
||||
if pmo == 2 {
|
||||
return "ak.created_at"
|
||||
}
|
||||
|
||||
return "ak.name"
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func (db *ConsoleDB) ProjectMembers() console.ProjectMembers {
|
||||
|
||||
// APIKeys is a getter for APIKeys repository
|
||||
func (db *ConsoleDB) APIKeys() console.APIKeys {
|
||||
return &apikeys{db.methods}
|
||||
return &apikeys{db.methods, db.db}
|
||||
}
|
||||
|
||||
// BucketUsage is a getter for accounting.BucketUsage repository
|
||||
|
@ -184,11 +184,11 @@ func (m *lockedAPIKeys) GetByHead(ctx context.Context, head []byte) (*console.AP
|
||||
return m.db.GetByHead(ctx, head)
|
||||
}
|
||||
|
||||
// GetByProjectID retrieves list of APIKeys for given projectID
|
||||
func (m *lockedAPIKeys) GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]console.APIKeyInfo, error) {
|
||||
// GetPagedByProjectID is a method for querying API keys from the database by projectID and cursor
|
||||
func (m *lockedAPIKeys) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.APIKeyCursor) (akp *console.APIKeyPage, err error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.db.GetByProjectID(ctx, projectID)
|
||||
return m.db.GetPagedByProjectID(ctx, projectID, cursor)
|
||||
}
|
||||
|
||||
// Update updates APIKeyInfo in store
|
||||
|
@ -211,7 +211,7 @@ func sanitizedOrderColumnName(pmo console.ProjectMemberOrder) string {
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeOrderDirectionName(pmo console.ProjectMemberOrderDirection) string {
|
||||
func sanitizeOrderDirectionName(pmo console.OrderDirection) string {
|
||||
if pmo == 2 {
|
||||
return "DESC"
|
||||
}
|
||||
|
@ -65,3 +65,23 @@ func (nodes postgresNodeIDList) Value() (driver.Value, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// uuidScan used to represent uuid scan struct
|
||||
type uuidScan struct {
|
||||
uuid *uuid.UUID
|
||||
}
|
||||
|
||||
// Scan is used to wrap logic of db scan with uuid conversion
|
||||
func (s *uuidScan) Scan(src interface{}) (err error) {
|
||||
b, ok := src.([]byte)
|
||||
if !ok {
|
||||
return Error.New("unexpected type %T for uuid", src)
|
||||
}
|
||||
|
||||
*s.uuid, err = bytesToUUID(b)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user