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"
2022-08-14 21:40:57 +00:00
"strconv"
2022-08-14 05:44:57 +00:00
2022-08-26 04:21:46 +00:00
"code.nonshy.com/nonshy/website/pkg/config"
2024-10-19 20:07:17 +00:00
"code.nonshy.com/nonshy/website/pkg/controller/chat"
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" )
2022-08-14 21:40:57 +00:00
// 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" ,
2022-08-14 21:40:57 +00:00
"username" ,
2023-08-30 04:10:00 +00:00
"username desc" ,
2022-08-14 21:40:57 +00:00
"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 21:40:57 +00:00
}
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" )
2024-06-19 21:12:25 +00:00
username = r . FormValue ( "name" ) // username search
searchTerm = r . FormValue ( "search" ) // profile text search
2024-08-03 21:54:22 +00:00
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" )
2023-09-02 00:12:27 +00:00
friendSearch = r . FormValue ( "friends" ) == "true"
2024-06-27 04:27:03 +00:00
likedSearch = r . FormValue ( "liked" ) == "true"
2024-10-19 20:07:17 +00:00
onChatSearch = r . FormValue ( "on_chat" ) == "true"
2022-08-14 21:40:57 +00:00
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 {
2022-08-14 21:40:57 +00:00
ageMin , ageMax = ageMax , ageMin
}
2024-07-13 19:05:36 +00:00
rawSearch := models . ParseSearchString ( searchTerm )
search , restricted := spam . RestrictSearchTerms ( rawSearch )
2024-06-19 21:12:25 +00:00
2022-08-15 00:45:55 +00:00
// 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 )
}
2024-08-03 21:54:22 +00:00
// 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
}
}
}
2022-08-14 21:40:57 +00:00
// 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"
}
2024-06-19 22:03:58 +00:00
// 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
}
2024-07-10 05:21:28 +00:00
// 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
}
2024-10-19 20:07:17 +00:00
// Are we filtering for "On Chat?"
var inUsername = [ ] string { }
if onChatSearch {
stats := chat . FilteredChatStatistics ( currentUser )
inUsername = stats . Usernames
if len ( inUsername ) == 0 {
session . FlashError ( w , r , "Notice: you wanted to filter by people currently on the chat room, but nobody is on chat at this time." )
inUsername = [ ] string { "@" }
}
}
2022-08-14 05:44:57 +00:00
pager := & models . Pagination {
PerPage : config . PageSizeMemberSearch ,
2022-08-14 21:40:57 +00:00
Sort : sort ,
2022-08-14 05:44:57 +00:00
}
pager . ParsePage ( r )
2022-09-09 04:42:20 +00:00
users , err := models . SearchUsers ( currentUser , & models . UserSearch {
2023-08-16 00:33:33 +00:00
Username : username ,
2024-10-19 20:07:17 +00:00
InUsername : inUsername ,
2023-08-16 00:33:33 +00:00
Gender : gender ,
Orientation : orientation ,
MaritalStatus : maritalStatus ,
2023-08-30 04:10:00 +00:00
HereFor : hereFor ,
2024-06-19 21:12:25 +00:00
ProfileText : search ,
2024-06-19 22:03:58 +00:00
Certified : certifiedOnly ,
2024-08-03 21:54:22 +00:00
NearCity : city ,
2023-09-02 00:20:34 +00:00
NotCertified : isCertified == "false" ,
2023-09-02 00:12:27 +00:00
ShyAccounts : isCertified == "shy" ,
2023-09-09 18:16:34 +00:00
IsBanned : isCertified == "banned" ,
2024-04-26 04:52:43 +00:00
IsDisabled : isCertified == "disabled" ,
IsAdmin : isCertified == "admin" ,
2023-09-02 00:12:27 +00:00
Friends : friendSearch ,
2024-06-27 04:27:03 +00:00
Liked : likedSearch ,
2023-08-16 00:33:33 +00:00
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 ,
2024-06-19 21:12:25 +00:00
"Search" : searchTerm ,
2024-08-03 21:54:22 +00:00
"City" : citySearch ,
2022-08-14 21:40:57 +00:00
"AgeMin" : ageMin ,
"AgeMax" : ageMax ,
2023-09-02 00:12:27 +00:00
"FriendSearch" : friendSearch ,
2024-06-27 04:27:03 +00:00
"LikedSearch" : likedSearch ,
2024-10-19 20:07:17 +00:00
"OnChatSearch" : onChatSearch ,
2022-08-14 21:40:57 +00:00
"Sort" : sort ,
2023-07-23 22:02:41 +00:00
2024-07-13 19:05:36 +00:00
// Restricted Search errors.
"RestrictedSearchError" : restricted ,
2023-07-23 22:02:41 +00:00
// Photo counts mapped to users
"PhotoCountMap" : models . MapPhotoCounts ( users ) ,
2023-08-20 02:11:33 +00:00
2023-09-02 00:12:27 +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.
2023-09-02 00:12:27 +00:00
"FriendMap" : models . MapFriends ( currentUser , users ) ,
2024-06-27 04:27:03 +00:00
"LikedMap" : models . MapLikes ( currentUser , "users" , likedIDs ) ,
2023-09-02 00:12:27 +00:00
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 ,
2024-08-03 21:54:22 +00:00
"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
}
} )
}