appserver

package module
v0.0.0-...-4f143e1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2022 License: MIT Imports: 18 Imported by: 0

README

Shopware


Foundation for Shopware apps based on Go

This library provides helper functions to write Shopware apps with a Go backend server. The library handles authorization as well as webhooks and actions.

Menu

Features

  • Automated handshake for easy installation in Shopware
  • Easy configuration with no additional router set-up needed
  • Generic endpoints for admin action buttons and webhooks
  • Written in Go, a language with high memory safety guarantees

Quick start

Installing

To start using the app server, install Go and run go get:

$ go get github.com/shopwareLabs/GoAppserver

Storage engines

The app server comes with two storage engines included, in-memory and bbolt.

in-memory (default)

This storage resets on every restart of the server and should only be used for quick-start purposes. All information is lost when the process is killed. This storage is used by default.

bbolt

bbolt is a key/value store based on files to provide a simple, fast, and reliable database for projects that don't require a full database server such as Postgres or MySQL

store, err := appserver.NewBBoltStore("./mydb.db")
if err != nil {
    log.Fatal(err)
}
defer store.Close()

srv := appserver.NewServer(
    "AppName",
    "AppSecret",
    "https://appserver.com/setup/register-confirm",
    appserver.WithCredentialStore(store),
)

Events

First, register a POST route in your web server and use HandleWebhook inside the handler:

mux.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
    if err := srv.HandleWebhook(r); err != nil {
      // handle errors
   }

    // webhook handled successfully
})

To listen on an event, add the event to your manifest.xml file in your app and point it to your webhook endpoint. Verifying the signature is done automatically for you.

manifest.xml

<webhooks>
    <webhook name="orderCompleted" url="https://appserver.com/webhook" event="checkout.order.placed"/>
</webhooks>

You can then register an event listener to the app server:

srv.Event("checkout.order.placed", func(webhook appserver.WebhookRequest, api *appserver.APIClient) error {
    // do something on this event

    return nil
})

Action buttons

First, register a POST route in your web server and use HandleAction inside the handler:

mux.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
    if err := srv.HandleAction(r); err != nil {
      // handle errors
   }

    // action handled successfully
})

To listen on a click on an action button, add the action button to your manifest.xml file in your app and point it to /action. Verifying the signature is done automatically for you.

manifest.xml

<admin>
    <action-button action="doSomething" entity="product" view="detail" url="https://appserver.com/action">
        <label>do something</label>
    </action-button>
</admin>

You can then register an admin action listener to the app server:

srv.Action("product", "doSomething", func(action appserver.ActionRequest, api *appserver.APIClient) error {
    // do something when someone clicks the action button

    return nil
})

Full example

Here is a full example on an app server, that uses the standard http package and listens for events and action buttons.

package main

import (
   "encoding/json"
   "log"
   "net/http"
   
   appserver "github.com/shopwareLabs/GoAppserver"
)

func main() {
   srv := appserver.NewServer(
      "AppName",
      "AppSecret",
      "https://appserver.com/setup/register-confirm",
   )

   // event listener
   srv.Event("checkout.order.placed", func(webhook appserver.WebhookRequest, api *appserver.APIClient) error {
      // do something on this event
      
      return nil
   })

   // action buttons
   srv.Action("product", "doSomething", func(action appserver.ActionRequest, api *appserver.APIClient) error {
      // do something when someone clicks the action button
      
      return nil
   })
   
   // register routes and start server
   mux := http.NewServeMux()
   mux.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
      if err := srv.HandleWebhook(r); err != nil {
         http.Error(w, err.Error(), http.StatusBadRequest)
         return
      }

      w.WriteHeader(200)
   })
   mux.HandleFunc("/action", func(w http.ResponseWriter, r *http.Request) {
      if err := srv.HandleAction(r); err != nil {
         http.Error(w, err.Error(), http.StatusBadRequest)
         return
      }

      w.WriteHeader(200)
   })
   mux.HandleFunc("/setup/register", func(w http.ResponseWriter, r *http.Request) {
      reg, err := srv.HandleRegistration(r)
      if err != nil {
         http.Error(w, err.Error(), http.StatusBadRequest)
         return
      }

      regJSON, err := json.Marshal(reg)
      if err != nil {
         http.Error(w, err.Error(), http.StatusInternalServerError)
         return
      }

      w.WriteHeader(200)
      w.Write(regJSON)
   })

   mux.HandleFunc("/setup/register-confirm", func(w http.ResponseWriter, r *http.Request) {
      if err := srv.HandleConfirm(r); err != nil {
         http.Error(w, err.Error(), http.StatusBadRequest)
         return
      }

      w.WriteHeader(200)
   })

   log.Println("Listening on port 10100")
   log.Fatal(http.ListenAndServe(":10100", mux))
}

About

GoAppserver is a project of shopware AG.

Documentation

Index

Constants

View Source
const (
	TotalCountModeDefault  = 0
	TotalCountModeExact    = 1
	TotalCountModeNextPage = 2

	SearchFilterTypeEquals    = "equals"
	SearchFilterTypeEqualsAny = "equalsAny"

	SearchSortDirectionAscending  = "ASC"
	SearchSortDirectionDescending = "DESC"
)
View Source
const (
	HeaderAppSignature     = "shopware-app-signature"
	HeaderPayloadSignature = "shopware-shop-signature"
)

Variables

View Source
var (
	ErrActionMissingAction = errors.New("missing action or entity")
)
View Source
var (
	ErrCredentialsNotFound = errors.New("credentials for shop not found")
)
View Source
var (
	ErrWebhookMissingEvent = errors.New("missing event")
)

Functions

This section is empty.

Types

type APIClient

type APIClient struct {
	// contains filtered or unexported fields
}

func (*APIClient) GetAppConfig

func (c *APIClient) GetAppConfig() (map[string]interface{}, error)

func (*APIClient) Request

func (c *APIClient) Request(method string, path string, payload interface{}) (*http.Response, error)

type ActionHandler

type ActionHandler func(action ActionRequest, api *APIClient) error

type ActionRequest

type ActionRequest struct {
	*AppRequest

	Data struct {
		IDs    []string `json:"ids"`
		Entity string   `json:"entity"`
		Action string   `json:"action"`
	} `json:"data"`

	Meta struct {
		Timestamp   int64  `json:"timestamp"`
		ReferenceID string `json:"reference"`
		LanguageID  string `json:"language"`
	} `json:"meta"`
}

type AppRequest

type AppRequest struct {
	Source Source `json:"source"`
}

type BBoltStore

type BBoltStore struct {
	// contains filtered or unexported fields
}

func NewBBoltStore

func NewBBoltStore(path string) (*BBoltStore, error)

func (*BBoltStore) Close

func (s *BBoltStore) Close()

func (*BBoltStore) Delete

func (s *BBoltStore) Delete(shopID string) error

func (*BBoltStore) Get

func (s *BBoltStore) Get(shopID string) (*Credentials, error)

func (*BBoltStore) Store

func (s *BBoltStore) Store(credentials *Credentials) error

type CredentialStore

type CredentialStore interface {
	Store(credentials *Credentials) error
	Get(shopID string) (*Credentials, error)
	Delete(shopID string) error
}

func NewMemoryCredentialStore

func NewMemoryCredentialStore() CredentialStore

type Credentials

type Credentials struct {
	APIKey     string `json:"apiKey"`
	SecretKey  string `json:"secretKey"`
	Timestamp  string `json:"timestamp" query:"timestamp"`
	ShopURL    string `json:"shopUrl" query:"shop-url"`
	ShopID     string `json:"shopId" query:"shop-id"`
	ShopSecret string `json:"shopSecret"`
}

type ErrActionHandlerNotFound

type ErrActionHandlerNotFound struct {
	// contains filtered or unexported fields
}

func (ErrActionHandlerNotFound) Error

func (e ErrActionHandlerNotFound) Error() string

type ErrWebhookHandlerNotFound

type ErrWebhookHandlerNotFound struct {
	// contains filtered or unexported fields
}

func (ErrWebhookHandlerNotFound) Error

type MemoryCredentialStore

type MemoryCredentialStore struct {
	// contains filtered or unexported fields
}

func (*MemoryCredentialStore) Delete

func (m *MemoryCredentialStore) Delete(shopID string) error

func (*MemoryCredentialStore) Get

func (m *MemoryCredentialStore) Get(shopID string) (*Credentials, error)

func (*MemoryCredentialStore) Store

func (m *MemoryCredentialStore) Store(credentials *Credentials) error

type RegistrationResponse

type RegistrationResponse struct {
	Proof           string `json:"proof"`
	Secret          string `json:"secret"`
	ConfirmationURL string `json:"confirmation_url"`
}
type Search struct {
	Includes       map[string][]string `json:"includes,omitempty"`
	Page           int64               `json:"page,omitempty"`
	Limit          int64               `json:"limit,omitempty"`
	IDs            []string            `json:"ids,omitempty"`
	Filter         []SearchFilter      `json:"filter,omitempty"`
	PostFilter     []SearchFilter      `json:"postFilter,omitempty"`
	Sort           []SearchSort        `json:"sort,omitempty"`
	Term           string              `json:"term,omitempty"`
	TotalCountMode int                 `json:"totalCountMode,omitempty"`
}

type SearchFilter

type SearchFilter struct {
	Type  string      `json:"type"`
	Field string      `json:"field"`
	Value interface{} `json:"value"`
}

type SearchResponse

type SearchResponse struct {
	Total        int64       `json:"total"`
	Data         interface{} `json:"data"`
	Aggregations interface{} `json:"aggregations"`
}

type SearchSort

type SearchSort struct {
	Direction      string `json:"order"`
	Field          string `json:"field"`
	NaturalSorting bool   `json:"naturalSorting"`
}

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(appName string, appSecret string, confirmationURL string, opts ...ServerOpt) *Server

func (*Server) Action

func (srv *Server) Action(entity string, action string, handler ActionHandler)

func (*Server) Event

func (srv *Server) Event(event string, handler WebhookHandler)

func (*Server) HandleAction

func (srv *Server) HandleAction(req *http.Request) error

func (Server) HandleConfirm

func (srv Server) HandleConfirm(req *http.Request) error

func (Server) HandleRegistration

func (srv Server) HandleRegistration(req *http.Request) (RegistrationResponse, error)

func (Server) HandleWebhook

func (srv Server) HandleWebhook(req *http.Request) error

type ServerOpt

type ServerOpt func(s *Server)

func WithCredentialStore

func WithCredentialStore(store CredentialStore) ServerOpt

type SignatureVerificationError

type SignatureVerificationError struct {
	// contains filtered or unexported fields
}

func (SignatureVerificationError) Error

func (SignatureVerificationError) Unwrap

func (e SignatureVerificationError) Unwrap() error

type Source

type Source struct {
	ShopID     string `json:"shopId"`
	ShopURL    string `json:"url"`
	AppVersion string `json:"appVersion"`
}

type WebhookHandler

type WebhookHandler func(webhook WebhookRequest, api *APIClient) error

type WebhookRequest

type WebhookRequest struct {
	*AppRequest

	Data struct {
		Payload []map[string]interface{} `json:"payload"`
		Event   string                   `json:"event"`
	} `json:"data"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL