package photo import ( "net/http" "code.nonshy.com/nonshy/website/pkg/config" "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/templates" ) // UserPhotos controller (/photo/u/:username) to view a user's gallery or manage if it's yourself. func UserPhotos() http.HandlerFunc { tmpl := templates.Must("photo/gallery.html") // Whitelist for ordering options. var sortWhitelist = []string{ "pinned desc nulls last, updated_at desc", "created_at desc", "created_at asc", "like_count desc", "comment_count desc", "views desc", } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Query params. var ( username = r.PathValue("username") viewStyle = r.FormValue("view") // cards (default), full // Search filters. filterExplicit = r.FormValue("explicit") filterVisibility = r.FormValue("visibility") sort = r.FormValue("sort") sortOK bool ) // Sort options. for _, v := range sortWhitelist { if sort == v { sortOK = true break } } if !sortOK { sort = sortWhitelist[0] } // Defaults. if viewStyle != "full" { viewStyle = "cards" } // Find this user. user, err := models.FindUser(username) if err != nil { templates.NotFoundPage(w, r) return } // Load the current user in case they are viewing their own page. currentUser, err := session.CurrentUser(r) if err != nil { session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser") } var ( areFriends = models.AreFriends(user.ID, currentUser.ID) isPrivate = user.Visibility == models.UserVisibilityPrivate && !areFriends isOwnPhotos = currentUser.ID == user.ID isShy = currentUser.IsShy() isShyFrom = !isOwnPhotos && (currentUser.IsShyFrom(user) || (isShy && !areFriends)) ) // Bail early if we are shy from this user. if isShy && isShyFrom { var vars = map[string]interface{}{ "IsOwnPhotos": currentUser.ID == user.ID, "IsShyUser": isShy, "IsShyFrom": isShyFrom, // "IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics? // "AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access. "User": user, "Photos": []*models.Photo{}, "PhotoCount": models.CountPhotos(user.ID), "Pager": &models.Pagination{}, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } return } // Is either one blocking? if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin { templates.NotFoundPage(w, r) return } // Is this user private and we're not friends? if isPrivate && !currentUser.IsAdmin && !isOwnPhotos { session.FlashError(w, r, "This user's profile page and photo gallery are private.") templates.Redirect(w, "/u/"+user.Username) return } // Has this user granted access to see their privates? var ( isGrantee = models.IsPrivateUnlocked(user.ID, currentUser.ID) // THEY have granted US access isGranted = models.IsPrivateUnlocked(currentUser.ID, user.ID) // WE have granted THEM access ) // What set of visibilities to query? visibility := []models.PhotoVisibility{models.PhotoPublic} if isOwnPhotos || isGrantee || currentUser.HasAdminScope(config.ScopePhotoModerator) { visibility = append(visibility, models.PhotoPrivate) } if isOwnPhotos || areFriends || currentUser.HasAdminScope(config.ScopePhotoModerator) { visibility = append(visibility, models.PhotoFriends) } // If we are Filtering by Visibility, ensure the target visibility is accessible to us. if filterVisibility != "" { var isOK bool for _, allowed := range visibility { if allowed == models.PhotoVisibility(filterVisibility) { isOK = true break } } // If the filter is within the set we are allowed to see, update the set. if isOK { visibility = []models.PhotoVisibility{models.PhotoVisibility(filterVisibility)} } else { session.FlashError(w, r, "Could not filter pictures by that visibility setting: it is not available for you.") visibility = []models.PhotoVisibility{models.PhotoNotAvailable} } } // Explicit photo filter? The default ("") will defer to the user's Explicit opt-in. if filterExplicit == "" { // If the viewer does not opt-in to explicit AND is not looking at their own gallery, // then default the explicit filter to "do not show explicit" if !currentUser.Explicit && !isOwnPhotos { filterExplicit = "false" } } // Get the page of photos. pager := &models.Pagination{ Page: 1, PerPage: config.PageSizeUserGallery, Sort: sort, } pager.ParsePage(r) photos, err := models.PaginateUserPhotos(user.ID, models.UserGallery{ Explicit: filterExplicit, Visibility: visibility, }, pager) if err != nil { log.Error("PaginateUserPhotos(%s): %s", user.Username, err) } // Get the count of explicit photos if we are not viewing explicit photos. var explicitCount int64 if filterExplicit == "false" { explicitCount, _ = models.CountExplicitPhotos(user.ID, visibility) } // Get Likes information about these photos. var photoIDs = []uint64{} for _, p := range photos { photoIDs = append(photoIDs, p.ID) } likeMap := models.MapLikes(currentUser, "photos", photoIDs) commentMap := models.MapCommentCounts("photos", photoIDs) // Can we see their default profile picture? If no: show a hint on the Gallery page that // their default pic isn't visible. var profilePictureHidden models.PhotoVisibility if ok, visibility := user.CanSeeProfilePicture(currentUser); !ok && visibility != models.PhotoPublic { profilePictureHidden = visibility } // Friend Photos Notification Opt-out: // If your friend posts too many photos and you want to mute them. // NOTE: notifications are "on by default" and only an explicit "false" // stored in the database indicates an opt-out. // New photo upload notification subscription status. var areNotificationsMuted bool if exists, v := models.IsSubscribed(currentUser, "friend.photos", user.ID); exists { areNotificationsMuted = !v } var vars = map[string]interface{}{ "IsOwnPhotos": currentUser.ID == user.ID, "IsShyUser": isShy, "IsShyFrom": isShyFrom, "IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics? "AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access. "AreFriends": areFriends, "AreNotificationsMuted": areNotificationsMuted, "ProfilePictureHiddenVisibility": profilePictureHidden, "User": user, "Photos": photos, "PhotoCount": models.CountPhotosICanSee(user, currentUser), "NoteCount": models.CountNotesAboutUser(currentUser, user), "FriendCount": models.CountFriends(user.ID), "PublicPhotoCount": models.CountPublicPhotos(user.ID), "Pager": pager, "LikeMap": likeMap, "CommentMap": commentMap, "ViewStyle": viewStyle, "ExplicitCount": explicitCount, // Search filters "Sort": sort, "FilterExplicit": filterExplicit, "FilterVisibility": filterVisibility, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }) }