add(crm): content order params

This commit is contained in:
Mitja Zivkovic
2018-12-05 14:26:49 +01:00
committed by Denis Arh
parent 9d62e6090e
commit dc2635e05c
9 changed files with 201 additions and 35 deletions
+6
View File
@@ -454,6 +454,12 @@
"type": "int",
"required": false,
"title": "Returned items per page (default 50)"
},
{
"name": "sort",
"type": "string",
"required": false,
"title": "Sort field (default id desc)"
}
]
}
+6
View File
@@ -218,6 +218,12 @@
"required": false,
"title": "Returned items per page (default 50)",
"type": "int"
},
{
"name": "sort",
"required": false,
"title": "Sort field (default id desc)",
"type": "string"
}
],
"path": [
+83 -4
View File
@@ -20,7 +20,7 @@ type (
FindByID(id uint64) (*types.Content, error)
Find(moduleID uint64, query string, page int, perPage int) (*FindResponse, error)
Find(moduleID uint64, query string, page int, perPage int, sort string) (*FindResponse, error)
Create(mod *types.Content) (*types.Content, error)
Update(mod *types.Content) (*types.Content, error)
@@ -34,6 +34,7 @@ type (
Page int `json:"page"`
PerPage int `json:"perPage"`
Count int `json:"count"`
Sort string `json:"sort"`
}
FindResponse struct {
@@ -66,7 +67,7 @@ func (r *content) FindByID(id uint64) (*types.Content, error) {
return mod, nil
}
func (r *content) Find(moduleID uint64, query string, page int, perPage int) (*FindResponse, error) {
func (r *content) Find(moduleID uint64, query string, page int, perPage int, sort string) (*FindResponse, error) {
if page < 0 {
page = 0
}
@@ -84,6 +85,7 @@ func (r *content) Find(moduleID uint64, query string, page int, perPage int) (*F
Page: page,
PerPage: perPage,
Query: query,
Sort: sort,
},
Contents: make([]*types.Content, 0),
}
@@ -93,10 +95,87 @@ func (r *content) Find(moduleID uint64, query string, page int, perPage int) (*F
sqlSelect := "SELECT * FROM crm_content"
sqlCount := "SELECT count(*) FROM crm_content"
sqlWhere := "WHERE module_id=? and deleted_at IS NULL"
sqlOrder := "ORDER BY id DESC"
sqlLimit := fmt.Sprintf("LIMIT %d, %d", page*perPage, perPage)
chuncks := strings.Split(sort, ",")
if len(chuncks) > 0 {
// Ger module fields.
modulRepo := Module(r.Context(), r.db())
mod, err := modulRepo.FindByID(moduleID)
if err != nil {
return nil, err
}
modFields, err := modulRepo.FieldNames(mod)
if err != nil {
return nil, err
}
fieldMap := make(map[string]bool)
for i := 0; i < len(modFields); i++ {
fieldMap[modFields[i]] = true
}
orderFields := make([]string, 0)
for _, c := range chuncks {
args := strings.Split(c, " ")
var field string
if _, ok := fieldMap[args[0]]; ok {
field = "JSON_UNQUOTE(JSON_EXTRACT(json, REPLACE(JSON_UNQUOTE(JSON_SEARCH(json, 'one', '" + args[0] + "')), '.name', '.value')))"
} else {
switch args[0] {
case "moduleId":
field = "module_id"
case "userId":
field = "user_id"
case "createdAt":
field = "created_at"
case "updatedAt":
field = "updated_at"
case "deletedAt":
field = "deleted_at"
default:
field = "id"
}
}
// Check for second order parameter or use default value ASC.
order := "ASC"
if len(args) == 2 {
order = strings.ToUpper(args[1])
switch order {
case "DESC":
order = "DESC"
default:
order = "ASC"
}
}
// We skip batch of parameters if there are more then 2 values.
if len(args) > 2 {
continue
}
// Add field and order to sort order fields.
orderFields = append(orderFields, field+" "+order)
}
sqlOrder = "ORDER BY " + strings.Join(orderFields, ", ")
}
// One possibility to order by field value without JSON, is query written bellow with FIELD over column names and order by value:
// SELECT * FROM crm_content
// LEFT JOIN crm_content_column ON crm_content.id = crm_content_column.content_id"
// WHERE column_name in ('name', 'email')
// ORDER BY FIELD(column_name, 'email', 'name'), column_value;
// Possibility to order with JSON:
// SELECT *,
// JSON_UNQUOTE(JSON_EXTRACT(json, REPLACE(JSON_UNQUOTE(JSON_SEARCH(json, 'all', 'email')), '.name', '.value'))) as emailField
// FROM crm_content
// ORDER by emailField asc;
switch true {
case query != "":
sqlWhere = sqlWhere + " AND id in (select distinct content_id from crm_content_column where column_value like ?)"
@@ -110,7 +189,7 @@ func (r *content) Find(moduleID uint64, query string, page int, perPage int) (*F
if err := r.db().Get(&response.Meta.Count, sqlCount+" "+sqlWhere, moduleID); err != nil {
return nil, err
}
if err := r.db().Select(&response.Contents, fmt.Sprintf("SELECT * FROM crm_content WHERE module_id=? and deleted_at IS NULL ORDER BY id DESC LIMIT %d, %d", page, perPage), moduleID); err != nil {
if err := r.db().Select(&response.Contents, sqlSelect+" "+sqlWhere+" "+sqlOrder+" "+sqlLimit, moduleID); err != nil {
return nil, err
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ func (s *Module) Edit(ctx context.Context, r *request.ModuleEdit) (interface{},
}
func (s *Module) ContentList(ctx context.Context, r *request.ModuleContentList) (interface{}, error) {
return s.content.With(ctx).Find(r.ModuleID, r.Query, r.Page, r.PerPage)
return s.content.With(ctx).Find(r.ModuleID, r.Query, r.Page, r.PerPage, r.Sort)
}
func (s *Module) ContentRead(ctx context.Context, r *request.ModuleContentRead) (interface{}, error) {
+5
View File
@@ -358,6 +358,7 @@ type ModuleContentList struct {
Query string
Page int
PerPage int
Sort string
ModuleID uint64 `json:",string"`
}
@@ -404,6 +405,10 @@ func (m *ModuleContentList) Fill(r *http.Request) (err error) {
m.PerPage = parseInt(val)
}
if val, ok := get["sort"]; ok {
m.Sort = val
}
m.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
return err
+4 -4
View File
@@ -28,7 +28,7 @@ type (
FindByID(contentID uint64) (*types.Content, error)
Find(moduleID uint64, query string, page int, perPage int) (*repository.FindResponse, error)
Find(moduleID uint64, query string, page int, perPage int, sort string) (*repository.FindResponse, error)
Create(content *types.Content) (*types.Content, error)
Update(content *types.Content) (*types.Content, error)
@@ -63,8 +63,8 @@ func (s *content) FindByID(id uint64) (*types.Content, error) {
return response, s.preload(response, "page", "user", "fields")
}
func (s *content) Find(moduleID uint64, query string, page int, perPage int) (*repository.FindResponse, error) {
response, err := s.repository.Find(moduleID, query, page, perPage)
func (s *content) Find(moduleID uint64, query string, page int, perPage int, sort string) (*repository.FindResponse, error) {
response, err := s.repository.Find(moduleID, query, page, perPage, sort)
if err != nil {
return nil, err
}
@@ -84,7 +84,7 @@ func (s *content) Create(mod *types.Content) (*types.Content, error) {
func (s *content) Update(mod *types.Content) (*types.Content, error) {
if mod.ID == 0 {
return nil, errors.New("Error when savig content, invalid ID")
return nil, errors.New("Error when saving content, invalid ID")
}
return s.repository.Update(mod)
}
+94 -25
View File
@@ -48,6 +48,10 @@ func TestContent(t *testing.T) {
Name: "options",
Kind: "select_multi",
},
types.ModuleField{
Name: "description",
Kind: "text",
},
},
}
@@ -71,37 +75,75 @@ func TestContent(t *testing.T) {
Name: "options",
Related: []string{"1", "2", "3"},
},
types.ContentColumn{
Name: "description",
Value: "jack of all trades",
},
}
content := &types.Content{
content1 := &types.Content{
ModuleID: module.ID,
}
(&content.Fields).Scan(func() []byte {
(&content1.Fields).Scan(func() []byte {
b, _ := json.Marshal(columns)
return b
}())
columns2 := []types.ContentColumn{
types.ContentColumn{
Name: "name",
Value: "Marko Novak",
},
types.ContentColumn{
Name: "email",
Value: "marko.n@example.com",
},
types.ContentColumn{
Name: "options",
Related: []string{"1", "2", "3"},
},
types.ContentColumn{
Name: "description",
Value: "persona non grata",
},
}
content2 := &types.Content{
ModuleID: module.ID,
}
(&content2.Fields).Scan(func() []byte {
b, _ := json.Marshal(columns2)
return b
}())
// now work with content
{
{
m, err := repository.Update(content)
assert(t, m == nil, "Expected empty return for ivalid update, got %#v", m)
m, err := repository.Update(content1)
assert(t, m == nil, "Expected empty return for invalid update, got %#v", m)
assert(t, err != nil, "Expected error when updating invalid content")
}
// create content
m, err := repository.Create(content)
m1, err := repository.Create(content1)
assert(t, err == nil, "Error when creating content: %+v", err)
assert(t, m.ID > 0, "Expected auto generated ID")
assert(t, m.User != nil, "Expected non-nil user when creating content")
assert(t, m.User.Username == "TestUser", "Expected 'TestUser' as username, got '%s'", m.User.Username)
assert(t, m1.ID > 0, "Expected auto generated ID")
assert(t, m1.User != nil, "Expected non-nil user when creating content")
assert(t, m1.User.Username == "TestUser", "Expected 'TestUser' as username, got '%s'", m1.User.Username)
// create content
m2, err := repository.Create(content2)
assert(t, err == nil, "Error when creating content: %+v", err)
assert(t, m2.ID > 0, "Expected auto generated ID")
assert(t, m2.User != nil, "Expected non-nil user when creating content")
assert(t, m2.User.Username == "TestUser", "Expected 'TestUser' as username, got '%s'", m2.User.Username)
// fetch created content
{
ms, err := repository.FindByID(m.ID)
ms, err := repository.FindByID(m1.ID)
assert(t, err == nil, "Error when retrieving content by id: %+v", err)
assert(t, ms.ID == m.ID, "Expected ID from database to match, %d != %d", m.ID, ms.ID)
assert(t, ms.ModuleID == m.ModuleID, "Expected Module ID from database to match, %d != %d", m.ModuleID, ms.ModuleID)
assert(t, ms.ID == m1.ID, "Expected ID from database to match, %d != %d", m1.ID, ms.ID)
assert(t, ms.ModuleID == m1.ModuleID, "Expected Module ID from database to match, %d != %d", m1.ModuleID, ms.ModuleID)
{
fields, err := repository.Fields(ms)
@@ -126,57 +168,84 @@ func TestContent(t *testing.T) {
// update created content
{
_, err := repository.Update(m)
_, err := repository.Update(m1)
assert(t, err == nil, "Error when updating content, %+v", err)
}
// re-fetch content
{
ms, err := repository.FindByID(m.ID)
ms, err := repository.FindByID(m1.ID)
assert(t, err == nil, "Error when retrieving content by id: %+v", err)
assert(t, ms.ID == m.ID, "Expected ID from database to match, %d != %d", m.ID, ms.ID)
assert(t, ms.ModuleID == m.ModuleID, "Expected ID from database to match, %d != %d", m.ModuleID, ms.ModuleID)
assert(t, ms.ID == m1.ID, "Expected ID from database to match, %d != %d", m1.ID, ms.ID)
assert(t, ms.ModuleID == m1.ModuleID, "Expected ID from database to match, %d != %d", m1.ModuleID, ms.ModuleID)
}
// fetch all contents
{
mr, err := repository.Find(module.ID, "", 0, 20)
mr, err := repository.Find(module.ID, "", 0, 20, "id desc")
assert(t, err == nil, "Error when retrieving contents: %+v", err)
assert(t, len(mr.Contents) == 1, "Expected one content, got %d", len(mr.Contents))
assert(t, mr.Meta.Count == 1, "Expected Meta.Count == 1, got %d", mr.Meta.Count)
assert(t, mr.Contents[0].ModuleID == m.ModuleID, "Expected content module to match, %d != %d", m.ModuleID, mr.Contents[0].ModuleID)
assert(t, len(mr.Contents) == 2, "Expected two content, got %d", len(mr.Contents))
assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count)
assert(t, mr.Meta.Sort == "id desc", "Expected Meta.Sort == id desc, got '%s'", mr.Meta.Sort)
assert(t, mr.Contents[0].ModuleID == m1.ModuleID, "Expected content module to match, %d != %d", m1.ModuleID, mr.Contents[0].ModuleID)
assert(t, mr.Contents[0].ID > mr.Contents[1].ID, "Expected order to be descending")
}
// fetch all contents
{
mr, err := repository.Find(module.ID, "", 0, 20, "name asc, email desc")
assert(t, err == nil, "Error when retrieving contents: %+v", err)
assert(t, len(mr.Contents) == 2, "Expected two content, got %d", len(mr.Contents))
assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count)
assert(t, mr.Meta.Sort == "name asc, email desc", "Expected Meta.Sort == 'name asc, email desc' '%s'", mr.Meta.Sort)
assert(t, mr.Contents[0].ModuleID == m1.ModuleID, "Expected content module to match, %d != %d", m1.ModuleID, mr.Contents[0].ModuleID)
assert(t, mr.Contents[0].ID > mr.Contents[1].ID, "Expected order to be ascending")
}
// fetch all contents
{
mr, err := repository.Find(module.ID, "", 0, 20, "created_at desc")
assert(t, err == nil, "Error when retrieving contents: %+v", err)
assert(t, len(mr.Contents) == 2, "Expected two content, got %d", len(mr.Contents))
assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count)
assert(t, mr.Meta.Sort == "created_at desc", "Expected Meta.Sort == created_at desc, got '%s'", mr.Meta.Sort)
assert(t, mr.Contents[0].ModuleID == m1.ModuleID, "Expected content module to match, %d != %d", m1.ModuleID, mr.Contents[0].ModuleID)
assert(t, mr.Contents[0].ID > mr.Contents[1].ID, "Expected order to be ascending")
}
// fetch all contents by query
{
mr, err := repository.Find(module.ID, "petric", 0, 20)
mr, err := repository.Find(module.ID, "petric", 0, 20, "id desc")
assert(t, err == nil, "Error when retrieving contents: %+v", err)
assert(t, len(mr.Contents) == 1, "Expected one content, got %d", len(mr.Contents))
assert(t, mr.Meta.Count == 1, "Expected Meta.Count == 1, got %d", mr.Meta.Count)
assert(t, mr.Meta.Page == 0, "Expected Meta.Page == 0, got %d", mr.Meta.Page)
assert(t, mr.Meta.PerPage == 20, "Expected Meta.PerPage == 20, got %d", mr.Meta.PerPage)
assert(t, mr.Meta.Query == "petric", "Expected Meta.Query == petric, got '%s'", mr.Meta.Query)
assert(t, mr.Meta.Sort == "id desc", "Expected Meta.Sort == id desc, got '%s'", mr.Meta.Sort)
}
// fetch all contents by query
{
mr, err := repository.Find(module.ID, "niall", 0, 20)
mr, err := repository.Find(module.ID, "niall", 0, 20, "id asc")
assert(t, err == nil, "Error when retrieving contents: %+v", err)
assert(t, len(mr.Contents) == 0, "Expected no contents, got %d", len(mr.Contents))
}
// re-fetch content
// delete content
{
err := repository.DeleteByID(m.ID)
err := repository.DeleteByID(m1.ID)
assert(t, err == nil, "Error when retrieving content by id: %+v", err)
err = repository.DeleteByID(m2.ID)
assert(t, err == nil, "Error when retrieving content by id: %+v", err)
}
// fetch all contents
{
mr, err := repository.Find(module.ID, "", 0, 20)
mr, err := repository.Find(module.ID, "", 0, 20, "")
assert(t, err == nil, "Error when retrieving contents: %+v", err)
assert(t, len(mr.Contents) == 0, "Expected no content, got %d", len(mr.Contents))
}
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ func TestPage(t *testing.T) {
{
{
m, err := repository.Update(page)
assert(t, m == nil, "Expected empty return for ivalid update, got %#v", m)
assert(t, m == nil, "Expected empty return for invalid update, got %#v", m)
assert(t, err != nil, "Expected error when updating invalid content")
}
+1
View File
@@ -333,6 +333,7 @@ Example bar chart with number of leads per country: select lead.country from lea
| query | string | GET | Search query | N/A | NO |
| page | int | GET | Page number (0 based) | N/A | NO |
| perPage | int | GET | Returned items per page (default 50) | N/A | NO |
| sort | string | GET | Sort field (default id desc) | N/A | NO |
| moduleID | uint64 | PATH | Module ID | N/A | YES |
## List/read contents from module section