New search filters and friendship sent icon
This commit is contained in:
parent
c8d09e6a17
commit
67a54c866e
|
@ -36,6 +36,7 @@ func Search() http.HandlerFunc {
|
||||||
orientation = r.FormValue("orientation")
|
orientation = r.FormValue("orientation")
|
||||||
maritalStatus = r.FormValue("marital_status")
|
maritalStatus = r.FormValue("marital_status")
|
||||||
hereFor = r.FormValue("here_for")
|
hereFor = r.FormValue("here_for")
|
||||||
|
friendSearch = r.FormValue("friends") == "true"
|
||||||
sort = r.FormValue("sort")
|
sort = r.FormValue("sort")
|
||||||
sortOK bool
|
sortOK bool
|
||||||
ageMin int
|
ageMin int
|
||||||
|
@ -93,6 +94,8 @@ func Search() http.HandlerFunc {
|
||||||
HereFor: hereFor,
|
HereFor: hereFor,
|
||||||
Certified: isCertified != "false",
|
Certified: isCertified != "false",
|
||||||
InnerCircle: isCertified == "circle",
|
InnerCircle: isCertified == "circle",
|
||||||
|
ShyAccounts: isCertified == "shy",
|
||||||
|
Friends: friendSearch,
|
||||||
AgeMin: ageMin,
|
AgeMin: ageMin,
|
||||||
AgeMax: ageMax,
|
AgeMax: ageMax,
|
||||||
}, pager)
|
}, pager)
|
||||||
|
@ -117,11 +120,18 @@ func Search() http.HandlerFunc {
|
||||||
"EmailOrUsername": username,
|
"EmailOrUsername": username,
|
||||||
"AgeMin": ageMin,
|
"AgeMin": ageMin,
|
||||||
"AgeMax": ageMax,
|
"AgeMax": ageMax,
|
||||||
|
"FriendSearch": friendSearch,
|
||||||
"Sort": sort,
|
"Sort": sort,
|
||||||
|
|
||||||
// Photo counts mapped to users
|
// Photo counts mapped to users
|
||||||
"PhotoCountMap": models.MapPhotoCounts(users),
|
"PhotoCountMap": models.MapPhotoCounts(users),
|
||||||
|
|
||||||
|
// Map Shy Account badges for these results
|
||||||
|
"ShyMap": models.MapShyAccounts(users),
|
||||||
|
|
||||||
|
// Map friendships to these users.
|
||||||
|
"FriendMap": models.MapFriends(currentUser, users),
|
||||||
|
|
||||||
// Current user's location setting.
|
// Current user's location setting.
|
||||||
"MyLocation": myLocation,
|
"MyLocation": myLocation,
|
||||||
"GeoIPInsights": insights,
|
"GeoIPInsights": insights,
|
||||||
|
|
|
@ -297,3 +297,45 @@ func (f *Friend) Save() error {
|
||||||
result := DB.Save(f)
|
result := DB.Save(f)
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FriendMap maps user IDs to friendship status for the current user.
|
||||||
|
type FriendMap map[uint64]bool
|
||||||
|
|
||||||
|
// MapFriends looks up a set of user IDs in bulk and returns a FriendMap suitable for templates.
|
||||||
|
func MapFriends(currentUser *User, users []*User) FriendMap {
|
||||||
|
var (
|
||||||
|
usermap = FriendMap{}
|
||||||
|
set = map[uint64]interface{}{}
|
||||||
|
distinct = []uint64{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Uniqueify users.
|
||||||
|
for _, user := range users {
|
||||||
|
if _, ok := set[user.ID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
set[user.ID] = nil
|
||||||
|
distinct = append(distinct, user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
matched = []*Friend{}
|
||||||
|
result = DB.Model(&Friend{}).Where(
|
||||||
|
"source_user_id = ? AND target_user_id IN ? AND approved = ?",
|
||||||
|
currentUser.ID, distinct, true,
|
||||||
|
).Find(&matched)
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.Error == nil {
|
||||||
|
for _, row := range matched {
|
||||||
|
usermap[row.TargetUserID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return usermap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a user from the FriendMap.
|
||||||
|
func (um FriendMap) Get(id uint64) bool {
|
||||||
|
return um[id]
|
||||||
|
}
|
||||||
|
|
98
pkg/models/shy_accounts.go
Normal file
98
pkg/models/shy_accounts.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
// Supplementary functions to do with Shy Accounts.
|
||||||
|
|
||||||
|
import "code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
|
||||||
|
// IsShy returns whether the user might have an "empty" profile from the perspective of anybody.
|
||||||
|
//
|
||||||
|
// An empty profile means their profile is Private or else ALL of their photos are non-public; so that
|
||||||
|
// somebody viewing their page might see nothing at all from them and consider them a "blank" profile.
|
||||||
|
func (u *User) IsShy() bool {
|
||||||
|
// NOTE: if you change the logic for Shy Accounts, also align your changes
|
||||||
|
// in the functions: WhereClauseShyAccounts.
|
||||||
|
|
||||||
|
// Non-certified users are considered empty.
|
||||||
|
if !u.Certified {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private profile automatically applies.
|
||||||
|
if u.Visibility == UserVisibilityPrivate {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ALL of our photos are non-public, that counts too.
|
||||||
|
var photoTypes = u.DistinctPhotoTypes()
|
||||||
|
if _, ok := photoTypes[PhotoPublic]; !ok {
|
||||||
|
log.Info("IsEmptyProfile: true because visibilities %+v did not include public", photoTypes)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhereClauseShyAccounts returns SQL query fragments when running User table queries
|
||||||
|
// that will filter for shy accounts.
|
||||||
|
//
|
||||||
|
// This is used by SearchUsers and MapShyAccounts.
|
||||||
|
func WhereClauseShyAccounts() (where string, placeholders []interface{}) {
|
||||||
|
where = `(
|
||||||
|
certified IS NOT true
|
||||||
|
OR visibility = ?
|
||||||
|
OR NOT EXISTS (
|
||||||
|
SELECT 1 FROM photos
|
||||||
|
WHERE user_id = users.id
|
||||||
|
AND visibility = ?
|
||||||
|
)
|
||||||
|
)`
|
||||||
|
placeholders = []interface{}{
|
||||||
|
UserVisibilityPrivate,
|
||||||
|
PhotoPublic,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShyMap maps user IDs to Shy Account status in bulk queries.
|
||||||
|
type ShyMap map[uint64]bool
|
||||||
|
|
||||||
|
// MapShyAccounts looks up a set of user IDs in bulk and returns a ShyMap suitable for templates.
|
||||||
|
func MapShyAccounts(users []*User) ShyMap {
|
||||||
|
var (
|
||||||
|
usermap = ShyMap{}
|
||||||
|
set = map[uint64]interface{}{}
|
||||||
|
distinct = []uint64{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Uniqueify users.
|
||||||
|
for _, user := range users {
|
||||||
|
if _, ok := set[user.ID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
set[user.ID] = nil
|
||||||
|
distinct = append(distinct, user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
matched = []*User{}
|
||||||
|
where, placeholders = WhereClauseShyAccounts()
|
||||||
|
result = (&User{}).
|
||||||
|
Preload().
|
||||||
|
Where("id IN ?", distinct).
|
||||||
|
Where(where, placeholders...).
|
||||||
|
Find(&matched)
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.Error == nil {
|
||||||
|
for _, row := range matched {
|
||||||
|
usermap[row.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return usermap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a user from the ShyMap.
|
||||||
|
func (um ShyMap) Get(id uint64) bool {
|
||||||
|
return um[id]
|
||||||
|
}
|
|
@ -139,31 +139,6 @@ func FindUser(username string) (*User, error) {
|
||||||
return u, result.Error
|
return u, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsShy returns whether the user might have an "empty" profile from the perspective of anybody.
|
|
||||||
//
|
|
||||||
// An empty profile means their profile is Private or else ALL of their photos are non-public; so that
|
|
||||||
// somebody viewing their page might see nothing at all from them and consider them a "blank" profile.
|
|
||||||
func (u *User) IsShy() bool {
|
|
||||||
// Non-certified users are considered empty.
|
|
||||||
if !u.Certified {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private profile automatically applies.
|
|
||||||
if u.Visibility == UserVisibilityPrivate {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ALL of our photos are non-public, that counts too.
|
|
||||||
var photoTypes = u.DistinctPhotoTypes()
|
|
||||||
if _, ok := photoTypes[PhotoPublic]; !ok {
|
|
||||||
log.Info("IsEmptyProfile: true because visibilities %+v did not include public", photoTypes)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsShyFrom tells whether the user is shy from the perspective of the other user.
|
// IsShyFrom tells whether the user is shy from the perspective of the other user.
|
||||||
//
|
//
|
||||||
// That is, depending on our profile visibility and friendship status.
|
// That is, depending on our profile visibility and friendship status.
|
||||||
|
@ -191,6 +166,8 @@ type UserSearch struct {
|
||||||
HereFor string
|
HereFor string
|
||||||
Certified bool
|
Certified bool
|
||||||
InnerCircle bool
|
InnerCircle bool
|
||||||
|
ShyAccounts bool
|
||||||
|
Friends bool
|
||||||
AgeMin int
|
AgeMin int
|
||||||
AgeMax int
|
AgeMax int
|
||||||
}
|
}
|
||||||
|
@ -289,6 +266,7 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
|
||||||
placeholders = append(placeholders, "here_for", "%"+search.HereFor+"%")
|
placeholders = append(placeholders, "here_for", "%"+search.HereFor+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Certified filter (including if Shy Accounts are asked for)
|
||||||
if search.Certified {
|
if search.Certified {
|
||||||
wheres = append(wheres, "certified = ?", "status = ?")
|
wheres = append(wheres, "certified = ?", "status = ?")
|
||||||
placeholders = append(placeholders, search.Certified, UserStatusActive)
|
placeholders = append(placeholders, search.Certified, UserStatusActive)
|
||||||
|
@ -299,6 +277,27 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
|
||||||
placeholders = append(placeholders, true, true)
|
placeholders = append(placeholders, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if search.ShyAccounts {
|
||||||
|
a, b := WhereClauseShyAccounts()
|
||||||
|
wheres = append(wheres, a)
|
||||||
|
placeholders = append(placeholders, b...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if search.Friends {
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM friends
|
||||||
|
WHERE source_user_id = ?
|
||||||
|
AND target_user_id = users.id
|
||||||
|
AND approved = ?
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
placeholders = append(placeholders,
|
||||||
|
user.ID,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if search.AgeMin > 0 {
|
if search.AgeMin > 0 {
|
||||||
date := time.Now().AddDate(-search.AgeMin, 0, 0)
|
date := time.Now().AddDate(-search.AgeMin, 0, 0)
|
||||||
wheres = append(wheres, "birthdate <= ?")
|
wheres = append(wheres, "birthdate <= ?")
|
||||||
|
|
|
@ -164,7 +164,7 @@
|
||||||
{{if eq .IsFriend "approved"}}
|
{{if eq .IsFriend "approved"}}
|
||||||
<i class="fa fa-check has-text-success"></i>
|
<i class="fa fa-check has-text-success"></i>
|
||||||
{{else if eq .IsFriend "pending"}}
|
{{else if eq .IsFriend "pending"}}
|
||||||
<i class="fa fa-spinner fa-spin"></i>
|
<i class="fa fa-paper-plane"></i>
|
||||||
{{else}}
|
{{else}}
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -29,7 +29,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if not (eq .Sort "distance")}}
|
{{if .FriendSearch}}
|
||||||
|
<div class="notification is-success is-light">
|
||||||
|
Currently searching within your <i class="fa fa-user-group"></i> Friends list.
|
||||||
|
</div>
|
||||||
|
{{else if not (eq .Sort "distance")}}
|
||||||
<div class="notification is-success is-light">
|
<div class="notification is-success is-light">
|
||||||
<strong>New feature:</strong> you can now see <strong>Who's Nearby!</strong>
|
<strong>New feature:</strong> you can now see <strong>Who's Nearby!</strong>
|
||||||
{{if not .MyLocation.Source}}
|
{{if not .MyLocation.Source}}
|
||||||
|
@ -72,22 +76,24 @@
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
|
||||||
<div class="column">
|
<div class="column pr-1">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Status:</label>
|
<label class="label">Status:</label>
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select id="certified" name="certified">
|
<select id="certified" name="certified">
|
||||||
<option value="true">Only certified users</option>
|
<option value="true">Only certified users</option>
|
||||||
|
<option value="friends"{{if eq $Root.Certified "friends"}} selected{{end}}>Friends only</option>
|
||||||
{{if .CurrentUser.IsInnerCircle}}
|
{{if .CurrentUser.IsInnerCircle}}
|
||||||
<option value="circle"{{if eq $Root.Certified "circle"}} selected{{end}}>Inner circle only</option>
|
<option value="circle"{{if eq $Root.Certified "circle"}} selected{{end}}>Inner circle only</option>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
<option value="shy"{{if eq $Root.Certified "shy"}} selected{{end}}>Shy Accounts</option>
|
||||||
<option value="false"{{if eq $Root.Certified "false"}} selected{{end}}>Show all users</option>
|
<option value="false"{{if eq $Root.Certified "false"}} selected{{end}}>Show all users</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column px-1">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Partial username:</label>
|
<label class="label">Partial username:</label>
|
||||||
<input type="text" class="input"
|
<input type="text" class="input"
|
||||||
|
@ -97,7 +103,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column px-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">
|
||||||
|
@ -125,7 +131,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column pl-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">
|
||||||
|
@ -142,7 +148,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="columns is-centered">
|
<div class="columns is-centered">
|
||||||
|
|
||||||
<div class="column">
|
<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">
|
||||||
|
@ -156,7 +162,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column px-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">
|
||||||
|
@ -170,7 +176,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column px-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">
|
||||||
|
@ -184,7 +190,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column px-1">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="friends">Friendship:</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="friends"
|
||||||
|
id="friends"
|
||||||
|
value="true"
|
||||||
|
{{if .FriendSearch}}checked{{end}}>
|
||||||
|
Show only my friends
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column px-1">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="sort">Sort by:</label>
|
<label class="label" for="sort">Sort by:</label>
|
||||||
<div class="select is-full-width">
|
<div class="select is-full-width">
|
||||||
|
@ -201,7 +221,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column has-text-right">
|
<div class="column pl-1 has-text-right">
|
||||||
<a href="/members" class="button">Reset</a>
|
<a href="/members" class="button">Reset</a>
|
||||||
<button type="submit" class="button is-success">
|
<button type="submit" class="button is-success">
|
||||||
<span>Search</span>
|
<span>Search</span>
|
||||||
|
@ -226,6 +246,16 @@
|
||||||
<div class="media block">
|
<div class="media block">
|
||||||
<div class="media-left">
|
<div class="media-left">
|
||||||
{{template "avatar-64x64" .}}
|
{{template "avatar-64x64" .}}
|
||||||
|
|
||||||
|
<!-- Friendship badge -->
|
||||||
|
{{if $Root.FriendMap.Get .ID}}
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<span class="is-size-7 has-text-warning-dark">
|
||||||
|
<i class="fa fa-user-group" title="Friends"></i>
|
||||||
|
Friends
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<p class="title is-4">
|
<p class="title is-4">
|
||||||
|
@ -246,11 +276,18 @@
|
||||||
<p class="subtitle is-6 mb-2">
|
<p class="subtitle is-6 mb-2">
|
||||||
<span class="icon"><i class="fa fa-user"></i></span>
|
<span class="icon"><i class="fa fa-user"></i></span>
|
||||||
<a href="/u/{{.Username}}">{{.Username}}</a>
|
<a href="/u/{{.Username}}">{{.Username}}</a>
|
||||||
|
|
||||||
|
<!-- Not Certified or Shy Account badge -->
|
||||||
{{if not .Certified}}
|
{{if not .Certified}}
|
||||||
<span class="has-text-danger is-size-7">
|
<span class="has-text-danger is-size-7">
|
||||||
<i class="fa fa-certificate"></i>
|
<i class="fa fa-certificate"></i>
|
||||||
<span>Not Certified!</span>
|
<span>Not Certified!</span>
|
||||||
</span>
|
</span>
|
||||||
|
{{else if $Root.ShyMap.Get .ID}}
|
||||||
|
<span class="has-text-danger is-size-7">
|
||||||
|
<i class="fa fa-ghost"></i>
|
||||||
|
<span>Shy Account</span>
|
||||||
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<!-- "(banned)" label -->
|
<!-- "(banned)" label -->
|
||||||
|
|
|
@ -40,6 +40,12 @@
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
|
|
||||||
|
<div class="notification is-success is-light">
|
||||||
|
<strong>New feature:</strong>
|
||||||
|
you can now <a href="/members?friends=true">search and sort</a> your friends list
|
||||||
|
in the <i class="fa fa-people-group"></i> Member Directory!
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{{if .IsPending}}
|
{{if .IsPending}}
|
||||||
You have sent {{.Pager.Total}} friend request{{Pluralize64 .Pager.Total}} which
|
You have sent {{.Pager.Total}} friend request{{Pluralize64 .Pager.Total}} which
|
||||||
|
@ -129,6 +135,10 @@
|
||||||
{{end}}<!-- range .Friends -->
|
{{end}}<!-- range .Friends -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
{{SimplePager .Pager}}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user