From 02eebb5cfc1ede5e0542237b75e270ed1466c5f9 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Wed, 21 Nov 2018 05:50:07 +0100 Subject: [PATCH] Add /page/tree, generated set types --- crm/docs/README.md | 13 +++++ crm/docs/src/spec.json | 7 +++ crm/docs/src/spec/page.json | 7 +++ crm/repository/module.go | 6 +-- crm/repository/page.go | 16 ++++-- crm/rest/handlers/page.go | 10 ++++ crm/rest/page.go | 4 ++ crm/rest/request/page.go | 40 ++++++++++++++ crm/service/page.go | 65 ++++++++++++++++++++-- crm/types/type-sets.gen.go | 105 ++++++++++++++++++++++++++++++++++++ crm/types/types.go | 4 ++ 11 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 crm/types/type-sets.gen.go diff --git a/crm/docs/README.md b/crm/docs/README.md index 6d497f78f..9805e2e70 100644 --- a/crm/docs/README.md +++ b/crm/docs/README.md @@ -265,6 +265,19 @@ CRM module pages | --------- | ---- | ------ | ----------- | ------- | --------- | | pageID | uint64 | PATH | Page ID | N/A | YES | +## Get page all pages, hierarchically + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/tree` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + ## Edit page #### Method diff --git a/crm/docs/src/spec.json b/crm/docs/src/spec.json index 15f7e0688..7372abe3b 100644 --- a/crm/docs/src/spec.json +++ b/crm/docs/src/spec.json @@ -141,6 +141,13 @@ ] } }, + { + "name": "tree", + "path": "/tree", + "method": "GET", + "title": "Get page all pages, hierarchically", + "parameters": {} + }, { "name": "edit", "method": "POST", diff --git a/crm/docs/src/spec/page.json b/crm/docs/src/spec/page.json index b76be9522..88d9fb63e 100644 --- a/crm/docs/src/spec/page.json +++ b/crm/docs/src/spec/page.json @@ -87,6 +87,13 @@ ] } }, + { + "Name": "tree", + "Method": "GET", + "Title": "Get page all pages, hierarchically", + "Path": "/tree", + "Parameters": {} + }, { "Name": "edit", "Method": "POST", diff --git a/crm/repository/module.go b/crm/repository/module.go index 475a91ef8..9a96a55d8 100644 --- a/crm/repository/module.go +++ b/crm/repository/module.go @@ -19,7 +19,7 @@ type ( Chart(r *request.ModuleChart) (interface{}, error) FindByID(id uint64) (*types.Module, error) - Find() ([]*types.Module, error) + Find() (types.ModuleSet, error) Create(mod *types.Module) (*types.Module, error) Update(mod *types.Module) (*types.Module, error) DeleteByID(id uint64) error @@ -56,8 +56,8 @@ func (r *module) FindByID(id uint64) (*types.Module, error) { return mod, nil } -func (r *module) Find() ([]*types.Module, error) { - mod := make([]*types.Module, 0) +func (r *module) Find() (types.ModuleSet, error) { + mod := types.ModuleSet{} return mod, r.db().Select(&mod, "SELECT * FROM crm_module ORDER BY id ASC") } diff --git a/crm/repository/page.go b/crm/repository/page.go index 2b5ec1b0a..43e341230 100644 --- a/crm/repository/page.go +++ b/crm/repository/page.go @@ -13,9 +13,10 @@ type ( PageRepository interface { With(ctx context.Context, db *factory.DB) PageRepository - Find(selfID uint64) ([]*types.Page, error) FindByID(id uint64) (*types.Page, error) FindByModuleID(id uint64) (*types.Page, error) + FindBySelfID(selfID uint64) (types.PageSet, error) + FindAll() (types.PageSet, error) Create(mod *types.Page) (*types.Page, error) Update(mod *types.Page) (*types.Page, error) @@ -58,9 +59,9 @@ func (r *page) FindByModuleID(id uint64) (*types.Page, error) { return page, nil } -func (r *page) Find(selfID uint64) ([]*types.Page, error) { - pages := make([]*types.Page, 0) - if err := r.db().Select(&pages, "SELECT * FROM crm_page where self_id=? ORDER BY weight ASC", selfID); err != nil { +func (r *page) FindBySelfID(selfID uint64) (types.PageSet, error) { + pages := types.PageSet{} + if err := r.db().Select(&pages, "SELECT * FROM crm_page WHERE self_id = ? ORDER BY weight ASC", selfID); err != nil { return pages, err } for _, page := range pages { @@ -71,9 +72,14 @@ func (r *page) Find(selfID uint64) ([]*types.Page, error) { return pages, nil } +func (r *page) FindAll() (types.PageSet, error) { + pages := types.PageSet{} + return pages, r.db().Select(&pages, "SELECT * FROM crm_page ORDER BY self_id, weight ASC") +} + func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { pageMap := map[uint64]bool{} - if pages, err := r.Find(selfID); err != nil { + if pages, err := r.FindBySelfID(selfID); err != nil { return nil } else { for _, page := range pages { diff --git a/crm/rest/handlers/page.go b/crm/rest/handlers/page.go index dd12051f9..15c459f7c 100644 --- a/crm/rest/handlers/page.go +++ b/crm/rest/handlers/page.go @@ -30,6 +30,7 @@ type PageAPI interface { List(context.Context, *request.PageList) (interface{}, error) Create(context.Context, *request.PageCreate) (interface{}, error) Read(context.Context, *request.PageRead) (interface{}, error) + Tree(context.Context, *request.PageTree) (interface{}, error) Edit(context.Context, *request.PageEdit) (interface{}, error) Reorder(context.Context, *request.PageReorder) (interface{}, error) Delete(context.Context, *request.PageDelete) (interface{}, error) @@ -40,6 +41,7 @@ type Page struct { List func(http.ResponseWriter, *http.Request) Create func(http.ResponseWriter, *http.Request) Read func(http.ResponseWriter, *http.Request) + Tree func(http.ResponseWriter, *http.Request) Edit func(http.ResponseWriter, *http.Request) Reorder func(http.ResponseWriter, *http.Request) Delete func(http.ResponseWriter, *http.Request) @@ -68,6 +70,13 @@ func NewPage(ph PageAPI) *Page { return ph.Read(r.Context(), params) }) }, + Tree: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPageTree() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.Tree(r.Context(), params) + }) + }, Edit: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() params := request.NewPageEdit() @@ -99,6 +108,7 @@ func (ph *Page) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http r.Get("/", ph.List) r.Post("/", ph.Create) r.Get("/{pageID}", ph.Read) + r.Get("/tree", ph.Tree) r.Post("/{pageID}", ph.Edit) r.Post("/{selfID}/reorder", ph.Reorder) r.Delete("/{pageID}", ph.Delete) diff --git a/crm/rest/page.go b/crm/rest/page.go index 979ef5e2d..3909016a7 100644 --- a/crm/rest/page.go +++ b/crm/rest/page.go @@ -25,6 +25,10 @@ func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, e return ctrl.page.With(ctx).Find(r.SelfID) } +func (ctrl *Page) Tree(ctx context.Context, r *request.PageTree) (interface{}, error) { + return ctrl.page.With(ctx).Tree() +} + func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{}, error) { p := &types.Page{ SelfID: r.SelfID, diff --git a/crm/rest/request/page.go b/crm/rest/request/page.go index 4684d2828..4c676a8dd 100644 --- a/crm/rest/request/page.go +++ b/crm/rest/request/page.go @@ -192,6 +192,46 @@ func (p *PageRead) Fill(r *http.Request) (err error) { var _ RequestFiller = NewPageRead() +// Page tree request parameters +type PageTree struct { +} + +func NewPageTree() *PageTree { + return &PageTree{} +} + +func (p *PageTree) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(p) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + return err +} + +var _ RequestFiller = NewPageTree() + // Page edit request parameters type PageEdit struct { PageID uint64 `json:",string"` diff --git a/crm/service/page.go b/crm/service/page.go index 19ed222a8..fcc817e9f 100644 --- a/crm/service/page.go +++ b/crm/service/page.go @@ -14,13 +14,15 @@ type ( db *factory.DB ctx context.Context repository repository.PageRepository + moduleRepo repository.ModuleRepository } PageService interface { With(ctx context.Context) PageService FindByID(pageID uint64) (*types.Page, error) - Find(selfID uint64) ([]*types.Page, error) + Find(selfID uint64) (pages types.PageSet, err error) + Tree() (pages types.PageSet, err error) Create(page *types.Page) (*types.Page, error) Update(page *types.Page) (*types.Page, error) @@ -40,6 +42,7 @@ func (s *page) With(ctx context.Context) PageService { db: db, ctx: ctx, repository: repository.Page(ctx, db), + moduleRepo: repository.Module(ctx, db), } } @@ -47,8 +50,50 @@ func (s *page) FindByID(id uint64) (*types.Page, error) { return s.repository.FindByID(id) } -func (s *page) Find(selfID uint64) ([]*types.Page, error) { - return s.repository.Find(selfID) +func (s *page) Find(selfID uint64) (pages types.PageSet, err error) { + return pages, s.db.Transaction(func() (err error) { + if pages, err = s.repository.FindBySelfID(selfID); err != nil { + return + } + + if err = s.preload(pages); err != nil { + return + } + + return nil + }) +} + +func (s *page) Tree() (pages types.PageSet, err error) { + var tree types.PageSet + + return tree, s.db.Transaction(func() (err error) { + if pages, err = s.repository.FindAll(); err != nil { + return + } + + if err = s.preload(pages); err != nil { + return + } + + _ = pages.Walk(func(p *types.Page) error { + if p.SelfID == 0 { + tree = append(tree, p) + } else if c := pages.FindByID(p.SelfID); c != nil { + if c.Children == nil { + c.Children = types.PageSet{} + } + + c.Children = append(c.Children, p) + } else { + // Ignore orphans :( + } + + return nil + }) + + return nil + }) } func (s *page) Reorder(selfID uint64, pageIDs []uint64) error { @@ -66,3 +111,17 @@ func (s *page) Update(mod *types.Page) (*types.Page, error) { func (s *page) DeleteByID(id uint64) error { return s.repository.DeleteByID(id) } + +// Preloads modules for all pages +func (s *page) preload(pages types.PageSet) error { + if modules, err := s.moduleRepo.Find(); err != nil { + return err + } else { + _ = pages.Walk(func(page *types.Page) error { + page.Module = modules.FindByID(page.ModuleID) + return nil + }) + } + + return nil +} diff --git a/crm/types/type-sets.gen.go b/crm/types/type-sets.gen.go new file mode 100644 index 000000000..3e55a9770 --- /dev/null +++ b/crm/types/type-sets.gen.go @@ -0,0 +1,105 @@ +package types + +type ( + + // ModuleSet slice of Module + ModuleSet []*Module + // PageSet slice of Page + PageSet []*Page +) + +// Walk iterates through every slice item and calls w(Module) err +func (set ModuleSet) Walk(w func(*Module) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(Module) (bool, err) and return filtered slice +func (set ModuleSet) Filter(f func(*Module) (bool, error)) (out ModuleSet, err error) { + var ok bool + out = ModuleSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// Finds slice item by its ID property +func (set ModuleSet) FindByID(ID uint64) *Module { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// Returns a slice of uint64s from all items in the set +func (set ModuleSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + +// Walk iterates through every slice item and calls w(Page) err +func (set PageSet) Walk(w func(*Page) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(Page) (bool, err) and return filtered slice +func (set PageSet) Filter(f func(*Page) (bool, error)) (out PageSet, err error) { + var ok bool + out = PageSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// Finds slice item by its ID property +func (set PageSet) FindByID(ID uint64) *Page { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// Returns a slice of uint64s from all items in the set +func (set PageSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} diff --git a/crm/types/types.go b/crm/types/types.go index 74b6fdb14..4cc86d968 100644 --- a/crm/types/types.go +++ b/crm/types/types.go @@ -1,5 +1,7 @@ package types +//go:generate go run ../../codegen/v2/type-set.go Module Page type-sets.gen.go + import ( "time" @@ -80,6 +82,8 @@ type ( Blocks types.JSONText `json:"blocks" db:"blocks"` + Children PageSet `json:"children,omitempty" db:"-"` + Visible bool `json:"visible" db:"visible"` Weight int `json:"-" db:"weight"` }