Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom applicaton support (work in progress) #52

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions commercetools/client_graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"io/ioutil"
"net/http"
"reflect"
"time"

mapstructure "github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
Expand Down Expand Up @@ -92,8 +94,42 @@ func (gql *GraphQLQuery) processResponse(resp *http.Response, dest interface{})
return output.Errors[0]
}

if output.Data != nil {
mapstructure.Decode(output.Data, &dest)
if output.Data == nil {
return nil
}

decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Metadata: nil,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
ToTimeHookFunc()),
Result: &dest,
})

err = decoder.Decode(output.Data)
if err != nil {
return err
}
return nil
}

func ToTimeHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}

switch f.Kind() {
case reflect.String:
return time.Parse(time.RFC3339, data.(string))
case reflect.Float64:
return time.Unix(0, int64(data.(float64))*int64(time.Millisecond)), nil
case reflect.Int64:
return time.Unix(0, data.(int64)*int64(time.Millisecond)), nil
default:
return data, nil
}
}
}
270 changes: 270 additions & 0 deletions commercetools/service_custom_applications.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
package commercetools

import (
"context"
"sort"
"time"
)

// CustomApplicationLabelLocale describes the structure of a localized label.
type CustomApplicationLabelLocale struct {
Locale string `json:"locale"`
Value string `json:"value"`
}

type CustomApplicationNavbarMenu struct {
Key string `json:"key"`
URIPath string `json:"uriPath"`
Icon string `json:"icon"`
LabelAllLocales []CustomApplicationLabelLocale `json:"labelAllLocales,omitempty"`
Permissions []string `json:"permissions"`
Submenu []CustomApplicationNavbarSubmenu `json:"submenu"`
}

type CustomApplicationNavbarSubmenu struct {
Key string `json:"key"`
URIPath string `json:"uriPath"`
LabelAllLocales []CustomApplicationLabelLocale `json:"labelAllLocales"`
Permissions []string `json:"permissions"`
}

type CustomApplicationDraft struct {
Name string `json:"name"`
Description string `json:"description"`
URL string `json:"url"`
NavbarMenu CustomApplicationNavbarMenu `json:"navbarMenu"`
OAuthScopes []string `json:"oAuthScopes,omitempty"`
}

// CustomApplication describes the structure of a Custom Application stored object.
type CustomApplication struct {
ID string `json:"id"`
CreatedAt time.Time `json:"createdAt,string"`
UpdatedAt time.Time `json:"updatedAt"`
IsActive bool `json:"isActive"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
URL string `json:"url"`
NavbarMenu CustomApplicationNavbarMenu `json:"navbarMenu"`
}

type CustomApplicationPagedQueryResponse struct {
Total int `json:"total,omitempty"`
Results []CustomApplication `json:"results"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Count int `json:"count"`
}

// ProjectExtension describes the structure of a project extension stored object.
type ProjectExtension struct {
ID string `json:"id"`
Applications []CustomApplication `json:"applications"`
}

// GraphQLResponseProjectExtension describes the structure of the query result for fetching a Custom Application.
type graphQLResponseProjectExtension struct {
ProjectExtension *ProjectExtension `json:"projectExtension"`
}

// GraphQLResponseProjectExtensionCreation describes the structure of the query result for creating a Custom Application.
type graphQLResponseProjectExtensionCreation struct {
CreateProjectExtensionApplication *ProjectExtension `json:"createProjectExtensionApplication"`
}

// GraphQLResponseProjectExtensionUpdate describes the structure of the query result for updating a Custom Application.
type graphQLResponseProjectExtensionUpdate struct {
UpdateProjectExtensionApplication *ProjectExtension `json:"updateProjectExtensionApplication"`
ActivateProjectExtensionApplication *ProjectExtension `json:"activateProjectExtensionApplication"`
DeactivateProjectExtensionApplication *ProjectExtension `json:"deactivateProjectExtensionApplication"`
}

// GraphQLResponseProjectExtensionDeletion describes the structure of the query result for deleting a Custom Application.
type graphQLResponseProjectExtensionDeletion struct {
DeleteProjectExtensionApplication *ProjectExtension `json:"deleteProjectExtensionApplication"`
}

func (client *Client) CustomApplicationCreate(ctx context.Context, draft *CustomApplicationDraft) (*CustomApplication, error) {
var result graphQLResponseProjectExtensionCreation

query := client.NewGraphQLQuery(`
mutation CreateCustomApplicationMutation($draft: ApplicationExtensionDataInput!) {
createProjectExtensionApplication(data: $draft) {
id
applications {
id
name
url
createdAt
}
}
}`)

query.Bind("draft", draft)
err := query.Execute(&result, query.ForMerchantCenter())
if err != nil {
return nil, err
}

apps := result.CreateProjectExtensionApplication.Applications

// Sort the apps descending on the created timestamp so we can easily find
// the last created item
sort.Slice(apps, func(i, j int) bool {
return apps[i].CreatedAt.After(apps[j].CreatedAt)
})

for _, app := range apps {
if app.Name == draft.Name && app.URL == draft.URL {
return client.CustomApplicationGetWithID(ctx, app.ID)
}
}

return nil, nil
}

func (client *Client) CustomApplicationGetWithID(ctx context.Context, ID string) (*CustomApplication, error) {
var result graphQLResponseProjectExtension

query := client.NewGraphQLQuery(`
query FetchCustomApplicationById($applicationId: ID!) {
projectExtension {
id
applications(where: { id: $applicationId }) {
id
createdAt
updatedAt
isActive
name
description
url
navbarMenu {
uriPath
icon
labelAllLocales {
locale
value
}
permissions
submenu {
uriPath
labelAllLocales {
locale
value
}
permissions
}
}
}
}
}
`)

query.Bind("applicationId", ID)
err := query.Execute(&result, query.ForMerchantCenter())
if err != nil {
return nil, err
}
if len(result.ProjectExtension.Applications) == 1 {
return &result.ProjectExtension.Applications[0], nil
}
return nil, nil
}

func (client *Client) CustomApplicationQuery(ctx context.Context, input *QueryInput) (*CustomApplicationPagedQueryResponse, error) {
var result graphQLResponseProjectExtension

query := client.NewGraphQLQuery(`
query FetchCustomApplicationById {
projectExtension {
id
applications {
id
createdAt
updatedAt
isActive
name
description
url
navbarMenu {
uriPath
icon
labelAllLocales {
locale
value
}
permissions
submenu {
uriPath
labelAllLocales {
locale
value
}
permissions
}
}
}
}
}
`)

err := query.Execute(&result, query.ForMerchantCenter())
if err != nil {
return nil, err
}

response := CustomApplicationPagedQueryResponse{
Results: result.ProjectExtension.Applications,
Total: len(result.ProjectExtension.Applications),
Count: len(result.ProjectExtension.Applications),
Offset: 0,
Limit: 0,
}
return &response, nil
}

func (client *Client) CustomApplicationDeleteWithID(ctx context.Context, ID string) error {
var result graphQLResponseProjectExtensionDeletion

query := client.NewGraphQLQuery(`
mutation DeleteCustomApplication($applicationId: ID!) {
deleteProjectExtensionApplication(applicationId: $applicationId) {
id
}
}
`)

query.Bind("applicationId", ID)

err := query.Execute(&result, query.ForMerchantCenter())
return err
}

func (client *Client) CustomApplicationUpdateWithID(ctx context.Context, ID string, draft *CustomApplicationDraft) (*CustomApplication, error) {
var result graphQLResponseProjectExtensionUpdate

query := client.NewGraphQLQuery(`
mutation UpdateCustomApplication(
$applicationId: ID!
$draft: ApplicationExtensionDataInput!
) {
updateProjectExtensionApplication(
applicationId: $applicationId
data: $draft
) {
id
applications(where: { id: $applicationId }) {
id
}
}
}
`)
query.Bind("applicationId", ID)
query.Bind("draft", draft)

err := query.Execute(&result, query.ForMerchantCenter())
if err != nil {
return nil, err
}
return client.CustomApplicationGetWithID(ctx, ID)
}
51 changes: 51 additions & 0 deletions commercetools/service_custom_applications_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package commercetools_test

import (
"context"
"testing"

"github.com/labd/commercetools-go-sdk/testutil"
"github.com/stretchr/testify/assert"
)

func TestCustomApplicationGetWithID(t *testing.T) {
jsonData := `{
"data":{
"projectExtension": {
"id":"ckcmb5qvfzper07140kmkkvuc",
"applications":[
{
"id":"ckcnc6hydu2ou08131y504crj",
"createdAt":"2020-07-15T12:24:10.593Z",
"updatedAt":"2020-07-15T12:24:10.593Z",
"isActive":false,
"name":"noooooo-test",
"description":"test terraform---xxxx-",
"url":"http://labdigital.nl",
"navbarMenu":{
"uriPath":"sort-test",
"icon":"",
"labelAllLocales":[
{
"locale":"NL",
"value":"Michael"
}
],
"permissions":["ViewCategories"],
"submenu":[]
}
}
]
}
}
}`

output := testutil.RequestData{}
client, server := testutil.MockClient(t, jsonData, &output, nil)
defer server.Close()

_, err := client.CustomApplicationGetWithID(context.TODO(), "1234")
assert.Nil(t, err)

assert.Equal(t, "/graphql", output.URL.Path)
}
Loading