web/satellite: API keys paged backend (#2839)
This commit is contained in:
parent
0c1798407c
commit
bb6086aeab
@ -14,8 +14,8 @@ import (
|
|||||||
//
|
//
|
||||||
// architecture: Database
|
// architecture: Database
|
||||||
type APIKeys interface {
|
type APIKeys interface {
|
||||||
// GetByProjectID retrieves list of APIKeys for given projectID
|
// GetPagedByProjectID is a method for querying API keys from the database by projectID and cursor
|
||||||
GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]APIKeyInfo, error)
|
GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor APIKeyCursor) (akp *APIKeyPage, err error)
|
||||||
// Get retrieves APIKeyInfo with given ID
|
// Get retrieves APIKeyInfo with given ID
|
||||||
Get(ctx context.Context, id uuid.UUID) (*APIKeyInfo, error)
|
Get(ctx context.Context, id uuid.UUID) (*APIKeyInfo, error)
|
||||||
// GetByHead retrieves APIKeyInfo for given key head
|
// GetByHead retrieves APIKeyInfo for given key head
|
||||||
@ -37,3 +37,37 @@ type APIKeyInfo struct {
|
|||||||
Secret []byte `json:"-"`
|
Secret []byte `json:"-"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
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) {
|
t.Run("GetPagedByProjectID success", func(t *testing.T) {
|
||||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
cursor := console.APIKeyCursor{
|
||||||
assert.NotNil(t, keys)
|
Page: 1,
|
||||||
assert.Equal(t, len(keys), 10)
|
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)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Get By ID success", func(t *testing.T) {
|
t.Run("Get By ID success", func(t *testing.T) {
|
||||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
cursor := console.APIKeyCursor{
|
||||||
assert.NotNil(t, keys)
|
Page: 1,
|
||||||
assert.Equal(t, len(keys), 10)
|
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)
|
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.NotNil(t, key)
|
||||||
assert.Equal(t, keys[0].ID, key.ID)
|
assert.Equal(t, page.APIKeys[0].ID, key.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Update success", func(t *testing.T) {
|
t.Run("Update success", func(t *testing.T) {
|
||||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
cursor := console.APIKeyCursor{
|
||||||
assert.NotNil(t, keys)
|
Page: 1,
|
||||||
assert.Equal(t, len(keys), 10)
|
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)
|
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.NotNil(t, key)
|
||||||
assert.Equal(t, keys[0].ID, key.ID)
|
assert.Equal(t, page.APIKeys[0].ID, key.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
key.Name = "some new name"
|
key.Name = "some new name"
|
||||||
@ -83,21 +114,26 @@ func TestApiKeysRepository(t *testing.T) {
|
|||||||
err = apikeys.Update(ctx, *key)
|
err = apikeys.Update(ctx, *key)
|
||||||
assert.NoError(t, err)
|
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.NotNil(t, key)
|
||||||
assert.Equal(t, key.Name, updatedKey.Name)
|
assert.Equal(t, key.Name, updatedKey.Name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Delete success", func(t *testing.T) {
|
t.Run("Delete success", func(t *testing.T) {
|
||||||
keys, err := apikeys.GetByProjectID(ctx, project.ID)
|
cursor := console.APIKeyCursor{
|
||||||
assert.NotNil(t, keys)
|
Page: 1,
|
||||||
assert.Equal(t, len(keys), 10)
|
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)
|
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.NotNil(t, key)
|
||||||
assert.Equal(t, keys[0].ID, key.ID)
|
assert.Equal(t, page.APIKeys[0].ID, key.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
key.Name = "some new name"
|
key.Name = "some new name"
|
||||||
@ -105,10 +141,23 @@ func TestApiKeysRepository(t *testing.T) {
|
|||||||
err = apikeys.Delete(ctx, key.ID)
|
err = apikeys.Delete(ctx, key.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
keys, err = apikeys.GetByProjectID(ctx, project.ID)
|
page, err = apikeys.GetPagedByProjectID(ctx, project.ID, cursor)
|
||||||
assert.NotNil(t, keys)
|
assert.NotNil(t, page)
|
||||||
assert.Equal(t, len(keys), 9)
|
assert.Equal(t, len(page.APIKeys), 9)
|
||||||
assert.NoError(t, err)
|
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
|
// createAPIKey holds macaroon.APIKey and console.APIKeyInfo
|
||||||
type createAPIKey struct {
|
type createAPIKey struct {
|
||||||
Key string
|
Key string
|
||||||
KeyInfo *console.APIKeyInfo
|
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"
|
ProjectMembersPageType = "projectMembersPage"
|
||||||
// ProjectMembersCursorInputType is a graphql type name for project members
|
// ProjectMembersCursorInputType is a graphql type name for project members
|
||||||
ProjectMembersCursorInputType = "projectMembersCursor"
|
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 is a field name for "name"
|
||||||
FieldName = "name"
|
FieldName = "name"
|
||||||
// FieldBucketName is a field name for "bucket 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{
|
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) {
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||||
project, _ := p.Source.(*console.Project)
|
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{
|
FieldUsage: &graphql.Field{
|
||||||
@ -442,7 +474,24 @@ func cursorArgsToProjectMembersCursor(args map[string]interface{}) console.Proje
|
|||||||
cursor.Limit = uint(limit)
|
cursor.Limit = uint(limit)
|
||||||
cursor.Page = uint(page)
|
cursor.Page = uint(page)
|
||||||
cursor.Order = console.ProjectMemberOrder(order)
|
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)
|
cursor.Search, _ = args[SearchArg].(string)
|
||||||
|
|
||||||
return cursor
|
return cursor
|
||||||
|
@ -301,17 +301,22 @@ func TestGraphqlQuery(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Project query api keys", func(t *testing.T) {
|
t.Run("Project query api keys", func(t *testing.T) {
|
||||||
query := fmt.Sprintf(
|
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(),
|
createdProject.ID.String(),
|
||||||
)
|
5,
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2)
|
||||||
|
|
||||||
result := testQuery(t, query)
|
result := testQuery(t, query)
|
||||||
|
|
||||||
data := result.(map[string]interface{})
|
data := result.(map[string]interface{})
|
||||||
project := data[consoleql.ProjectQuery].(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) {
|
testAPIKey := func(t *testing.T, actual map[string]interface{}, expected *console.APIKeyInfo) {
|
||||||
assert.Equal(t, expected.Name, actual[consoleql.FieldName])
|
assert.Equal(t, expected.Name, actual[consoleql.FieldName])
|
||||||
@ -326,7 +331,7 @@ func TestGraphqlQuery(t *testing.T) {
|
|||||||
|
|
||||||
var foundKey1, foundKey2 bool
|
var foundKey1, foundKey2 bool
|
||||||
|
|
||||||
for _, entry := range keys {
|
for _, entry := range apiKeys {
|
||||||
key := entry.(map[string]interface{})
|
key := entry.(map[string]interface{})
|
||||||
|
|
||||||
id := key[consoleql.FieldID].(string)
|
id := key[consoleql.FieldID].(string)
|
||||||
|
@ -28,6 +28,7 @@ type TypeCreator struct {
|
|||||||
paymentMethod *graphql.Object
|
paymentMethod *graphql.Object
|
||||||
projectMember *graphql.Object
|
projectMember *graphql.Object
|
||||||
projectMemberPage *graphql.Object
|
projectMemberPage *graphql.Object
|
||||||
|
apiKeyPage *graphql.Object
|
||||||
apiKeyInfo *graphql.Object
|
apiKeyInfo *graphql.Object
|
||||||
createAPIKey *graphql.Object
|
createAPIKey *graphql.Object
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ type TypeCreator struct {
|
|||||||
projectInput *graphql.InputObject
|
projectInput *graphql.InputObject
|
||||||
bucketUsageCursor *graphql.InputObject
|
bucketUsageCursor *graphql.InputObject
|
||||||
projectMembersCursor *graphql.InputObject
|
projectMembersCursor *graphql.InputObject
|
||||||
|
apiKeysCursor *graphql.InputObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create create types and check for error
|
// Create create types and check for error
|
||||||
@ -60,6 +62,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.apiKeysCursor = graphqlAPIKeysCursor()
|
||||||
|
if err := c.apiKeysCursor.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// entities
|
// entities
|
||||||
c.user = graphqlUser()
|
c.user = graphqlUser()
|
||||||
if err := c.user.Error(); err != nil {
|
if err := c.user.Error(); err != nil {
|
||||||
@ -116,6 +123,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.apiKeyPage = graphqlAPIKeysPage(c)
|
||||||
|
if err := c.apiKeyPage.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
c.project = graphqlProject(service, c)
|
c.project = graphqlProject(service, c)
|
||||||
if err := c.project.Error(); err != nil {
|
if err := c.project.Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -40,7 +40,7 @@ type ProjectMembersCursor struct {
|
|||||||
Limit uint
|
Limit uint
|
||||||
Page uint
|
Page uint
|
||||||
Order ProjectMemberOrder
|
Order ProjectMemberOrder
|
||||||
OrderDirection ProjectMemberOrderDirection
|
OrderDirection OrderDirection
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProjectMembersPage represent project members page result
|
// ProjectMembersPage represent project members page result
|
||||||
@ -50,7 +50,7 @@ type ProjectMembersPage struct {
|
|||||||
Search string
|
Search string
|
||||||
Limit uint
|
Limit uint
|
||||||
Order ProjectMemberOrder
|
Order ProjectMemberOrder
|
||||||
OrderDirection ProjectMemberOrderDirection
|
OrderDirection OrderDirection
|
||||||
Offset uint64
|
Offset uint64
|
||||||
|
|
||||||
PageCount uint
|
PageCount uint
|
||||||
@ -69,13 +69,3 @@ const (
|
|||||||
// Created indicates that we should order by created date
|
// Created indicates that we should order by created date
|
||||||
Created ProjectMemberOrder = 3
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAPIKeysInfoByProjectID retrieves all api keys for a given project
|
// GetAPIKeys returns paged api key list for given Project
|
||||||
func (s *Service) GetAPIKeysInfoByProjectID(ctx context.Context, projectID uuid.UUID) (info []APIKeyInfo, err error) {
|
func (s *Service) GetAPIKeys(ctx context.Context, projectID uuid.UUID, cursor APIKeyCursor) (page *APIKeyPage, err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
auth, err := GetAuth(ctx)
|
auth, err := GetAuth(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1066,12 +1067,16 @@ func (s *Service) GetAPIKeysInfoByProjectID(ctx context.Context, projectID uuid.
|
|||||||
return nil, ErrUnauthorized.Wrap(err)
|
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 {
|
if err != nil {
|
||||||
return nil, ErrConsoleInternal.Wrap(err)
|
return nil, ErrConsoleInternal.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return info, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProjectUsage retrieves project usage for a given period
|
// 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/skyrings/skyring-common/tools/uuid"
|
"github.com/skyrings/skyring-common/tools/uuid"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
@ -15,41 +16,123 @@ import (
|
|||||||
|
|
||||||
// apikeys is an implementation of satellite.APIKeys
|
// apikeys is an implementation of satellite.APIKeys
|
||||||
type apikeys struct {
|
type apikeys struct {
|
||||||
db dbx.Methods
|
methods dbx.Methods
|
||||||
|
db *dbx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByProjectID implements satellite.APIKeys ordered by name
|
func (keys *apikeys) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.APIKeyCursor) (akp *console.APIKeyPage, err error) {
|
||||||
func (keys *apikeys) GetByProjectID(ctx context.Context, projectID uuid.UUID) (_ []console.APIKeyInfo, err error) {
|
|
||||||
defer mon.Task()(&ctx)(&err)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiKeys []console.APIKeyInfo
|
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 {
|
err = rows.Scan(&uuidScan{&ak.ID}, &uuidScan{&ak.ProjectID}, &ak.Name, &partnerIDBytes, &ak.CreatedAt)
|
||||||
info, err := fromDBXAPIKey(ctx, key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
parseErr.Add(err)
|
return nil, err
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiKeys, nil
|
return page, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get implements satellite.APIKeys
|
// Get implements satellite.APIKeys
|
||||||
func (keys *apikeys) Get(ctx context.Context, id uuid.UUID) (_ *console.APIKeyInfo, err error) {
|
func (keys *apikeys) Get(ctx context.Context, id uuid.UUID) (_ *console.APIKeyInfo, err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -60,7 +143,7 @@ func (keys *apikeys) Get(ctx context.Context, id uuid.UUID) (_ *console.APIKeyIn
|
|||||||
// GetByHead implements satellite.APIKeys
|
// GetByHead implements satellite.APIKeys
|
||||||
func (keys *apikeys) GetByHead(ctx context.Context, head []byte) (_ *console.APIKeyInfo, err error) {
|
func (keys *apikeys) GetByHead(ctx context.Context, head []byte) (_ *console.APIKeyInfo, err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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[:])
|
optional.PartnerId = dbx.ApiKey_PartnerId(info.PartnerID[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
dbKey, err := keys.db.Create_ApiKey(
|
dbKey, err := keys.methods.Create_ApiKey(
|
||||||
ctx,
|
ctx,
|
||||||
dbx.ApiKey_Id(id[:]),
|
dbx.ApiKey_Id(id[:]),
|
||||||
dbx.ApiKey_ProjectId(info.ProjectID[:]),
|
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
|
// Update implements satellite.APIKeys
|
||||||
func (keys *apikeys) Update(ctx context.Context, key console.APIKeyInfo) (err error) {
|
func (keys *apikeys) Update(ctx context.Context, key console.APIKeyInfo) (err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
_, err = keys.db.Update_ApiKey_By_Id(
|
_, err = keys.methods.Update_ApiKey_By_Id(
|
||||||
ctx,
|
ctx,
|
||||||
dbx.ApiKey_Id(key.ID[:]),
|
dbx.ApiKey_Id(key.ID[:]),
|
||||||
dbx.ApiKey_Update_Fields{
|
dbx.ApiKey_Update_Fields{
|
||||||
@ -115,7 +198,7 @@ func (keys *apikeys) Update(ctx context.Context, key console.APIKeyInfo) (err er
|
|||||||
// Delete implements satellite.APIKeys
|
// Delete implements satellite.APIKeys
|
||||||
func (keys *apikeys) Delete(ctx context.Context, id uuid.UUID) (err error) {
|
func (keys *apikeys) Delete(ctx context.Context, id uuid.UUID) (err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,3 +232,12 @@ func fromDBXAPIKey(ctx context.Context, key *dbx.ApiKey) (_ *console.APIKeyInfo,
|
|||||||
|
|
||||||
return result, nil
|
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
|
// APIKeys is a getter for APIKeys repository
|
||||||
func (db *ConsoleDB) APIKeys() console.APIKeys {
|
func (db *ConsoleDB) APIKeys() console.APIKeys {
|
||||||
return &apikeys{db.methods}
|
return &apikeys{db.methods, db.db}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketUsage is a getter for accounting.BucketUsage repository
|
// 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)
|
return m.db.GetByHead(ctx, head)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByProjectID retrieves list of APIKeys for given projectID
|
// GetPagedByProjectID is a method for querying API keys from the database by projectID and cursor
|
||||||
func (m *lockedAPIKeys) GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]console.APIKeyInfo, error) {
|
func (m *lockedAPIKeys) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.APIKeyCursor) (akp *console.APIKeyPage, err error) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
return m.db.GetByProjectID(ctx, projectID)
|
return m.db.GetPagedByProjectID(ctx, projectID, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates APIKeyInfo in store
|
// 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 {
|
if pmo == 2 {
|
||||||
return "DESC"
|
return "DESC"
|
||||||
}
|
}
|
||||||
|
@ -65,3 +65,23 @@ func (nodes postgresNodeIDList) Value() (driver.Value, error) {
|
|||||||
|
|
||||||
return out, nil
|
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