website/pkg/controller/account/search.go

246 lines
7.1 KiB
Go
Raw Permalink Normal View History

2022-08-14 05:44:57 +00:00
package account
import (
2024-07-13 19:05:36 +00:00
"fmt"
2022-08-14 05:44:57 +00:00
"net/http"
"strconv"
2022-08-14 05:44:57 +00:00
2022-08-26 04:21:46 +00:00
"code.nonshy.com/nonshy/website/pkg/config"
2023-08-20 04:09:23 +00:00
"code.nonshy.com/nonshy/website/pkg/geoip"
"code.nonshy.com/nonshy/website/pkg/log"
2022-08-26 04:21:46 +00:00
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/session"
2024-07-13 19:05:36 +00:00
"code.nonshy.com/nonshy/website/pkg/spam"
2022-08-26 04:21:46 +00:00
"code.nonshy.com/nonshy/website/pkg/templates"
2023-09-14 06:13:02 +00:00
"code.nonshy.com/nonshy/website/pkg/worker"
2022-08-14 05:44:57 +00:00
)
// Search controller.
func Search() http.HandlerFunc {
tmpl := templates.Must("account/search.html")
// Whitelist for ordering options.
var sortWhitelist = []string{
"last_login_at desc",
"created_at desc",
2024-08-24 06:09:27 +00:00
"certified_at desc",
"username",
2023-08-30 04:10:00 +00:00
"username desc",
"lower(name)",
2023-08-30 04:10:00 +00:00
"lower(name) desc",
2023-08-20 02:11:33 +00:00
"distance",
}
2022-08-14 05:44:57 +00:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Search filters.
var (
isCertified = r.FormValue("certified")
username = r.FormValue("name") // username search
searchTerm = r.FormValue("search") // profile text search
citySearch = r.FormValue("wcs")
2022-08-14 05:44:57 +00:00
gender = r.FormValue("gender")
orientation = r.FormValue("orientation")
maritalStatus = r.FormValue("marital_status")
2023-08-30 04:10:00 +00:00
hereFor = r.FormValue("here_for")
friendSearch = r.FormValue("friends") == "true"
2024-06-27 04:27:03 +00:00
likedSearch = r.FormValue("liked") == "true"
sort = r.FormValue("sort")
sortOK bool
2022-08-14 05:44:57 +00:00
)
2023-09-10 19:07:16 +00:00
ageMin, err1 := strconv.Atoi(r.FormValue("age_min"))
ageMax, err2 := strconv.Atoi(r.FormValue("age_max"))
if ageMin > ageMax && err1 == nil && err2 == nil {
ageMin, ageMax = ageMax, ageMin
}
2024-07-13 19:05:36 +00:00
rawSearch := models.ParseSearchString(searchTerm)
search, restricted := spam.RestrictSearchTerms(rawSearch)
// Get current user.
currentUser, err := session.CurrentUser(r)
if err != nil {
session.FlashError(w, r, "Couldn't get current user!")
templates.Redirect(w, "/")
return
}
2024-07-13 19:05:36 +00:00
// Report when search terms are restricted.
if restricted != nil {
// Admin users: allow the search anyway.
if currentUser.IsAdmin {
search = rawSearch
} else {
fb := &models.Feedback{
Intent: "report",
Subject: "Search Keyword Blacklist",
UserID: currentUser.ID,
TableName: "users",
TableID: currentUser.ID,
Message: fmt.Sprintf(
"A user has run a search on the Member Directory using search terms which are prohibited.\n\n"+
"Their search query was: %s",
searchTerm,
),
}
// Save the feedback.
if err := models.CreateFeedback(fb); err != nil {
log.Error("Couldn't save feedback from user updating their DOB: %s", err)
}
}
}
2023-08-20 04:09:23 +00:00
// Geolocation/Who's Nearby: if the current user uses GeoIP, update
// their coordinates now.
myLocation, err := models.RefreshGeoIP(currentUser.ID, r)
if err != nil {
log.Error("RefreshGeoIP: %s", err)
}
// Are they doing a Location search (from world city typeahead)?
var city *models.WorldCities
if citySearch != "" {
sort = "distance"
// Require the current user to have THEIR location set, for fairness.
if myLocation.Source == models.LocationSourceNone {
session.FlashError(w, r, "You must set your own location before you can search for others by their location.")
} else {
// Look up the coordinates of their search.
city, err = models.FindWorldCity(citySearch)
if err != nil {
session.FlashError(w, r, "Location search: no match was found for '%s', please use one of the exact search results from the type-ahead on the Location field.", citySearch)
citySearch = "" // null out their search
}
}
}
// Sort options.
for _, v := range sortWhitelist {
if sort == v {
sortOK = true
break
}
}
if !sortOK {
sort = "last_login_at desc"
}
2024-08-24 06:09:27 +00:00
// Real name for certified_at
if sort == "certified_at desc" {
sort = "certification_photos.updated_at desc"
}
2022-08-14 05:44:57 +00:00
// Default
if isCertified == "" {
isCertified = "true"
}
// Always filter for certified-only users unless the request specifically looked for non-certified.
// Searches for disabled/banned users (admin only) should also reveal ALL users including non-certified.
var certifiedOnly = true
if isCertified == "false" || isCertified == "all" || isCertified == "disabled" || isCertified == "banned" {
certifiedOnly = false
}
// Non-admin view: always hide non-certified profiles, they can be unsafe (fake profiles, scams if they won't certify)
if !currentUser.IsAdmin {
certifiedOnly = true
}
2022-08-14 05:44:57 +00:00
pager := &models.Pagination{
PerPage: config.PageSizeMemberSearch,
Sort: sort,
2022-08-14 05:44:57 +00:00
}
pager.ParsePage(r)
users, err := models.SearchUsers(currentUser, &models.UserSearch{
Username: username,
Gender: gender,
Orientation: orientation,
MaritalStatus: maritalStatus,
2023-08-30 04:10:00 +00:00
HereFor: hereFor,
ProfileText: search,
Certified: certifiedOnly,
NearCity: city,
2023-09-02 00:20:34 +00:00
NotCertified: isCertified == "false",
ShyAccounts: isCertified == "shy",
IsBanned: isCertified == "banned",
IsDisabled: isCertified == "disabled",
IsAdmin: isCertified == "admin",
Friends: friendSearch,
2024-06-27 04:27:03 +00:00
Liked: likedSearch,
AgeMin: ageMin,
AgeMax: ageMax,
2022-08-14 05:44:57 +00:00
}, pager)
if err != nil {
2024-03-30 03:35:41 +00:00
session.FlashError(w, r, "An error has occurred: %s.", err)
2022-08-14 05:44:57 +00:00
}
2023-08-20 04:09:23 +00:00
// Who's Nearby feature, get some data.
insights, _ := geoip.GetRequestInsights(r)
2023-09-14 06:13:02 +00:00
// Collect usernames to map to chat online status.
var usernames = []string{}
2024-06-27 04:27:03 +00:00
var userIDs = []uint64{}
2023-09-14 06:13:02 +00:00
for _, user := range users {
usernames = append(usernames, user.Username)
2024-06-27 04:27:03 +00:00
userIDs = append(userIDs, user.ID)
}
// User IDs of these I have "Liked"
likedIDs, err := models.LikedIDs(currentUser, "users", userIDs)
if err != nil {
log.Error("LikedIDs: %s", err)
2023-09-14 06:13:02 +00:00
}
2022-08-14 05:44:57 +00:00
var vars = map[string]interface{}{
"Users": users,
"Pager": pager,
"Enum": config.ProfileEnums,
// Search filter values.
"Certified": isCertified,
"Gender": gender,
"Orientation": orientation,
"MaritalStatus": maritalStatus,
2023-08-30 04:10:00 +00:00
"HereFor": hereFor,
2022-08-14 05:44:57 +00:00
"EmailOrUsername": username,
"Search": searchTerm,
"City": citySearch,
"AgeMin": ageMin,
"AgeMax": ageMax,
"FriendSearch": friendSearch,
2024-06-27 04:27:03 +00:00
"LikedSearch": likedSearch,
"Sort": sort,
2024-07-13 19:05:36 +00:00
// Restricted Search errors.
"RestrictedSearchError": restricted,
// Photo counts mapped to users
"PhotoCountMap": models.MapPhotoCounts(users),
2023-08-20 02:11:33 +00:00
// Map Shy Account badges for these results
"ShyMap": models.MapShyAccounts(users),
2024-06-27 04:27:03 +00:00
// Map friendships and likes to these users.
"FriendMap": models.MapFriends(currentUser, users),
2024-06-27 04:27:03 +00:00
"LikedMap": models.MapLikes(currentUser, "users", likedIDs),
2023-09-14 06:13:02 +00:00
// Users on the chat room map.
"UserOnChatMap": worker.GetChatStatistics().MapUsersOnline(usernames),
2023-08-20 02:11:33 +00:00
// Current user's location setting.
2023-08-20 04:09:23 +00:00
"MyLocation": myLocation,
"GeoIPInsights": insights,
"DistanceMap": models.MapDistances(currentUser, city, users),
2022-08-14 05:44:57 +00:00
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}