got Find Users to work
This commit is contained in:
@@ -139,6 +139,18 @@ const (
|
|||||||
UserFlagMassMailOptOut = uint(2)
|
UserFlagMassMailOptOut = uint(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Selectors for field and operator in user search.
|
||||||
|
const (
|
||||||
|
SearchUserFieldName = 0
|
||||||
|
SearchUserFieldDescription = 1
|
||||||
|
SearchUserFieldFirstName = 2
|
||||||
|
SearchUserFieldLastName = 3
|
||||||
|
|
||||||
|
SearchUserOperPrefix = 0
|
||||||
|
SearchUserOperSubstring = 1
|
||||||
|
SearchUserOperRegex = 2
|
||||||
|
)
|
||||||
|
|
||||||
// userCache is the cache for User objects.
|
// userCache is the cache for User objects.
|
||||||
var userCache *lru.TwoQueueCache = nil
|
var userCache *lru.TwoQueueCache = nil
|
||||||
|
|
||||||
@@ -175,6 +187,15 @@ func (u *User) ContactInfo() (*ContactInfo, error) {
|
|||||||
return AmGetContactInfo(u.ContactID)
|
return AmGetContactInfo(u.ContactID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContactInfo returns the contact info structure for the user, quietly.
|
||||||
|
func (u *User) ContactInfoQ() *ContactInfo {
|
||||||
|
if u.ContactID < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ci, _ := AmGetContactInfo(u.ContactID)
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
|
||||||
// SetContactID sets the contact ID of a user.
|
// SetContactID sets the contact ID of a user.
|
||||||
func (u *User) SetContactID(cid int32) error {
|
func (u *User) SetContactID(cid int32) error {
|
||||||
u.Mutex.Lock()
|
u.Mutex.Lock()
|
||||||
@@ -748,3 +769,87 @@ func AmSetUserProperty(uid int32, ndx int32, val *string) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AmSearchUsers searches for users matching certain criteria.
|
||||||
|
* Parameters:
|
||||||
|
* field - A value indicating which field to search:
|
||||||
|
* SearchUserFieldName - The user name.
|
||||||
|
* SearchUserFieldDescription - The user description.
|
||||||
|
* SearchUserFieldFirstName - The user's first name.
|
||||||
|
* SearchUserFieldLastName - The user's last name.
|
||||||
|
* oper - The operation to perform on the search field:
|
||||||
|
* SearchUserOperPrefix - The specified field has the string "term" as a prefix.
|
||||||
|
* SearchUserOperSubstring - The specified field contains the string "term".
|
||||||
|
* SearchUserOperRegex - The specified field matches the regular expression in "term".
|
||||||
|
* term - The search term, as specified above.
|
||||||
|
* offset - Number of communities to skip at beginning of list.
|
||||||
|
* max - Maximum number of communities to return.
|
||||||
|
* Returns:
|
||||||
|
* Array of User pointers representing the return elements.
|
||||||
|
* The total number of users matching this query (could be greater than max)
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmSearchUsers(field int, oper int, term string, offset int, max int) ([]*User, int, error) {
|
||||||
|
var queryPortion strings.Builder
|
||||||
|
switch field {
|
||||||
|
case SearchUserFieldName:
|
||||||
|
queryPortion.WriteString("u.username ")
|
||||||
|
case SearchUserFieldDescription:
|
||||||
|
queryPortion.WriteString("u.description ")
|
||||||
|
case SearchUserFieldFirstName:
|
||||||
|
queryPortion.WriteString("c.given_name ")
|
||||||
|
case SearchUserFieldLastName:
|
||||||
|
queryPortion.WriteString("c.family_name ")
|
||||||
|
default:
|
||||||
|
return nil, -1, errors.New("invalid field selector")
|
||||||
|
}
|
||||||
|
switch oper {
|
||||||
|
case SearchUserOperPrefix:
|
||||||
|
queryPortion.WriteString("LIKE '")
|
||||||
|
queryPortion.WriteString(util.SqlEscape(term, true))
|
||||||
|
queryPortion.WriteString("%'")
|
||||||
|
case SearchUserOperSubstring:
|
||||||
|
queryPortion.WriteString("LIKE '%")
|
||||||
|
queryPortion.WriteString(util.SqlEscape(term, true))
|
||||||
|
queryPortion.WriteString("%'")
|
||||||
|
case SearchUserOperRegex:
|
||||||
|
queryPortion.WriteString("REGEXP '")
|
||||||
|
queryPortion.WriteString(util.SqlEscape(term, false))
|
||||||
|
queryPortion.WriteString("'")
|
||||||
|
default:
|
||||||
|
return nil, -1, errors.New("invalid operator selector")
|
||||||
|
}
|
||||||
|
q := queryPortion.String()
|
||||||
|
rs, err := amdb.Query("SELECT COUNT(*) FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND " + q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
if !rs.Next() {
|
||||||
|
return nil, -1, errors.New("internal error getting count")
|
||||||
|
}
|
||||||
|
var total int
|
||||||
|
rs.Scan(&total)
|
||||||
|
if total == 0 {
|
||||||
|
return make([]*User, 0), 0, nil
|
||||||
|
}
|
||||||
|
if offset > 0 {
|
||||||
|
rs, err = amdb.Query("SELECT u.uid FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q+
|
||||||
|
" ORDER BY u.username LIMIT ? OFFSET ?", max, offset)
|
||||||
|
} else {
|
||||||
|
rs, err = amdb.Query("SELECT u.uid FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q+
|
||||||
|
" ORDER BY u.username LIMIT ?", max)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, total, err
|
||||||
|
}
|
||||||
|
rc := make([]*User, 0, min(max, 10000))
|
||||||
|
for rs.Next() {
|
||||||
|
var uid int32
|
||||||
|
rs.Scan(&uid)
|
||||||
|
u, err := AmGetUser(uid)
|
||||||
|
if err == nil {
|
||||||
|
rc = append(rc, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc, total, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -218,7 +218,41 @@ func Find(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "USR":
|
case "USR":
|
||||||
// TODO
|
var iField, iOper int
|
||||||
|
switch field {
|
||||||
|
case "name":
|
||||||
|
iField = database.SearchUserFieldName
|
||||||
|
case "descr":
|
||||||
|
iField = database.SearchUserFieldDescription
|
||||||
|
case "first":
|
||||||
|
iField = database.SearchUserFieldFirstName
|
||||||
|
case "last":
|
||||||
|
iField = database.SearchUserFieldLastName
|
||||||
|
default:
|
||||||
|
ctxt.VarMap().Set("errorMessage", "invalid parameter to find")
|
||||||
|
return "framed_template", "find.jet", nil
|
||||||
|
}
|
||||||
|
switch oper {
|
||||||
|
case "st":
|
||||||
|
iOper = database.SearchUserOperPrefix
|
||||||
|
case "in":
|
||||||
|
iOper = database.SearchUserOperSubstring
|
||||||
|
case "re":
|
||||||
|
iOper = database.SearchUserOperRegex
|
||||||
|
default:
|
||||||
|
ctxt.VarMap().Set("errorMessage", "invalid parameter to find")
|
||||||
|
return "framed_template", "find.jet", nil
|
||||||
|
}
|
||||||
|
var ulist []*database.User
|
||||||
|
ulist, total, err = database.AmSearchUsers(iField, iOper, term, ofs*listMax, listMax)
|
||||||
|
if err == nil {
|
||||||
|
if ulist == nil {
|
||||||
|
numResults = 0
|
||||||
|
} else {
|
||||||
|
numResults = len(ulist)
|
||||||
|
ctxt.VarMap().Set("resultList", ulist)
|
||||||
|
}
|
||||||
|
}
|
||||||
case "CAT":
|
case "CAT":
|
||||||
// TODO
|
// TODO
|
||||||
case "PST":
|
case "PST":
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
@@ -137,6 +138,32 @@ func displayMemberCount(a jet.Arguments) reflect.Value {
|
|||||||
return reflect.ValueOf(count)
|
return reflect.ValueOf(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayFullName(a jet.Arguments) reflect.Value {
|
||||||
|
ci := a.Get(0).Convert(reflect.TypeFor[*database.ContactInfo]()).Interface().(*database.ContactInfo)
|
||||||
|
var rc strings.Builder
|
||||||
|
if ci.Prefix != nil && *ci.Prefix != "" {
|
||||||
|
rc.WriteString(*ci.Prefix)
|
||||||
|
rc.WriteString(" ")
|
||||||
|
}
|
||||||
|
if ci.GivenName != nil && *ci.GivenName != "" {
|
||||||
|
rc.WriteString(*ci.GivenName)
|
||||||
|
}
|
||||||
|
if ci.MiddleInit != nil && *ci.MiddleInit != "" {
|
||||||
|
rc.WriteString(" ")
|
||||||
|
rc.WriteString(*ci.MiddleInit)
|
||||||
|
rc.WriteString(".")
|
||||||
|
}
|
||||||
|
if ci.FamilyName != nil && *ci.FamilyName != "" {
|
||||||
|
rc.WriteString(" ")
|
||||||
|
rc.WriteString(*ci.FamilyName)
|
||||||
|
}
|
||||||
|
if ci.Suffix != nil && *ci.Suffix != "" {
|
||||||
|
rc.WriteString(" ")
|
||||||
|
rc.WriteString(*ci.Suffix)
|
||||||
|
}
|
||||||
|
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(
|
||||||
@@ -154,6 +181,7 @@ func SetupTemplates() {
|
|||||||
views.AddGlobalFunc("ExtractCommunityLogo", extractCommunityLogo)
|
views.AddGlobalFunc("ExtractCommunityLogo", extractCommunityLogo)
|
||||||
views.AddGlobalFunc("DisplayActivity", displayActivity)
|
views.AddGlobalFunc("DisplayActivity", displayActivity)
|
||||||
views.AddGlobalFunc("DisplayMemberCount", displayMemberCount)
|
views.AddGlobalFunc("DisplayMemberCount", displayMemberCount)
|
||||||
|
views.AddGlobalFunc("DisplayFullName", displayFullName)
|
||||||
|
|
||||||
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())
|
||||||
|
|||||||
+16
-1
@@ -235,7 +235,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ else if mode == "USR" }}
|
{{ else if mode == "USR" }}
|
||||||
TODO: I don't know USR yet
|
<!-- User Result -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-sm pt-0.5 flex-shrink-0">🟣</span>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="mb-2">
|
||||||
|
<a href="/user/{{ rx.Username }}"
|
||||||
|
class="text-blue-700 hover:text-blue-900 font-bold text-base">{{ rx.Username }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700 space-y-1">
|
||||||
|
<div>
|
||||||
|
{{ ci := rx.ContactInfoQ() }}
|
||||||
|
{{ DisplayFullName(ci) }}, from {{ ci.Locality }}, {{ ci.Region }} {{ ci.Country }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ else if mode == "CAT" }}
|
{{ else if mode == "CAT" }}
|
||||||
TODO: I don't know CAT yet
|
TODO: I don't know CAT yet
|
||||||
{{ else if mode == "PST" }}
|
{{ else if mode == "PST" }}
|
||||||
|
|||||||
Reference in New Issue
Block a user