From 7ec3ddcf25d84ffb57e71d648e1fc863de0863c4 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Jul 2018 10:47:48 +0200 Subject: [PATCH 1/5] No need for .env, developer should copy it from .env.sample --- .env | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 59cffdba5..000000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -SAM_HTTP_ADDR=:8082 -SAM_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci From 43d11428fe0e02284a9bd35ba368c21afba5c764 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Jul 2018 10:52:01 +0200 Subject: [PATCH 2/5] Make CRM configurable through .env and part of realize config --- .env.sample | 5 ++++- .realize.yaml | 23 ++++++++++++++++++++++- cmd/crm/main.go | 27 ++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/.env.sample b/.env.sample index cc4191eaa..8b8fefe4b 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1,5 @@ SAM_HTTP_ADDR=:3000 -SAM_DB_DSN=crust:crust@tcp(db1:3306)/crust?collation=utf8mb4_general_ci +SAM_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci + +CRM_HTTP_ADDR=:3001 +CRM_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci diff --git a/.realize.yaml b/.realize.yaml index 383dfd42c..dedeb6f01 100755 --- a/.realize.yaml +++ b/.realize.yaml @@ -18,7 +18,7 @@ settings: server: status: false schema: -- name: crust +- name: sam path: cmd/sam commands: vet: @@ -39,3 +39,24 @@ schema: - .git - .realize - vendor +- name: crm + path: cmd/crm + commands: + vet: + status: true + test: + status: false + run: + status: true + args: + - api + watcher: + paths: + - . + - ../../crm + extensions: + - go + ignored_paths: + - .git + - .realize + - vendor diff --git a/cmd/crm/main.go b/cmd/crm/main.go index c50f0313e..4405f7a30 100644 --- a/cmd/crm/main.go +++ b/cmd/crm/main.go @@ -9,11 +9,20 @@ import ( "net/http" "github.com/go-chi/chi" + _ "github.com/joho/godotenv/autoload" "github.com/crusttech/crust/crm/rest" "github.com/titpetric/factory" ) +const ( + defaultAddr = ":3000" + defaultDsn = "crust:crust@tcp(db1:3306)/crust?collation=utf8mb4_general_ci" + + envVarKey_HTTP_ADDR = "CRM_HTTP_ADDR" + envVarKey_DB_DSN = "CRM_DB_DSN" +) + func handleError(err error, message string) { if message == "" { message = "Error making API call" @@ -24,11 +33,23 @@ func handleError(err error, message string) { } func main() { - // set up flags + var envHttpAddr, envDbDsn string + var has bool + + if envHttpAddr, has = os.LookupEnv(envVarKey_HTTP_ADDR); !has { + envHttpAddr = defaultAddr + } + + if envDbDsn, has = os.LookupEnv(envVarKey_DB_DSN); !has { + envDbDsn = defaultDsn + } + var ( - addr = flag.String("addr", ":3000", "Listen address for HTTP server") - dsn = flag.String("dsn", "crust:crust@tcp(db1:3306)/crust?collation=utf8mb4_general_ci", "DSN for database connection") + // set up flags + addr = flag.String("addr", envHttpAddr, "Listen address for HTTP server") + dsn = flag.String("dsn", envDbDsn, "DSN for database connection") ) + println(*dsn) flag.Parse() // log to stdout not stderr From 601f51266728ecad65e8661c5b63698620287563 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Jul 2018 10:56:47 +0200 Subject: [PATCH 3/5] Fix codegen macos issues and update Makefile to use codegen.sh --- Makefile | 7 +++---- codegen.sh | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 36e1eb363..9c008a1d8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build realize dep spec protobuf qa qa.test qa.vet +.PHONY: build realize dep spec protobuf qa qa.test qa.vet codegen PKG = "github.com/$(shell cat .project)" @@ -28,9 +28,8 @@ dep.update: dep: dep ensure -v -spec: $(SPEC) - cd sam/docs/src && $(SPEC) - cd sam/ && ./_gen.sh +codegen: $(SPEC) + ./codegen.sh protobuf: $(PROTOC) # @todo this needs work (it hangs and outputs nothing) diff --git a/codegen.sh b/codegen.sh index f39f8bf33..693f16d8c 100755 --- a/codegen.sh +++ b/codegen.sh @@ -1,14 +1,15 @@ #!/bin/bash set -e +set -x _PWD=$PWD SPECS=$(find $PWD -name 'spec.json' | xargs -n1 dirname) for SPEC in $SPECS; do echo "=== spec $SPEC ===" - cd $SPEC && rm -rf spec && spec && cd $_PWD + cd $SPEC && rm -rf spec && ${GOPATH}/bin/spec && cd $_PWD SRC=$(dirname $(dirname $SPEC)) echo "=== codegen $SRC ===" - GOPATHS=$(codegen/codegen.php $(basename $SRC) | tee -a /dev/stderr | xargs --no-run-if-empty -n1 dirname | sort | uniq) + GOPATHS=$(codegen/codegen.php $(basename $SRC) | tee -a /dev/stderr | xargs -n1 dirname | sort | uniq) for FOLDER in $GOPATHS; do if [[ $FOLDER != "." ]]; then echo "== go fmt $FOLDER ==" From 3bbb094e845ed614be82adbfdcb251898f20a41b Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Jul 2018 11:03:34 +0200 Subject: [PATCH 4/5] Add Name & Type to types.Field --- crm/docs/src/spec.json | 7 +++++-- crm/docs/src/spec/field.json | 13 ++++++++++++- crm/types/field.go | 25 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/crm/docs/src/spec.json b/crm/docs/src/spec.json index 6bebcf4ba..8fb0f101c 100644 --- a/crm/docs/src/spec.json +++ b/crm/docs/src/spec.json @@ -9,7 +9,10 @@ "struct": [ { "name": "Field", - "fields": [] + "fields": [ + { "name": "Name", "type": "string", "tag": "json:\"name\"" }, + { "name": "Type", "type": "string", "tag": "json:\"type\"" } + ] } ], "apis": [ @@ -114,4 +117,4 @@ } ] } -] \ No newline at end of file +] diff --git a/crm/docs/src/spec/field.json b/crm/docs/src/spec/field.json index 50d1954e2..7f75b6f0f 100644 --- a/crm/docs/src/spec/field.json +++ b/crm/docs/src/spec/field.json @@ -5,7 +5,18 @@ "Interface": "Field", "Struct": [ { - "fields": [], + "fields": [ + { + "name": "Name", + "tag": "json:\"name\"", + "type": "string" + }, + { + "name": "Type", + "tag": "json:\"type\"", + "type": "string" + } + ], "name": "Field" } ], diff --git a/crm/types/field.go b/crm/types/field.go index 1d4a1c26c..2380075ac 100644 --- a/crm/types/field.go +++ b/crm/types/field.go @@ -18,6 +18,9 @@ package types type ( // Fields Field struct { + Name string `json:"name" db:"name"` + Type string `json:"type" db:"type"` + changed []string } ) @@ -28,3 +31,25 @@ func (Field) New() *Field { } /* Getters/setters */ +func (f *Field) GetName() string { + return f.Name +} + +func (f *Field) SetName(value string) *Field { + if f.Name != value { + f.changed = append(f.changed, "Name") + f.Name = value + } + return f +} +func (f *Field) GetType() string { + return f.Type +} + +func (f *Field) SetType(value string) *Field { + if f.Type != value { + f.changed = append(f.changed, "Type") + f.Type = value + } + return f +} From 9c05a271f71d0400cc1f3507147715763bca561b Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Jul 2018 11:06:40 +0200 Subject: [PATCH 5/5] Service & repository implementation for CRM field & module --- crm/repository/error.go | 13 ++++ crm/repository/field.go | 72 +++++++++++++++++++++++ crm/repository/module.go | 124 +++++++++++++++++++++++++++++++++++++++ crm/rest/field.go | 16 ++--- crm/rest/module.go | 66 +++++++++------------ crm/service/field.go | 70 +++++++--------------- crm/service/module.go | 48 +++++++++++++++ 7 files changed, 313 insertions(+), 96 deletions(-) create mode 100644 crm/repository/error.go create mode 100644 crm/repository/field.go create mode 100644 crm/repository/module.go create mode 100644 crm/service/module.go diff --git a/crm/repository/error.go b/crm/repository/error.go new file mode 100644 index 000000000..85114c9fe --- /dev/null +++ b/crm/repository/error.go @@ -0,0 +1,13 @@ +package repository + +type ( + repositoryError string +) + +const ( + ErrDatabaseError = repositoryError("DatabaseError") +) + +func (e repositoryError) Error() string { + return "repository." + string(e) +} diff --git a/crm/repository/field.go b/crm/repository/field.go new file mode 100644 index 000000000..c1b2e822b --- /dev/null +++ b/crm/repository/field.go @@ -0,0 +1,72 @@ +package repository + +import ( + "context" + "encoding/json" + "fmt" + "github.com/crusttech/crust/crm/types" + "os" + "path" + "path/filepath" +) + +const ( + // @todo root should be configurable + // @todo move this to db or stack it inside the binary or container + fieldPath = "crm/data/%s.json" +) + +type ( + field struct{} +) + +func Field() field { + return field{} +} + +// Finds field by it's name and returns it +func (repo field) FindByName(ctx context.Context, name string) (*types.Field, error) { + return repo.decode(fmt.Sprintf(fieldPath, name)) +} + +// Returns all known fields +func (repo field) Find(ctx context.Context) ([]*types.Field, error) { + matches, err := filepath.Glob(fmt.Sprintf(fieldPath, "*")) + if err != nil { + return nil, err + } + + res := make([]*types.Field, len(matches)) + for i, match := range matches { + if res[i], err = repo.decode(match); err != nil { + return nil, err + } + } + return res, nil +} + +func (repo field) decode(filepath string) (*types.Field, error) { + file, err := os.Open(filepath) + if err != nil { + // @todo wrap error + return nil, err + } + + defer file.Close() + + // Preset field's type with name of the file (sans .json) + // if type is explicitly set within the file, it will be overwritten + field := &types.Field{Type: repo.typeFromPath(filepath)} + if err := json.NewDecoder(file).Decode(&field); err != nil { + // @todo wrap error + return nil, err + } + + return field, nil +} + +// Removes path and extension from full filename +func (repo field) typeFromPath(filepath string) string { + t := path.Base(filepath) + return t[:len(t)-5] +} diff --git a/crm/repository/module.go b/crm/repository/module.go new file mode 100644 index 000000000..5ca494e47 --- /dev/null +++ b/crm/repository/module.go @@ -0,0 +1,124 @@ +package repository + +import ( + "context" + "github.com/crusttech/crust/crm/types" + "github.com/titpetric/factory" +) + +type ( + module struct{} +) + +func Module() module { + return module{} +} + +func (r module) FindById(ctx context.Context, id uint64) (*types.Module, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := &types.Module{} + if err := db.Get(mod, "SELECT * FROM crm_module WHERE id = ?", id); err != nil { + println(err.Error()) + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r module) Find(ctx context.Context) ([]*types.Module, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := make([]*types.Module, 0) + if err := db.Select(&mod, "SELECT * FROM crm_module ORDER BY name ASC"); err != nil { + println(err.Error()) + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r module) Create(ctx context.Context, mod *types.Module) (*types.Module, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod.SetID(factory.Sonyflake.NextID()) + if err := db.Insert("crm_module", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r module) Update(ctx context.Context, mod *types.Module) (*types.Module, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + if err := db.Replace("crm_module", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r module) Delete(ctx context.Context, mod *types.Module) error { + db, err := factory.Database.Get() + if err != nil { + return ErrDatabaseError + } + + if _, err := db.Exec("DELETE FROM crm_module WHERE ID = ?", mod.ID); err != nil { + return ErrDatabaseError + } else { + return nil + } +} + +//func (r module) Edit(r *moduleEditRequest) (interface{}, error) { +// db, err := factory.Database.Get() +// if err != nil { +// return nil, err +// } +// +// m := module{}.New() +// m.SetID(r.id).SetName(r.name) +// if m.GetID() > 0 { +// return m, db.Replace("crm_module", m) +// } +// m.SetID(factory.Sonyflake.NextID()) +// return m, db.Insert("crm_module", m) +//} +// +//func (r module) ContentList(r *moduleContentListRequest) (interface{}, error) { +// db, err := factory.Database.Get() +// if err != nil { +// return nil, err +// } +// +// if r.id > 0 { +// m := ModuleContentRow{}.New() +// return m, db.Get(m, "select * from crm_module id=?", r.id) +// } +// +// res := make([]ModuleContentRow, 0) +// err = db.Select(&res, "select * from crm_module order by name asc") +// return res, err +//} +// +//func (r module) ContentEdit(r *moduleContentEditRequest) (interface{}, error) { +// return nil, errors.New("Not implemented: module.content/edit") +//} +// +//func (r module) ContentDelete(r *moduleContentDeleteRequest) (interface{}, error) { +// return nil, errors.New("Not implemented: module.content/delete") +//} diff --git a/crm/rest/field.go b/crm/rest/field.go index 98f0c0af1..8a36b48b6 100644 --- a/crm/rest/field.go +++ b/crm/rest/field.go @@ -3,31 +3,33 @@ package rest import ( "github.com/pkg/errors" + "context" "github.com/crusttech/crust/crm/rest/server" "github.com/crusttech/crust/crm/service" + "github.com/crusttech/crust/crm/types" ) var _ = errors.Wrap type ( Field struct { - service FieldInterface + service fieldService } - FieldInterface interface { - List() (interface{}, error) - Type(id string) (interface{}, error) + fieldService interface { + FindByName(context.Context, string) (*types.Field, error) + Find(context.Context) ([]*types.Field, error) } ) func (Field) New() *Field { - return &Field{service.Field{}.New()} + return &Field{service: service.Field()} } func (self *Field) List(_ *server.FieldListRequest) (interface{}, error) { - return self.service.List() + return self.service.Find(context.TODO()) } func (self *Field) Type(r *server.FieldTypeRequest) (interface{}, error) { - return self.service.Type(r.ID) + return self.service.FindByName(context.TODO(), r.ID) } diff --git a/crm/rest/module.go b/crm/rest/module.go index 001b65a42..ed09b6651 100644 --- a/crm/rest/module.go +++ b/crm/rest/module.go @@ -2,65 +2,53 @@ package rest import ( "github.com/pkg/errors" - "github.com/titpetric/factory" + "context" "github.com/crusttech/crust/crm/rest/server" + "github.com/crusttech/crust/crm/service" "github.com/crusttech/crust/crm/types" ) var _ = errors.Wrap -type Module struct{} +type ( + Module struct { + service moduleService + } + + moduleService interface { + FindById(context.Context, uint64) (*types.Module, error) + Find(context.Context) ([]*types.Module, error) + + Create(context.Context, *types.Module) (*types.Module, error) + Update(context.Context, *types.Module) (*types.Module, error) + Delete(context.Context, *types.Module) error + } +) func (Module) New() *Module { - return &Module{} + return &Module{ + service: service.Module(), + } } -func (*Module) List(r *server.ModuleListRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - if r.ID > 0 { - m := types.Module{}.New() - return m, db.Get(m, "select * from crm_module id=?", r.ID) - } - - res := make([]Module, 0) - err = db.Select(&res, "select * from crm_module order by name asc") - return res, err +func (c *Module) List(r *server.ModuleListRequest) (interface{}, error) { + return c.service.Find(context.TODO()) } -func (*Module) Edit(r *server.ModuleEditRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - +func (c *Module) Edit(r *server.ModuleEditRequest) (interface{}, error) { m := types.Module{}.New() m.SetID(r.ID).SetName(r.Name) + if m.GetID() > 0 { - return m, db.Replace("crm_module", m) + return c.service.Update(context.TODO(), m) } - m.SetID(factory.Sonyflake.NextID()) - return m, db.Insert("crm_module", m) + + return c.service.Create(context.TODO(), m) } func (*Module) ContentList(r *server.ModuleContentListRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - if r.ID > 0 { - m := types.ModuleContentRow{}.New() - return m, db.Get(m, "select * from crm_module id=?", r.ID) - } - - res := make([]types.ModuleContentRow, 0) - err = db.Select(&res, "select * from crm_module order by name asc") - return res, err + return nil, errors.New("Not implemented: Module.content/edit") } func (*Module) ContentEdit(r *server.ModuleContentEditRequest) (interface{}, error) { diff --git a/crm/service/field.go b/crm/service/field.go index 9009e5799..e635c35ef 100644 --- a/crm/service/field.go +++ b/crm/service/field.go @@ -1,62 +1,32 @@ package service import ( - "encoding/json" - "os" - "path" - "path/filepath" - - "github.com/pkg/errors" - - _ "github.com/crusttech/crust/crm/types" + "context" + "github.com/crusttech/crust/crm/repository" + "github.com/crusttech/crust/crm/types" ) -var _ = errors.Wrap +type ( + fieldType struct { + repository fieldTypeRepository + } -type Field struct{} + fieldTypeRepository interface { + FindByName(context.Context, string) (*types.Field, error) + Find(context.Context) ([]*types.Field, error) + } +) -func (Field) New() *Field { - return &Field{} +func Field() fieldType { + return fieldType{ + repository: repository.Field(), + } } -func (*Field) List() (interface{}, error) { - matches, err := filepath.Glob("../crm/data/*.json") - if err != nil { - return nil, err - } - - res := make([]interface{}, 0) - for _, match := range matches { - t := path.Base(match) - t = t[:len(t)-5] - params, err := decodeJSON(match) - if err != nil { - return nil, errors.Wrap(err, "Error when parsing "+match) - } - params["type"] = t - res = append(res, params) - } - return res, nil +func (svc fieldType) FindByName(ctx context.Context, name string) (*types.Field, error) { + return svc.repository.FindByName(ctx, name) } -func (*Field) Type(id string) (interface{}, error) { - if id == "" { - return nil, errors.New("Missing id parameter") - } - params, err := decodeJSON("../crm/data/" + id + ".json") - if err != nil { - return nil, errors.Wrap(err, "Error reading field type: "+id) - } - params["type"] = id - return params, nil -} - -func decodeJSON(filename string) (map[string]interface{}, error) { - file, err := os.Open(filename) - if err != nil { - return nil, err - } - defer file.Close() - result := make(map[string]interface{}) - return result, json.NewDecoder(file).Decode(&result) +func (svc fieldType) Find(ctx context.Context) ([]*types.Field, error) { + return svc.repository.Find(ctx) } diff --git a/crm/service/module.go b/crm/service/module.go new file mode 100644 index 000000000..c730eb2f0 --- /dev/null +++ b/crm/service/module.go @@ -0,0 +1,48 @@ +package service + +import ( + "context" + "github.com/crusttech/crust/crm/repository" + "github.com/crusttech/crust/crm/types" +) + +type ( + module struct { + repository moduleRepository + } + + moduleRepository interface { + FindById(context.Context, uint64) (*types.Module, error) + Find(context.Context) ([]*types.Module, error) + + Create(context.Context, *types.Module) (*types.Module, error) + Update(context.Context, *types.Module) (*types.Module, error) + Delete(context.Context, *types.Module) error + } +) + +func Module() module { + return module{ + repository: repository.Module(), + } +} + +func (svc module) FindById(ctx context.Context, id uint64) (*types.Module, error) { + return svc.repository.FindById(ctx, id) +} + +func (svc module) Find(ctx context.Context) ([]*types.Module, error) { + return svc.repository.Find(ctx) +} + +func (svc module) Create(ctx context.Context, mod *types.Module) (*types.Module, error) { + return svc.repository.Create(ctx, mod) +} + +func (svc module) Update(ctx context.Context, mod *types.Module) (*types.Module, error) { + return svc.repository.Update(ctx, mod) +} + +func (svc module) Delete(ctx context.Context, mod *types.Module) error { + return svc.repository.Delete(ctx, mod) +}