got Find Categories to work
This commit is contained in:
+125
-3
@@ -14,6 +14,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Category is the structure defining a category.
|
// Category is the structure defining a category.
|
||||||
@@ -26,6 +28,13 @@ type Category struct {
|
|||||||
Name string `db:"name"`
|
Name string `db:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Selectors for operator in category search.
|
||||||
|
const (
|
||||||
|
SearchCatOperPrefix = 0
|
||||||
|
SearchCatOperSubstring = 1
|
||||||
|
SearchCatOperRegex = 2
|
||||||
|
)
|
||||||
|
|
||||||
// allCategories is the list of all categories loaded from the database.
|
// allCategories is the list of all categories loaded from the database.
|
||||||
var allCategories []Category
|
var allCategories []Category
|
||||||
|
|
||||||
@@ -35,6 +44,19 @@ var categoryIdMap map[int32]*Category = make(map[int32]*Category)
|
|||||||
// categoryMutex syncs the loading of the categories.
|
// categoryMutex syncs the loading of the categories.
|
||||||
var categoryMutex sync.Mutex
|
var categoryMutex sync.Mutex
|
||||||
|
|
||||||
|
// isCatEnabled determines if category features are enabled.
|
||||||
|
func isCatEnabled() (bool, error) {
|
||||||
|
g, err := AmGlobals()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
set, err := g.Flags()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !set.Get(GlobalFlagNoCategories), nil
|
||||||
|
}
|
||||||
|
|
||||||
// loadCategories loads the categories list from the database.
|
// loadCategories loads the categories list from the database.
|
||||||
func loadCategories() error {
|
func loadCategories() error {
|
||||||
categoryMutex.Lock()
|
categoryMutex.Lock()
|
||||||
@@ -69,7 +91,14 @@ func loadCategories() error {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmGetCategory(catid int32) (*Category, error) {
|
func AmGetCategory(catid int32) (*Category, error) {
|
||||||
err := loadCategories()
|
ok, err := isCatEnabled()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("category feature not supported")
|
||||||
|
}
|
||||||
|
err = loadCategories()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -93,7 +122,14 @@ func AmGetCategory(catid int32) (*Category, error) {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmGetCategoryHierarchy(catid int32) ([]*Category, error) {
|
func AmGetCategoryHierarchy(catid int32) ([]*Category, error) {
|
||||||
err := loadCategories()
|
ok, err := isCatEnabled()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("category feature not supported")
|
||||||
|
}
|
||||||
|
err = loadCategories()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -124,7 +160,14 @@ func AmGetCategoryHierarchy(catid int32) ([]*Category, error) {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmGetSubCategories(catid int32) ([]*Category, error) {
|
func AmGetSubCategories(catid int32) ([]*Category, error) {
|
||||||
err := loadCategories()
|
ok, err := isCatEnabled()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("category feature not supported")
|
||||||
|
}
|
||||||
|
err = loadCategories()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -139,3 +182,82 @@ func AmGetSubCategories(catid int32) ([]*Category, error) {
|
|||||||
})
|
})
|
||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AmSearchCategories searches for categories matching certain criteria.
|
||||||
|
* Parameters:
|
||||||
|
* oper - The operation to perform on the category name:
|
||||||
|
* SearchCatOperPrefix - The category name has the string "term" as a prefix.
|
||||||
|
* SearchCatOperSubstring - The category name contains the string "term".
|
||||||
|
* SearchCatOperRegex - The category name matches the regular expression in "term".
|
||||||
|
* term - The search term, as specified above.
|
||||||
|
* offset - Number of categories to skip at beginning of list.
|
||||||
|
* max - Maximum number of categories to return.
|
||||||
|
* Returns:
|
||||||
|
* Array of Category pointers representing the return elements.
|
||||||
|
* The total number of categories matching this query (could be greater than max)
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmSearchCategories(oper int, term string, offset int, max int, showAll bool, searchAll bool) ([]*Category, int, error) {
|
||||||
|
ok, err := isCatEnabled()
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, -1, errors.New("category feature not supported")
|
||||||
|
}
|
||||||
|
var queryString strings.Builder
|
||||||
|
queryString.WriteString("name ")
|
||||||
|
switch oper {
|
||||||
|
case SearchCatOperPrefix:
|
||||||
|
queryString.WriteString("LIKE '")
|
||||||
|
queryString.WriteString(util.SqlEscape(term, true))
|
||||||
|
queryString.WriteString("%'")
|
||||||
|
case SearchCatOperSubstring:
|
||||||
|
queryString.WriteString("LIKE '%")
|
||||||
|
queryString.WriteString(util.SqlEscape(term, true))
|
||||||
|
queryString.WriteString("%'")
|
||||||
|
case SearchCatOperRegex:
|
||||||
|
queryString.WriteString("REGEXP '")
|
||||||
|
queryString.WriteString(util.SqlEscape(term, false))
|
||||||
|
queryString.WriteString("'")
|
||||||
|
default:
|
||||||
|
return nil, -1, errors.New("invalid operator to search function")
|
||||||
|
}
|
||||||
|
if !showAll {
|
||||||
|
queryString.WriteString(" AND hide_dir = 0")
|
||||||
|
}
|
||||||
|
if !searchAll {
|
||||||
|
queryString.WriteString(" AND hide_search = 0")
|
||||||
|
}
|
||||||
|
q := queryString.String()
|
||||||
|
rs, err := amdb.Query("SELECT COUNT(*) FROM refcategory WHERE " + q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
if !rs.Next() {
|
||||||
|
return nil, -1, errors.New("internal error getting category total")
|
||||||
|
}
|
||||||
|
var total int
|
||||||
|
rs.Scan(&total)
|
||||||
|
if total == 0 {
|
||||||
|
return make([]*Category, 0), 0, nil
|
||||||
|
}
|
||||||
|
if offset > 0 {
|
||||||
|
rs, err = amdb.Query("SELECT catid FROM refcategory WHERE "+q+" ORDER BY parent, name LIMIT ? OFFSET ?", max, offset)
|
||||||
|
} else {
|
||||||
|
rs, err = amdb.Query("SELECT catid FROM refcategory WHERE "+q+" ORDER BY parent, name LIMIT ?", max)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, total, err
|
||||||
|
}
|
||||||
|
rc := make([]*Category, 0, min(max, 1000))
|
||||||
|
for rs.Next() {
|
||||||
|
var catid int32
|
||||||
|
rs.Scan(&catid)
|
||||||
|
c, err := AmGetCategory(catid)
|
||||||
|
if err == nil {
|
||||||
|
rc = append(rc, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc, total, nil
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -782,8 +782,8 @@ func AmSetUserProperty(uid int32, ndx int32, val *string) error {
|
|||||||
* SearchUserOperSubstring - The specified field contains the string "term".
|
* SearchUserOperSubstring - The specified field contains the string "term".
|
||||||
* SearchUserOperRegex - The specified field matches the regular expression in "term".
|
* SearchUserOperRegex - The specified field matches the regular expression in "term".
|
||||||
* term - The search term, as specified above.
|
* term - The search term, as specified above.
|
||||||
* offset - Number of communities to skip at beginning of list.
|
* offset - Number of users to skip at beginning of list.
|
||||||
* max - Maximum number of communities to return.
|
* max - Maximum number of users to return.
|
||||||
* Returns:
|
* Returns:
|
||||||
* Array of User pointers representing the return elements.
|
* Array of User pointers representing the return elements.
|
||||||
* The total number of users matching this query (could be greater than max)
|
* The total number of users matching this query (could be greater than max)
|
||||||
|
|||||||
@@ -254,7 +254,30 @@ func Find(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "CAT":
|
case "CAT":
|
||||||
// TODO
|
listMax = 20
|
||||||
|
var iOper int
|
||||||
|
switch oper {
|
||||||
|
case "st":
|
||||||
|
iOper = database.SearchCatOperPrefix
|
||||||
|
case "in":
|
||||||
|
iOper = database.SearchCatOperSubstring
|
||||||
|
case "re":
|
||||||
|
iOper = database.SearchCatOperRegex
|
||||||
|
default:
|
||||||
|
ctxt.VarMap().Set("errorMessage", "invalid parameter to find")
|
||||||
|
return "framed_template", "find.jet", nil
|
||||||
|
}
|
||||||
|
var catlist []*database.Category
|
||||||
|
catlist, total, err = database.AmSearchCategories(iOper, term, ofs*listMax, listMax,
|
||||||
|
ctxt.TestPermission("Global.ShowHiddenCategories"), ctxt.TestPermission("Global.SearchHiddenCategories"))
|
||||||
|
if err == nil {
|
||||||
|
if catlist == nil {
|
||||||
|
numResults = 0
|
||||||
|
} else {
|
||||||
|
numResults = len(catlist)
|
||||||
|
ctxt.VarMap().Set("resultList", catlist)
|
||||||
|
}
|
||||||
|
}
|
||||||
case "PST":
|
case "PST":
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,19 @@ func displayFullName(a jet.Arguments) reflect.Value {
|
|||||||
return reflect.ValueOf(rc.String())
|
return reflect.ValueOf(rc.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayExpandCat(a jet.Arguments) reflect.Value {
|
||||||
|
cat := a.Get(0).Convert(reflect.TypeFor[*database.Category]()).Interface().(*database.Category)
|
||||||
|
hier, _ := database.AmGetCategoryHierarchy(cat.CatId)
|
||||||
|
var rc strings.Builder
|
||||||
|
for i, c := range hier {
|
||||||
|
if i > 0 {
|
||||||
|
rc.WriteString(": ")
|
||||||
|
}
|
||||||
|
rc.WriteString(c.Name)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(rc.String())
|
||||||
|
}
|
||||||
|
|
||||||
// SetupTemplates is called to set up the template renderer after the configuration is loaded.
|
// SetupTemplates is called to set up the template renderer after the configuration is loaded.
|
||||||
func SetupTemplates() {
|
func SetupTemplates() {
|
||||||
views = jet.NewSet(
|
views = jet.NewSet(
|
||||||
@@ -182,6 +195,7 @@ func SetupTemplates() {
|
|||||||
views.AddGlobalFunc("DisplayActivity", displayActivity)
|
views.AddGlobalFunc("DisplayActivity", displayActivity)
|
||||||
views.AddGlobalFunc("DisplayMemberCount", displayMemberCount)
|
views.AddGlobalFunc("DisplayMemberCount", displayMemberCount)
|
||||||
views.AddGlobalFunc("DisplayFullName", displayFullName)
|
views.AddGlobalFunc("DisplayFullName", displayFullName)
|
||||||
|
views.AddGlobalFunc("DisplayExpandCat", displayExpandCat)
|
||||||
|
|
||||||
views.AddGlobalFunc("GetCountryList", func(jet.Arguments) reflect.Value {
|
views.AddGlobalFunc("GetCountryList", func(jet.Arguments) reflect.Value {
|
||||||
return reflect.ValueOf(util.AmCountryList())
|
return reflect.ValueOf(util.AmCountryList())
|
||||||
|
|||||||
+8
-1
@@ -252,7 +252,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ else if mode == "CAT" }}
|
{{ else if mode == "CAT" }}
|
||||||
TODO: I don't know CAT yet
|
<!-- Category Result -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-sm pt-0.5 flex-shrink-0">🟣</span>
|
||||||
|
<div class="flex-1 mb-2">
|
||||||
|
<a href="/find?mode=COM&catid={{ rx.CatId }}"
|
||||||
|
class="text-blue-700 hover:text-blue-900 font-bold text-base">{{ DisplayExpandCat(rx) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ else if mode == "PST" }}
|
{{ else if mode == "PST" }}
|
||||||
TODO: I don't know PST yet
|
TODO: I don't know PST yet
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user