diff --git a/pkg/controller/account/profile.go b/pkg/controller/account/profile.go index 7b9da46..41f59d2 100644 --- a/pkg/controller/account/profile.go +++ b/pkg/controller/account/profile.go @@ -72,9 +72,9 @@ func Profile() http.HandlerFunc { // Inject relationship booleans for profile picture display. models.SetUserRelationships(currentUser, []*models.User{user}) - // Admin user can always see the profile pic - but only on this page. Other avatar displays - // will show the yellow or pink shy.png if the admin is not friends or not granted. - if currentUser.IsAdmin { + // Admin user (photo moderator) can always see the profile pic - but only on this page. + // Other avatar displays will show the yellow or pink shy.png if the admin is not friends or not granted. + if currentUser.HasAdminScope(config.ScopePhotoModerator) { user.UserRelationship.IsFriend = true user.UserRelationship.IsPrivateGranted = true } diff --git a/pkg/controller/api/likes.go b/pkg/controller/api/likes.go index 53df2ac..4243909 100644 --- a/pkg/controller/api/likes.go +++ b/pkg/controller/api/likes.go @@ -98,20 +98,7 @@ func Likes() http.HandlerFunc { if user, err := models.GetUser(photo.UserID); err == nil { // Safety check: if the current user should not see this picture, they can not "Like" it. // Example: you unfriended them but they still had the image on their old browser page. - var unallowed bool - if currentUser.ID != user.ID { - if (photo.Visibility == models.PhotoFriends && !models.AreFriends(user.ID, currentUser.ID)) || - (photo.Visibility == models.PhotoPrivate && !models.IsPrivateUnlocked(user.ID, currentUser.ID)) { - unallowed = true - } - } - - // Blocking safety check: if either user blocks the other, liking is not allowed. - if models.IsBlocking(currentUser.ID, user.ID) { - unallowed = true - } - - if unallowed { + if ok, _ := photo.ShouldBeSeenBy(currentUser); !ok { SendJSON(w, http.StatusForbidden, Response{ Error: "You are not allowed to like that photo.", }) @@ -121,7 +108,7 @@ func Likes() http.HandlerFunc { // Mark this photo as 'viewed' if it received a like. // Example: on a gallery view the photo is only 'viewed' if interacted with (lightbox), // going straight for the 'Like' button should count as well. - photo.View(currentUser.ID) + photo.View(currentUser) targetUser = user } diff --git a/pkg/controller/api/photo.go b/pkg/controller/api/photo.go index c4bd91e..27e8bcd 100644 --- a/pkg/controller/api/photo.go +++ b/pkg/controller/api/photo.go @@ -49,7 +49,7 @@ func ViewPhoto() http.HandlerFunc { } // Check permission to have seen this photo. - if ok, err := photo.CanBeSeenBy(currentUser); !ok { + if ok, err := photo.ShouldBeSeenBy(currentUser); !ok { log.Error("Photo %d can't be seen by %s: %s", photo.ID, currentUser.Username, err) SendJSON(w, http.StatusNotFound, Response{ Error: "Photo Not Found", @@ -58,7 +58,7 @@ func ViewPhoto() http.HandlerFunc { } // Mark a view. - if err := photo.View(currentUser.ID); err != nil { + if err := photo.View(currentUser); err != nil { log.Error("Update photo(%d) views: %s", photo.ID, err) } diff --git a/pkg/controller/photo/view.go b/pkg/controller/photo/view.go index c34af78..4bbaca5 100644 --- a/pkg/controller/photo/view.go +++ b/pkg/controller/photo/view.go @@ -85,7 +85,7 @@ func View() http.HandlerFunc { _, isSubscribed := models.IsSubscribed(currentUser, "photos", photo.ID) // Mark this photo as "Viewed" by the user. - if err := photo.View(currentUser.ID); err != nil { + if err := photo.View(currentUser); err != nil { log.Error("Update photo(%d) views: %s", photo.ID, err) } diff --git a/pkg/models/photo.go b/pkg/models/photo.go index 70715db..ddbc818 100644 --- a/pkg/models/photo.go +++ b/pkg/models/photo.go @@ -109,8 +109,27 @@ func GetPhotos(IDs []uint64) (map[uint64]*Photo, error) { // CanBeSeenBy checks whether a photo can be seen by the current user. // +// An admin user with omni photo view permission can always see the photo. +// // Note: this function incurs several DB queries to look up the photo's owner and block lists. func (p *Photo) CanBeSeenBy(currentUser *User) (bool, error) { + // Admins with photo moderator ability can always see. + if currentUser.HasAdminScope(config.ScopePhotoModerator) { + return true, nil + } + + return p.ShouldBeSeenBy(currentUser) +} + +// ShouldBeSeenBy checks whether a photo should be seen by the current user. +// +// Even if the current user is an admin with photo moderator ability, this function will return +// whether the admin 'should' be able to see if not for their admin status. Example: a private +// photo may be shown to the admin so they can moderate it, but they shouldn't be able to "like" +// it or mark it as "viewed." +// +// Note: this function incurs several DB queries to look up the photo's owner and block lists. +func (p *Photo) ShouldBeSeenBy(currentUser *User) (bool, error) { // Find the photo's owner. user, err := GetUser(p.UserID) if err != nil { @@ -120,7 +139,7 @@ func (p *Photo) CanBeSeenBy(currentUser *User) (bool, error) { var isOwnPhoto = currentUser.ID == user.ID // Is either one blocking? - if !currentUser.IsAdmin && IsBlocking(currentUser.ID, user.ID) { + if IsBlocking(currentUser.ID, user.ID) { return false, errors.New("is blocking") } @@ -129,13 +148,13 @@ func (p *Photo) CanBeSeenBy(currentUser *User) (bool, error) { areFriends = AreFriends(user.ID, currentUser.ID) isPrivate = user.Visibility == UserVisibilityPrivate && !areFriends ) - if isPrivate && !currentUser.IsAdmin && !isOwnPhoto { + if isPrivate && !isOwnPhoto { return false, errors.New("user is private and we aren't friends") } // Is this a private photo and are we allowed to see? isGranted := IsPrivateUnlocked(user.ID, currentUser.ID) - if p.Visibility == PhotoPrivate && !isGranted && !isOwnPhoto && !currentUser.IsAdmin { + if p.Visibility == PhotoPrivate && !isGranted && !isOwnPhoto { return false, errors.New("photo is private") } @@ -193,14 +212,19 @@ func PaginateUserPhotos(userID uint64, conf UserGallery, pager *Pagination) ([]* // View a photo, incrementing its Views count but not its UpdatedAt. // Debounced with a Redis key. -func (p *Photo) View(userID uint64) error { +func (p *Photo) View(user *User) error { // The owner of the photo does not count views. - if p.UserID == userID { + if p.UserID == user.ID { return nil } + // Should the viewer be able to see this, regardless of their admin ability? + if ok, err := p.ShouldBeSeenBy(user); !ok { + return err + } + // Debounce this. - var redisKey = fmt.Sprintf(config.PhotoViewDebounceRedisKey, userID, p.ID) + var redisKey = fmt.Sprintf(config.PhotoViewDebounceRedisKey, user.ID, p.ID) if redis.Exists(redisKey) { return nil } diff --git a/pkg/models/user_relationship.go b/pkg/models/user_relationship.go index 1c0bca0..911777c 100644 --- a/pkg/models/user_relationship.go +++ b/pkg/models/user_relationship.go @@ -42,6 +42,8 @@ func SetUserRelationships(currentUser *User, users []*User) error { // Inject the UserRelationships. for _, u := range users { + u.UserRelationship.Computed = true + if u.ID == currentUser.ID { // Current user - set both bools to true - you can always see your own profile pic. u.UserRelationship.IsFriend = true diff --git a/web/templates/account/profile.html b/web/templates/account/profile.html index 75fd6b1..4b2ffed 100644 --- a/web/templates/account/profile.html +++ b/web/templates/account/profile.html @@ -11,7 +11,9 @@ {{if or (not .CurrentUser) .IsExternalView}} {{else}} - + + + {{end}} diff --git a/web/templates/photo/gallery.html b/web/templates/photo/gallery.html index 6fbfa90..004701f 100644 --- a/web/templates/photo/gallery.html +++ b/web/templates/photo/gallery.html @@ -804,7 +804,7 @@ }).then(response => response.json()) .then(data => { if (data.StatusCode !== 200) { - window.alert(data.data.error); + console.error("When marking photo %d as viewed: status code %d: %s", photoID, data.StatusCode, data.data.error); return; } }).catch(window.alert);