Full text profile search for the member directory

This commit is contained in:
Noah Petherbridge 2024-06-19 14:12:25 -07:00
parent 6ac121b345
commit 616f6ae76b
5 changed files with 68 additions and 14 deletions

View File

@ -68,6 +68,11 @@ var (
"music_movies", "music_movies",
"hide_age", "hide_age",
} }
EssayProfileFields = []string{
"about_me",
"interests",
"music_movies",
}
// Site preference names (stored in ProfileField table) // Site preference names (stored in ProfileField table)
SitePreferenceFields = []string{ SitePreferenceFields = []string{

View File

@ -32,7 +32,8 @@ func Search() http.HandlerFunc {
// Search filters. // Search filters.
var ( var (
isCertified = r.FormValue("certified") isCertified = r.FormValue("certified")
username = r.FormValue("name") // username search username = r.FormValue("name") // username search
searchTerm = r.FormValue("search") // profile text search
gender = r.FormValue("gender") gender = r.FormValue("gender")
orientation = r.FormValue("orientation") orientation = r.FormValue("orientation")
maritalStatus = r.FormValue("marital_status") maritalStatus = r.FormValue("marital_status")
@ -48,6 +49,8 @@ func Search() http.HandlerFunc {
ageMin, ageMax = ageMax, ageMin ageMin, ageMax = ageMax, ageMin
} }
search := models.ParseSearchString(searchTerm)
// Get current user. // Get current user.
currentUser, err := session.CurrentUser(r) currentUser, err := session.CurrentUser(r)
if err != nil { if err != nil {
@ -91,6 +94,7 @@ func Search() http.HandlerFunc {
Orientation: orientation, Orientation: orientation,
MaritalStatus: maritalStatus, MaritalStatus: maritalStatus,
HereFor: hereFor, HereFor: hereFor,
ProfileText: search,
Certified: isCertified == "true", Certified: isCertified == "true",
NotCertified: isCertified == "false", NotCertified: isCertified == "false",
InnerCircle: isCertified == "circle", InnerCircle: isCertified == "circle",
@ -127,6 +131,7 @@ func Search() http.HandlerFunc {
"MaritalStatus": maritalStatus, "MaritalStatus": maritalStatus,
"HereFor": hereFor, "HereFor": hereFor,
"EmailOrUsername": username, "EmailOrUsername": username,
"Search": searchTerm,
"AgeMin": ageMin, "AgeMin": ageMin,
"AgeMax": ageMax, "AgeMax": ageMax,
"FriendSearch": friendSearch, "FriendSearch": friendSearch,

View File

@ -258,6 +258,7 @@ type UserSearch struct {
Orientation string Orientation string
MaritalStatus string MaritalStatus string
HereFor string HereFor string
ProfileText *Search
Certified bool Certified bool
NotCertified bool NotCertified bool
InnerCircle bool InnerCircle bool
@ -364,6 +365,30 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
placeholders = append(placeholders, "here_for", "%"+search.HereFor+"%") placeholders = append(placeholders, "here_for", "%"+search.HereFor+"%")
} }
// Profile text search.
if terms := search.ProfileText; terms != nil {
for _, term := range terms.Includes {
var ilike = "%" + strings.ToLower(term) + "%"
wheres = append(wheres, `
EXISTS (
SELECT 1 FROM profile_fields
WHERE user_id = users.id AND name IN ? AND value ILIKE ?
)
`)
placeholders = append(placeholders, config.EssayProfileFields, ilike)
}
for _, term := range terms.Excludes {
var ilike = "%" + strings.ToLower(term) + "%"
wheres = append(wheres, `
NOT EXISTS (
SELECT 1 FROM profile_fields
WHERE user_id = users.id AND name IN ? AND value ILIKE ?
)
`)
placeholders = append(placeholders, config.EssayProfileFields, ilike)
}
}
// Only admin user can show disabled/banned users. // Only admin user can show disabled/banned users.
var statuses = []string{} var statuses = []string{}
if user.HasAdminScope(config.ScopeUserBan) { if user.HasAdminScope(config.ScopeUserBan) {

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/log" "code.nonshy.com/nonshy/website/pkg/log"
) )
@ -55,7 +56,7 @@ func CountNotesAboutUser(currentUser *User, user *User) int64 {
count int64 count int64
) )
if currentUser.IsAdmin { if currentUser.HasAdminScope(config.ScopeUserNotes) {
wheres = append(wheres, "about_user_id = ?") wheres = append(wheres, "about_user_id = ?")
placeholders = append(placeholders, user.ID) placeholders = append(placeholders, user.ID)
} else { } else {

View File

@ -87,9 +87,9 @@
<div class="column pr-1"> <div class="column pr-1">
<div class="field"> <div class="field">
<label class="label">Status:</label> <label class="label" for="certified">Status:</label>
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select id="certified" name="certified"> <select id="certified" name="certified" id="certified">
<optgroup label="Certification Status"> <optgroup label="Certification Status">
<option value="true">Only certified users</option> <option value="true">Only certified users</option>
<option value="false"{{if eq $Root.Certified "false"}} selected{{end}}>Non-certified only</option> <option value="false"{{if eq $Root.Certified "false"}} selected{{end}}>Non-certified only</option>
@ -115,15 +115,33 @@
<div class="column px-1"> <div class="column px-1">
<div class="field"> <div class="field">
<label class="label">Name or username:</label> <label class="label" for="name">Name or username:</label>
<input type="text" class="input" <input type="text" class="input"
name="name" name="name" id="name"
autocomplete="off" autocomplete="off"
value="{{$Root.EmailOrUsername}}"> value="{{$Root.EmailOrUsername}}">
</div> </div>
</div> </div>
<div class="column px-1"> <div class="column is-half pl-1">
<div class="field">
<label class="label" for="search">Profile text:</label>
<input type="text" class="input"
name="search" id="search"
autocomplete="off"
value="{{$Root.Search}}">
<p class="help">
Tip: you can <span class="has-text-success">"quote exact phrases"</span> and
<span class="has-text-success">-exclude</span> words (or
<span class="has-text-success">-"exclude phrases"</span>) from your search.
</p>
</div>
</div>
</div>
<div class="columns is-centered">
<div class="column pr-1">
<div class="field"> <div class="field">
<label class="label">Age:</label> <label class="label">Age:</label>
<div class="columns is-mobile is-gapless"> <div class="columns is-mobile is-gapless">
@ -151,7 +169,7 @@
</div> </div>
</div> </div>
<div class="column pl-1"> <div class="column px-1">
<div class="field"> <div class="field">
<label class="label" for="gender">Gender:</label> <label class="label" for="gender">Gender:</label>
<div class="select is-fullwidth"> <div class="select is-fullwidth">
@ -165,10 +183,7 @@
</div> </div>
</div> </div>
</div> <div class="column px-1">
<div class="columns is-centered">
<div class="column pr-1">
<div class="field"> <div class="field">
<label class="label" for="orientation">Orientation:</label> <label class="label" for="orientation">Orientation:</label>
<div class="select is-fullwidth"> <div class="select is-fullwidth">
@ -182,7 +197,7 @@
</div> </div>
</div> </div>
<div class="column px-1"> <div class="column pl-1">
<div class="field"> <div class="field">
<label class="label" for="marital_status">Relationship:</label> <label class="label" for="marital_status">Relationship:</label>
<div class="select is-fullwidth"> <div class="select is-fullwidth">
@ -196,7 +211,10 @@
</div> </div>
</div> </div>
<div class="column px-1"> </div>
<div class="columns is-centered">
<div class="column pr-1">
<div class="field"> <div class="field">
<label class="label" for="here_for">Here for:</label> <label class="label" for="here_for">Here for:</label>
<div class="select is-fullwidth"> <div class="select is-fullwidth">