package account import ( "fmt" "net/http" "strconv" "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/controller/chat" "code.nonshy.com/nonshy/website/pkg/geoip" "code.nonshy.com/nonshy/website/pkg/log" "code.nonshy.com/nonshy/website/pkg/models" "code.nonshy.com/nonshy/website/pkg/session" "code.nonshy.com/nonshy/website/pkg/spam" "code.nonshy.com/nonshy/website/pkg/templates" "code.nonshy.com/nonshy/website/pkg/worker" ) // 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", "certified_at desc", "username", "username desc", "lower(name)", "lower(name) desc", "distance", } 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") gender = r.FormValue("gender") orientation = r.FormValue("orientation") maritalStatus = r.FormValue("marital_status") hereFor = r.FormValue("here_for") friendSearch = r.FormValue("friends") == "true" likedSearch = r.FormValue("liked") == "true" onChatSearch = r.FormValue("on_chat") == "true" sort = r.FormValue("sort") sortOK bool ) 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 } 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 } // 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) } } } // 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" } // Real name for certified_at if sort == "certified_at desc" { sort = "certification_photos.updated_at desc" } // 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 } // 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{"@"} } } pager := &models.Pagination{ PerPage: config.PageSizeMemberSearch, Sort: sort, } pager.ParsePage(r) users, err := models.SearchUsers(currentUser, &models.UserSearch{ Username: username, InUsername: inUsername, Gender: gender, Orientation: orientation, MaritalStatus: maritalStatus, HereFor: hereFor, ProfileText: search, Certified: certifiedOnly, NearCity: city, NotCertified: isCertified == "false", ShyAccounts: isCertified == "shy", IsBanned: isCertified == "banned", IsDisabled: isCertified == "disabled", IsAdmin: isCertified == "admin", Friends: friendSearch, Liked: likedSearch, AgeMin: ageMin, AgeMax: ageMax, }, pager) if err != nil { session.FlashError(w, r, "An error has occurred: %s.", err) } // Who's Nearby feature, get some data. insights, _ := geoip.GetRequestInsights(r) // Collect usernames to map to chat online status. var usernames = []string{} var userIDs = []uint64{} for _, user := range users { usernames = append(usernames, user.Username) 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) } var vars = map[string]interface{}{ "Users": users, "Pager": pager, "Enum": config.ProfileEnums, // Search filter values. "Certified": isCertified, "Gender": gender, "Orientation": orientation, "MaritalStatus": maritalStatus, "HereFor": hereFor, "EmailOrUsername": username, "Search": searchTerm, "City": citySearch, "AgeMin": ageMin, "AgeMax": ageMax, "FriendSearch": friendSearch, "LikedSearch": likedSearch, "OnChatSearch": onChatSearch, "Sort": sort, // Restricted Search errors. "RestrictedSearchError": restricted, // Photo counts mapped to users "PhotoCountMap": models.MapPhotoCounts(users), // Map Shy Account badges for these results "ShyMap": models.MapShyAccounts(users), // Map friendships and likes to these users. "FriendMap": models.MapFriends(currentUser, users), "LikedMap": models.MapLikes(currentUser, "users", likedIDs), // Users on the chat room map. "UserOnChatMap": worker.GetChatStatistics().MapUsersOnline(usernames), // Current user's location setting. "MyLocation": myLocation, "GeoIPInsights": insights, "DistanceMap": models.MapDistances(currentUser, city, users), } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }) }