Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into feature/WEOS-1298
Browse files Browse the repository at this point in the history
  • Loading branch information
atoniaw committed Feb 14, 2022
2 parents 32b8767 + 9a1a000 commit 1c55283
Show file tree
Hide file tree
Showing 18 changed files with 412 additions and 133 deletions.
21 changes: 15 additions & 6 deletions context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const EVENT_STORE ContextKey = "_event_store"
const SCHEMA_BUILDERS ContextKey = "_schema_builders"
const FILTERS ContextKey = "_filters"
const SORTS ContextKey = "_sorts"
const PAYLOAD ContextKey = "_payload"
const SEQUENCE_NO string = "sequence_no"

//Path initializers are run per path and can be used to configure routes that are not defined in the open api spec
Expand All @@ -54,54 +55,62 @@ func GetContentType(ctx context.Context) *ContentType {
return nil
}

//Get account info from context
//GetAccount info from context
func GetAccount(ctx context.Context) string {
if value, ok := ctx.Value(ACCOUNT_ID).(string); ok {
return value
}
return ""
}

//Get user info from context
//GetUser info from context
func GetUser(ctx context.Context) string {
if value, ok := ctx.Value(USER_ID).(string); ok {
return value
}
return ""
}

//Get log level from context
//GetLogLevel from context
func GetLogLevel(ctx context.Context) string {
if value, ok := ctx.Value(LOG_LEVEL).(string); ok {
return value
}
return ""
}

//Get request id from context
//GetRequestID from context
func GetRequestID(ctx context.Context) string {
if value, ok := ctx.Value(REQUEST_ID).(string); ok {
return value
}
return ""
}

//Get entity id if it's in the context
//GetEntityID if it's in the context
func GetEntityID(ctx context.Context) string {
if value, ok := ctx.Value(ENTITY_ID).(string); ok {
return value
}
return ""
}

//Get error
//GetError return error from context
func GetError(ctx context.Context) error {
if value, ok := ctx.Value(ctx).(error); ok {
return value
}
return nil
}

//GetPayload returns payload from context
func GetPayload(ctx context.Context) []byte {
if value, ok := ctx.Value(PAYLOAD).([]byte); ok {
return value
}
return []byte("")
}

//Deprecated: Context Use the Go context in the echo request instead
type Context struct {
echo.Context
Expand Down
35 changes: 21 additions & 14 deletions controllers/rest/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ type RESTAPI struct {
Log model.Log
DB *sql.DB
Client *http.Client
projection *projections.GORMProjection
config *APIConfig
projection *projections.GORMDB
Config *APIConfig
e *echo.Echo
PathConfigs map[string]*PathConfig
Schemas map[string]ds.Builder
Expand Down Expand Up @@ -70,11 +70,14 @@ type APIInterface interface {
SetEchoInstance(e *echo.Echo)
}

//Deprecated: 02/13/2022 made Config public
func (p *RESTAPI) AddConfig(config *APIConfig) error {
p.config = config
p.Config = config
return nil
}

//Deprecated: 02/13/2022 This should not but actively used
//AddPathConfig add path Config
func (p *RESTAPI) AddPathConfig(path string, config *PathConfig) error {
if p.PathConfigs == nil {
p.PathConfigs = make(map[string]*PathConfig)
Expand Down Expand Up @@ -284,23 +287,27 @@ func (p *RESTAPI) Initialize(ctxt context.Context) error {
//these are the dynamic struct builders for the schemas in the OpenAPI
var schemas map[string]ds.Builder

if p.config != nil && p.config.Database != nil {
if p.Config != nil && p.Config.Database != nil {
//setup default projection
var gormDB *gorm.DB
var err error

p.DB, gormDB, err = p.SQLConnectionFromConfig(p.config.Database)
p.DB, gormDB, err = p.SQLConnectionFromConfig(p.Config.Database)
if err != nil {
return err
}

//setup default projection if gormDB is configured
if gormDB != nil {
defaultProjection, err := projections.NewProjection(ctxt, gormDB, p.EchoInstance().Logger)
if err != nil {
return err
//check if default projection was already set
defaultProjection, _ := p.GetProjection("Default")
if defaultProjection == nil {
defaultProjection, err = projections.NewProjection(ctxt, gormDB, p.EchoInstance().Logger)
if err != nil {
return err
}
p.RegisterProjection("Default", defaultProjection)
}
p.RegisterProjection("Default", defaultProjection)
//get the database schema
schemas = CreateSchema(ctxt, p.EchoInstance(), p.Swagger)
p.Schemas = schemas
Expand All @@ -316,7 +323,7 @@ func (p *RESTAPI) Initialize(ctxt context.Context) error {
//if there is a projection then add the event handler as a subscriber to the event store
if defaultProjection, err := p.GetProjection("Default"); err == nil {
//only setup the gorm event repository if it's a gorm projection
if gormProjection, ok := defaultProjection.(*projections.GORMProjection); ok {
if gormProjection, ok := defaultProjection.(model.GormProjection); ok {
defaultEventStore, err := model.NewBasicEventRepository(gormProjection.DB(), p.EchoInstance().Logger, false, "", "")
if err != nil {
return err
Expand Down Expand Up @@ -346,9 +353,9 @@ func (p *RESTAPI) Initialize(ctxt context.Context) error {
//setup middleware - https://echo.labstack.com/middleware/

//setup global pre middleware
if p.config != nil && p.config.Rest != nil {
if p.Config != nil && p.Config.Rest != nil {
var preMiddlewares []echo.MiddlewareFunc
for _, middlewareName := range p.config.Rest.PreMiddleware {
for _, middlewareName := range p.Config.Rest.PreMiddleware {
t := reflect.ValueOf(middlewareName)
m := t.MethodByName(middlewareName)
if !m.IsValid() {
Expand All @@ -363,7 +370,7 @@ func (p *RESTAPI) Initialize(ctxt context.Context) error {
//setup global middleware
var middlewares []echo.MiddlewareFunc
//prepend Context middleware
//for _, middlewareName := range p.config.Rest.Middleware {
//for _, middlewareName := range p.Config.Rest.Middleware {
// tmiddleware, err := p.GetMiddleware(middlewareName)
// if err != nil {
// p.e.Logger.Fatalf("invalid middleware set '%s'. Must be of type rest.Middleware", middlewareName)
Expand Down Expand Up @@ -423,7 +430,7 @@ func (p *RESTAPI) Initialize(ctxt context.Context) error {
return err
}

//SQLConnectionFromConfig get db connection based on a config
//SQLConnectionFromConfig get db connection based on a Config
func (p *RESTAPI) SQLConnectionFromConfig(config *model.DBConfig) (*sql.DB, *gorm.DB, error) {
var connStr string
var err error
Expand Down
43 changes: 40 additions & 3 deletions controllers/rest/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ components:
t.Fatalf("unexpected error getting default projection '%s'", err)
}
var ok bool
var defaultGormProject *projections.GORMProjection
if defaultGormProject, ok = defaultProjection.(*projections.GORMProjection); !ok {
var defaultGormProject *projections.GORMDB
if defaultGormProject, ok = defaultProjection.(*projections.GORMDB); !ok {
t.Fatalf("unexpected error getting default projection '%s'", err)
}

Expand Down Expand Up @@ -359,10 +359,47 @@ func TestRESTAPI_RegisterEventDispatcher(t *testing.T) {

func TestRESTAPI_RegisterProjection(t *testing.T) {
tapi := &api.RESTAPI{}
tapi.RegisterProjection("test", &projections.GORMProjection{})
tapi.RegisterProjection("test", &projections.GORMDB{})
//get dispatcher
_, err := tapi.GetProjection("test")
if err != nil {
t.Fatalf("unexpected error getting projection '%s'", err)
}
}

func TestRESTAPI_DefaultProjectionRegisteredBefore(t *testing.T) {
os.Remove("test.db")
tapi, err := api.New("./fixtures/blog.yaml")
if err != nil {
t.Fatalf("un expected error loading spec '%s'", err)
}
_, gormDB, err := tapi.SQLConnectionFromConfig(tapi.Config.Database)
gormProjection, err := projections.NewProjection(context.TODO(), gormDB, tapi.EchoInstance().Logger)
if err != nil {
t.Fatalf("error setting up gorm projection")
}
//setup mock projection as anonymous struct
mockProjection := &struct {
*projections.GORMDB
Test bool
}{
gormProjection,
true,
}
tapi.RegisterProjection("Default", mockProjection)
err = tapi.Initialize(context.TODO())
if err != nil {
t.Fatalf("unexpected error '%s'", err)
}
//get the projection from the api
defaultProjection, err := tapi.GetProjection("Default")
if err != nil {
t.Fatalf("unexpected error getting default projection '%s'", err)
}
if _, ok := defaultProjection.(*struct {
*projections.GORMDB
Test bool
}); !ok {
t.Errorf("expected the projection to be the one that was set")
}
}
64 changes: 5 additions & 59 deletions controllers/rest/controller_standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"github.com/wepala/weos/projections"
"io/ioutil"
"net/http"
"strconv"
"strings"
Expand All @@ -31,35 +30,7 @@ func CreateMiddleware(api *RESTAPI, projection projections.Projection, commandDi
api.EchoInstance().Logger.Errorf("no entity factory detected for '%s'", ctxt.Request().RequestURI)
return err
}
var payload []byte
var err error

ct := ctxt.Request().Header.Get("Content-Type")

switch ct {
case "application/json":
payload, err = ioutil.ReadAll(ctxt.Request().Body)
if err != nil {
return err
}
case "application/x-www-form-urlencoded":
payload, err = ConvertFormToJson(ctxt.Request(), "application/x-www-form-urlencoded")
if err != nil {
return err
}
default:
if strings.Contains(ct, "multipart/form-data") {
payload, err = ConvertFormToJson(ctxt.Request(), "multipart/form-data")
if err != nil {
return err
}
} else if ct == "" {
return NewControllerError("expected a content-type to be explicitly defined", err, http.StatusBadRequest)
} else {
return NewControllerError("the content-type provided is not supported", err, http.StatusBadRequest)
}
}

payload := weoscontext.GetPayload(newContext)
//for inserting weos_id during testing
payMap := map[string]interface{}{}
var weosID string
Expand All @@ -74,7 +45,7 @@ func CreateMiddleware(api *RESTAPI, projection projections.Projection, commandDi
weosID = ksuid.New().String()
}

err = commandDispatcher.Dispatch(newContext, model.Create(newContext, payload, entityFactory.Name(), weosID), eventSource, projection, api.EchoInstance().Logger)
err := commandDispatcher.Dispatch(newContext, model.Create(newContext, payload, entityFactory.Name(), weosID), eventSource, projection, api.EchoInstance().Logger)
if err != nil {
if errr, ok := err.(*model.DomainError); ok {
return NewControllerError(errr.Error(), err, http.StatusBadRequest)
Expand Down Expand Up @@ -128,8 +99,7 @@ func CreateBatchMiddleware(api *RESTAPI, projection projections.Projection, comm
api.EchoInstance().Logger.Errorf("no entity factory detected for '%s'", ctxt.Request().RequestURI)
return err
}
//reads the request body
payload, _ := ioutil.ReadAll(ctxt.Request().Body)
payload := weoscontext.GetPayload(newContext)

err := commandDispatcher.Dispatch(newContext, model.CreateBatch(newContext, payload, entityFactory.Name()), nil, nil, api.EchoInstance().Logger)
if err != nil {
Expand Down Expand Up @@ -166,34 +136,10 @@ func UpdateMiddleware(api *RESTAPI, projection projections.Projection, commandDi
}
var weosID string
var sequenceNo string
var payload []byte
var err error

ct := ctxt.Request().Header.Get("Content-Type")
var err error

switch ct {
case "application/json":
payload, err = ioutil.ReadAll(ctxt.Request().Body)
if err != nil {
return err
}
case "application/x-www-form-urlencoded":
payload, err = ConvertFormToJson(ctxt.Request(), "application/x-www-form-urlencoded")
if err != nil {
return err
}
default:
if strings.Contains(ct, "multipart/form-data") {
payload, err = ConvertFormToJson(ctxt.Request(), "multipart/form-data")
if err != nil {
return err
}
} else if ct == "" {
return NewControllerError("expected a content-type to be explicitly defined", err, http.StatusBadRequest)
} else {
return NewControllerError("the content-type provided is not supported", err, http.StatusBadRequest)
}
}
payload := weoscontext.GetPayload(newContext)
//getting etag from context
etagInterface := newContext.Value("If-Match")
if etagInterface != nil {
Expand Down
1 change: 1 addition & 0 deletions controllers/rest/controller_standard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ func TestStandardControllers_CreateBatch(t *testing.T) {
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/blogs", body)
req.Header.Set(weoscontext.HeaderXAccountID, accountID)
req.Header.Set("Content-Type", "application/json")
mw := rest.Context(restAPI, projection, dispatcher, eventRepository, entityFactory, path, path.Post)
createBatchMw := rest.CreateBatchMiddleware(restAPI, projection, dispatcher, eventRepository, entityFactory, path, path.Post)
e.POST("/blogs", controller, mw, createBatchMw)
Expand Down
13 changes: 4 additions & 9 deletions controllers/rest/fixtures/blog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ paths:
x-controller: HealthCheck
x-middleware:
- Recover
x-command-dispatcher: HealthCheck
x-event-dispatcher: HealthCheck
responses:
200:
description: Health Response
Expand Down Expand Up @@ -167,6 +165,9 @@ paths:
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/Blog"
multipart/form-data:
schema:
$ref: "#/components/schemas/Blog"
responses:
201:
description: Add Blog to Aggregator
Expand Down Expand Up @@ -344,13 +345,7 @@ paths:
name: If-Match
schema:
type: string
requestBody:
description: Blog info that is submitted
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/Blog"
x-schema: "Blog"
summary: Delete blog
operationId: Delete Blog
responses:
Expand Down
Loading

0 comments on commit 1c55283

Please sign in to comment.