web/satellite: API keys paged backend (#2839)

This commit is contained in:
Bogdan Artemenko 2019-09-12 17:19:30 +03:00 committed by GitHub
parent 0c1798407c
commit bb6086aeab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 413 additions and 71 deletions

View File

@ -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
)

View File

@ -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)
})
})
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
)

View File

@ -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

View 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
)

View File

@ -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
return nil, err
}
apiKeys = append(apiKeys, *info)
if partnerIDBytes != nil {
partnerID, err = bytesToUUID(partnerIDBytes)
if err != nil {
return nil, err
}
}
ak.PartnerID = partnerID
apiKeys = append(apiKeys, ak)
}
if err := parseErr.Err(); err != nil {
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 apiKeys, nil
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"
}

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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
}