8fca36836c
* On a user gallery page: if the current user can not see their default profile pic (friends-only or private), include a notice and link to the FAQ about this. * Add a new placeholder avatar for profile pics that are set to "Inner circle only" when viewed by members outside the circle.
240 lines
7.7 KiB
Go
240 lines
7.7 KiB
Go
package photo
|
|
|
|
import (
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"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"
|
|
)
|
|
|
|
var UserPhotosRegexp = regexp.MustCompile(`^/photo/u/([^@]+?)$`)
|
|
|
|
// 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{
|
|
"created_at desc",
|
|
"created_at asc",
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Query params.
|
|
var (
|
|
viewStyle = r.FormValue("view") // cards (default), full
|
|
|
|
// Search filters.
|
|
filterExplicit = r.FormValue("explicit")
|
|
filterVisibility = r.FormValue("visibility")
|
|
sort = r.FormValue("sort")
|
|
sortOK bool
|
|
|
|
// Inner circle invite view?
|
|
innerCircleInvite = r.FormValue("intent") == "inner_circle"
|
|
)
|
|
|
|
// Sort options.
|
|
for _, v := range sortWhitelist {
|
|
if sort == v {
|
|
sortOK = true
|
|
break
|
|
}
|
|
}
|
|
if !sortOK {
|
|
sort = sortWhitelist[0]
|
|
}
|
|
|
|
// Defaults.
|
|
if viewStyle != "full" {
|
|
viewStyle = "cards"
|
|
}
|
|
|
|
// Parse the username out of the URL parameters.
|
|
var username string
|
|
m := UserPhotosRegexp.FindStringSubmatch(r.URL.Path)
|
|
if m != nil {
|
|
username = m[1]
|
|
}
|
|
|
|
// 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))
|
|
)
|
|
|
|
// Inner circle invite: not if we are not in the circle ourselves.
|
|
if innerCircleInvite && !currentUser.IsInnerCircle() {
|
|
innerCircleInvite = false
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Inner circle photos.
|
|
if currentUser.IsInnerCircle() {
|
|
visibility = append(visibility, models.PhotoInnerCircle)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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,
|
|
"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),
|
|
"InnerCircleMinimumPublicPhotos": config.InnerCircleMinimumPublicPhotos,
|
|
"Pager": pager,
|
|
"LikeMap": likeMap,
|
|
"CommentMap": commentMap,
|
|
"ViewStyle": viewStyle,
|
|
"ExplicitCount": explicitCount,
|
|
"InnerCircleInviteView": innerCircleInvite,
|
|
|
|
// 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
|
|
}
|
|
})
|
|
}
|