Skip to content

Commit

Permalink
feat(api): add GQL API keys management API
Browse files Browse the repository at this point in the history
  • Loading branch information
ncarlier committed Mar 19, 2019
1 parent 979d0b5 commit 0b8d099
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 87 deletions.
1 change: 1 addition & 0 deletions pkg/db/api-key.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "github.com/ncarlier/reader/pkg/model"

// APIKeyRepository is the repository interface to manage API keys
type APIKeyRepository interface {
GetAPIKeyByID(id uint) (*model.APIKey, error)
GetAPIKeyByToken(token string) (*model.APIKey, error)
GetAPIKeyByUserIDAndAlias(userID uint, alias string) (*model.APIKey, error)
GetAPIKeysByUserID(userID uint) ([]model.APIKey, error)
Expand Down
144 changes: 57 additions & 87 deletions pkg/db/postgres/api-key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,67 @@ package postgres
import (
"database/sql"
"errors"
"fmt"

"github.com/ncarlier/reader/pkg/model"
)

func (pg *DB) createAPIKey(apiKey model.APIKey) (*model.APIKey, error) {
row := pg.db.QueryRow(`
INSERT INTO api_keys
(user_id, alias, token)
VALUES
($1, $2, $3)
RETURNING id, user_id, alias, token, created_at
`,
apiKey.UserID, apiKey.Alias, apiKey.Token,
)
const apiKeyColumns = `
id,
user_id,
alias,
token,
last_usage_at,
created_at,
updated_at
`

func scanAPIKeyRow(row *sql.Row) (*model.APIKey, error) {
result := model.APIKey{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Alias,
&result.Token,
&result.LastUsageAt,
&result.CreatedAt,
&result.UpdatedAt,
)
if err != nil {

if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &result, nil
}

func (pg *DB) createAPIKey(apiKey model.APIKey) (*model.APIKey, error) {
row := pg.db.QueryRow(fmt.Sprintf(`
INSERT INTO api_keys
(user_id, alias, token)
VALUES
($1, $2, $3)
RETURNING %s
`, apiKeyColumns),
apiKey.UserID, apiKey.Alias, apiKey.Token,
)
return scanAPIKeyRow(row)
}

func (pg *DB) updateAPIKey(apiKey model.APIKey) (*model.APIKey, error) {
row := pg.db.QueryRow(`
row := pg.db.QueryRow(fmt.Sprintf(`
UPDATE api_keys SET
alias=$3,
updated_at=NOW()
WHERE id=$1 AND user_id=$2
RETURNING id, user_id, alias, token, last_usage_at, created_at, updated_at
`,
RETURNING %s
`, apiKeyColumns),
apiKey.ID, apiKey.UserID, apiKey.Alias,
)

result := model.APIKey{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Alias,
&result.Token,
&result.LastUsageAt,
&result.CreatedAt,
&result.UpdatedAt,
)
if err != nil {
return nil, err
}
return &result, nil
return scanAPIKeyRow(row)
}

// CreateOrUpdateAPIKey creates or updates an apiKey into the DB
Expand All @@ -68,86 +74,50 @@ func (pg *DB) CreateOrUpdateAPIKey(apiKey model.APIKey) (*model.APIKey, error) {
return pg.createAPIKey(apiKey)
}

// GetAPIKeyByID get an apiKey from the DB
func (pg *DB) GetAPIKeyByID(id uint) (*model.APIKey, error) {
row := pg.db.QueryRow(fmt.Sprintf(`
SELECT %s
FROM api_keys
WHERE id = $1`, apiKeyColumns),
id,
)
return scanAPIKeyRow(row)
}

// GetAPIKeyByToken find an apiKey by token form the DB (last usage is updated!)
func (pg *DB) GetAPIKeyByToken(token string) (*model.APIKey, error) {
row := pg.db.QueryRow(`
row := pg.db.QueryRow(fmt.Sprintf(`
UPDATE api_keys SET
last_usage_at=NOW()
WHERE token=$1
RETURNING id, user_id, alias, token, last_usage_at, created_at, updated_at
`,
RETURNING %s
`, apiKeyColumns),
token,
)

result := model.APIKey{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Alias,
&result.Token,
&result.LastUsageAt,
&result.CreatedAt,
&result.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &result, nil
return scanAPIKeyRow(row)
}

// GetAPIKeyByUserIDAndAlias returns API key of an user by its alias
func (pg *DB) GetAPIKeyByUserIDAndAlias(userID uint, alias string) (*model.APIKey, error) {
row := pg.db.QueryRow(`
SELECT
id,
user_id,
alias,
token,
last_usage_at,
created_at,
updated_at
row := pg.db.QueryRow(fmt.Sprintf(`
SELECT %s
FROM api_keys
WHERE user_id = $1 AND alias = $2`,
WHERE user_id = $1 AND alias = $2`, apiKeyColumns),
userID, alias,
)

result := model.APIKey{}

err := row.Scan(
&result.ID,
&result.UserID,
&result.Alias,
&result.Token,
&result.LastUsageAt,
&result.CreatedAt,
&result.UpdatedAt,
)

if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &result, nil
return scanAPIKeyRow(row)
}

// GetAPIKeysByUserID returns api-keys of an user from DB
func (pg *DB) GetAPIKeysByUserID(userID uint) ([]model.APIKey, error) {
rows, err := pg.db.Query(`
SELECT
id,
user_id,
alias,
token,
last_usage_at,
created_at,
updated_at
rows, err := pg.db.Query(fmt.Sprintf(`
SELECT %s
FROM api_keys
WHERE user_id=$1
ORDER BY alias ASC`,
ORDER BY alias ASC`, apiKeyColumns),
userID,
)
if err != nil {
Expand Down
105 changes: 105 additions & 0 deletions pkg/schema/api-key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package schema

import (
"errors"

"github.com/ncarlier/reader/pkg/tooling"

"github.com/graphql-go/graphql"
"github.com/ncarlier/reader/pkg/service"
)

var apiKeyType = graphql.NewObject(
graphql.ObjectConfig{
Name: "APIKey",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.Int,
},
"alias": &graphql.Field{
Type: graphql.String,
},
"token": &graphql.Field{
Type: graphql.String,
},
"last_usage_at": &graphql.Field{
Type: graphql.DateTime,
},
"created_at": &graphql.Field{
Type: graphql.DateTime,
},
"updated_at": &graphql.Field{
Type: graphql.DateTime,
},
},
},
)

// QUERIES

var apiKeysQueryField = &graphql.Field{
Type: graphql.NewList(apiKeyType),
Resolve: apiKeysResolver,
}

func apiKeysResolver(p graphql.ResolveParams) (interface{}, error) {
apiKeys, err := service.Lookup().GetAPIKeys(p.Context)
if err != nil {
return nil, err
}
return apiKeys, nil
}

// MUTATIONS

var createOrUpdateAPIKeyMutationField = &graphql.Field{
Type: apiKeyType,
Description: "create or update an API key (use the ID parameter to update)",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.ID,
},
"alias": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: createOrUpdateAPIKeyResolver,
}

func createOrUpdateAPIKeyResolver(p graphql.ResolveParams) (interface{}, error) {
var id *uint
if val, ok := tooling.ConvGQLStringToUint(p.Args["id"]); ok {
id = &val
}
alias, _ := p.Args["alias"].(string)

apiKey, err := service.Lookup().CreateOrUpdateAPIKey(p.Context, id, alias)
if err != nil {
return nil, err
}
return apiKey, nil
}

var deleteAPIKeyMutationField = &graphql.Field{
Type: apiKeyType,
Description: "delete an API key",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.ID),
},
},
Resolve: deleteAPIKeyResolver,
}

func deleteAPIKeyResolver(p graphql.ResolveParams) (interface{}, error) {
id, ok := tooling.ConvGQLStringToUint(p.Args["id"])
if !ok {
return nil, errors.New("invalid API key ID")
}

apiKey, err := service.Lookup().DeleteAPIKey(p.Context, id)
if err != nil {
return nil, err
}
return apiKey, nil
}
3 changes: 3 additions & 0 deletions pkg/schema/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var rootQuery = graphql.NewObject(
Fields: graphql.Fields{
"me": meQueryField,
"categories": categoriesQueryField,
"apiKeys": apiKeysQueryField,
},
},
)
Expand All @@ -24,6 +25,8 @@ var rootMutation = graphql.NewObject(
Fields: graphql.Fields{
"createOrUpdateCategory": createOrUpdateCategoryMutationField,
"deleteCategory": deleteCategoryMutationField,
"createOrUpdateAPIKey": createOrUpdateAPIKeyMutationField,
"deleteAPIKey": deleteAPIKeyMutationField,
},
},
)
Expand Down
Loading

0 comments on commit 0b8d099

Please sign in to comment.