diff --git a/pkg/controller/account/profile.go b/pkg/controller/account/profile.go index ff7a12c..7dfa172 100644 --- a/pkg/controller/account/profile.go +++ b/pkg/controller/account/profile.go @@ -65,6 +65,16 @@ func Profile() http.HandlerFunc { return } + // 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 { + user.UserRelationship.IsFriend = true + user.UserRelationship.IsPrivateGranted = true + } + var isSelf = currentUser.ID == user.ID // Banned or disabled? Only admin can view then. diff --git a/pkg/controller/account/search.go b/pkg/controller/account/search.go index 2ea64f3..e743540 100644 --- a/pkg/controller/account/search.go +++ b/pkg/controller/account/search.go @@ -72,7 +72,7 @@ func Search() http.HandlerFunc { } pager.ParsePage(r) - users, err := models.SearchUsers(currentUser.ID, &models.UserSearch{ + users, err := models.SearchUsers(currentUser, &models.UserSearch{ EmailOrUsername: username, Gender: gender, Orientation: orientation, diff --git a/pkg/controller/admin/feedback.go b/pkg/controller/admin/feedback.go index ddd54f3..a429ca4 100644 --- a/pkg/controller/admin/feedback.go +++ b/pkg/controller/admin/feedback.go @@ -156,7 +156,7 @@ func Feedback() http.HandlerFunc { userIDs = append(userIDs, p.UserID) } } - userMap, err := models.MapUsers(userIDs) + userMap, err := models.MapUsers(currentUser, userIDs) if err != nil { session.FlashError(w, r, "Couldn't map user IDs: %s", err) } diff --git a/pkg/controller/block/block.go b/pkg/controller/block/block.go index c126348..297affc 100644 --- a/pkg/controller/block/block.go +++ b/pkg/controller/block/block.go @@ -27,7 +27,7 @@ func Blocked() http.HandlerFunc { Sort: "updated_at desc", } pager.ParsePage(r) - blocked, err := models.PaginateBlockList(currentUser.ID, pager) + blocked, err := models.PaginateBlockList(currentUser, pager) if err != nil { session.FlashError(w, r, "Couldn't paginate block list: %s", err) templates.Redirect(w, "/") diff --git a/pkg/controller/friend/friends.go b/pkg/controller/friend/friends.go index 50f723b..4d2897c 100644 --- a/pkg/controller/friend/friends.go +++ b/pkg/controller/friend/friends.go @@ -32,13 +32,16 @@ func Friends() http.HandlerFunc { Sort: "updated_at desc", } pager.ParsePage(r) - friends, err := models.PaginateFriends(currentUser.ID, isRequests, isPending, pager) + friends, err := models.PaginateFriends(currentUser, isRequests, isPending, pager) if err != nil { session.FlashError(w, r, "Couldn't paginate friends: %s", err) templates.Redirect(w, "/") return } + // Inject relationship booleans. + models.SetUserRelationships(currentUser, friends) + var vars = map[string]interface{}{ "IsRequests": isRequests, "IsPending": isPending, diff --git a/pkg/controller/inbox/inbox.go b/pkg/controller/inbox/inbox.go index 0e1762f..bb55186 100644 --- a/pkg/controller/inbox/inbox.go +++ b/pkg/controller/inbox/inbox.go @@ -116,7 +116,7 @@ func Inbox() http.HandlerFunc { userIDs = append(userIDs, m.SourceUserID, m.TargetUserID) } } - userMap, err := models.MapUsers(userIDs) + userMap, err := models.MapUsers(currentUser, userIDs) if err != nil { session.FlashError(w, r, "Couldn't map users: %s", err) } diff --git a/pkg/controller/photo/certification.go b/pkg/controller/photo/certification.go index e81e842..7d2eeee 100644 --- a/pkg/controller/photo/certification.go +++ b/pkg/controller/photo/certification.go @@ -174,6 +174,12 @@ func AdminCertification() http.HandlerFunc { view = "pending" } + // Get the current user. + currentUser, err := session.CurrentUser(r) + if err != nil { + session.FlashError(w, r, "Couldn't get CurrentUser: %s", err) + } + // Short circuit the GET view for username/email search (exact match) if username := r.FormValue("username"); username != "" { user, err := models.FindUser(username) @@ -341,7 +347,7 @@ func AdminCertification() http.HandlerFunc { for _, p := range photos { userIDs = append(userIDs, p.UserID) } - userMap, err := models.MapUsers(userIDs) + userMap, err := models.MapUsers(currentUser, userIDs) if err != nil { session.FlashError(w, r, "Couldn't map user IDs: %s", err) } diff --git a/pkg/controller/photo/private.go b/pkg/controller/photo/private.go index 23ae2df..ac0c728 100644 --- a/pkg/controller/photo/private.go +++ b/pkg/controller/photo/private.go @@ -35,7 +35,7 @@ func Private() http.HandlerFunc { Sort: "updated_at desc", } pager.ParsePage(r) - users, err := models.PaginatePrivatePhotoList(currentUser.ID, isGrantee, pager) + users, err := models.PaginatePrivatePhotoList(currentUser, isGrantee, pager) if err != nil { session.FlashError(w, r, "Couldn't paginate users: %s", err) templates.Redirect(w, "/") diff --git a/pkg/controller/photo/site_gallery.go b/pkg/controller/photo/site_gallery.go index 359e076..681c8df 100644 --- a/pkg/controller/photo/site_gallery.go +++ b/pkg/controller/photo/site_gallery.go @@ -67,7 +67,7 @@ func SiteGallery() http.HandlerFunc { for _, photo := range photos { userIDs = append(userIDs, photo.UserID) } - userMap, err := models.MapUsers(userIDs) + userMap, err := models.MapUsers(currentUser, userIDs) if err != nil { session.FlashError(w, r, "Failed to MapUsers: %s", err) } diff --git a/pkg/models/blocklist.go b/pkg/models/blocklist.go index 80801be..144611c 100644 --- a/pkg/models/blocklist.go +++ b/pkg/models/blocklist.go @@ -63,7 +63,7 @@ func IsBlocked(sourceUserID, targetUserID uint64) bool { } // PaginateBlockList views a user's blocklist. -func PaginateBlockList(userID uint64, pager *Pagination) ([]*User, error) { +func PaginateBlockList(user *User, pager *Pagination) ([]*User, error) { // We paginate over the Block table. var ( bs = []*Block{} @@ -73,7 +73,7 @@ func PaginateBlockList(userID uint64, pager *Pagination) ([]*User, error) { query = DB.Where( "source_user_id = ?", - userID, + user.ID, ) query = query.Order(pager.Sort) @@ -88,7 +88,7 @@ func PaginateBlockList(userID uint64, pager *Pagination) ([]*User, error) { userIDs = append(userIDs, b.TargetUserID) } - return GetUsers(userIDs) + return GetUsers(user, userIDs) } // BlockedUserIDs returns all user IDs blocked by the user. diff --git a/pkg/models/comment.go b/pkg/models/comment.go index d28dd70..2fd6dfd 100644 --- a/pkg/models/comment.go +++ b/pkg/models/comment.go @@ -79,6 +79,10 @@ func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagin query.Model(&Comment{}).Count(&pager.Total) result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&cs) + + // Inject user relationships into these comments' authors. + SetUserRelationshipsInComments(user, cs) + return cs, result.Error } diff --git a/pkg/models/forum_recent.go b/pkg/models/forum_recent.go index f131934..2ecb2f9 100644 --- a/pkg/models/forum_recent.go +++ b/pkg/models/forum_recent.go @@ -131,14 +131,22 @@ func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([] forums, _ = GetForums(forumIDs) } + // Collect comments so we can inject UserRelationships in efficiently. + var ( + coms = []*Comment{} + thrs = []*Thread{} + ) + // Merge all the objects back in. for _, rc := range result { if com, ok := comments[rc.CommentID]; ok { rc.Comment = com + coms = append(coms, com) } if thr, ok := threads[rc.ThreadID]; ok { rc.Thread = thr + thrs = append(thrs, thr) } else { log.Error("RecentPosts: didn't find thread ID %d in map!") } @@ -148,5 +156,9 @@ func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([] } } + // Inject user relationships into all comment users now. + SetUserRelationshipsInComments(user, coms) + SetUserRelationshipsInThreads(user, thrs) + return result, nil } diff --git a/pkg/models/friend.go b/pkg/models/friend.go index 01fb470..dd7b954 100644 --- a/pkg/models/friend.go +++ b/pkg/models/friend.go @@ -123,7 +123,7 @@ The `requests` and `sent` bools are mutually exclusive (use only one, or neither asks for unanswered friend requests to you, and `sent` returns the friend requests that you have sent and have not been answered. */ -func PaginateFriends(userID uint64, requests bool, sent bool, pager *Pagination) ([]*User, error) { +func PaginateFriends(user *User, requests bool, sent bool, pager *Pagination) ([]*User, error) { // We paginate over the Friend table. var ( fs = []*Friend{} @@ -138,17 +138,17 @@ func PaginateFriends(userID uint64, requests bool, sent bool, pager *Pagination) if requests { query = DB.Where( "target_user_id = ? AND approved = ?", - userID, false, + user.ID, false, ) } else if sent { query = DB.Where( "source_user_id = ? AND approved = ?", - userID, false, + user.ID, false, ) } else { query = DB.Where( "source_user_id = ? AND approved = ?", - userID, true, + user.ID, true, ) } @@ -168,7 +168,7 @@ func PaginateFriends(userID uint64, requests bool, sent bool, pager *Pagination) } } - return GetUsers(userIDs) + return GetUsers(user, userIDs) } // GetFriendRequests returns all pending friend requests for a user. diff --git a/pkg/models/private_photo.go b/pkg/models/private_photo.go index 8fc80c2..66ee1cb 100644 --- a/pkg/models/private_photo.go +++ b/pkg/models/private_photo.go @@ -96,7 +96,7 @@ If grantee is true, it returns the list of users who have granted YOU access to private photos. If grantee is false, it returns the users that YOU have granted access to see YOUR OWN private photos. */ -func PaginatePrivatePhotoList(userID uint64, grantee bool, pager *Pagination) ([]*User, error) { +func PaginatePrivatePhotoList(user *User, grantee bool, pager *Pagination) ([]*User, error) { var ( pbs = []*PrivatePhoto{} userIDs = []uint64{} @@ -109,11 +109,11 @@ func PaginatePrivatePhotoList(userID uint64, grantee bool, pager *Pagination) ([ if grantee { // Return the private photo grants for whom YOU are the recipient. wheres = append(wheres, "target_user_id = ?") - placeholders = append(placeholders, userID) + placeholders = append(placeholders, user.ID) } else { // Return the users that YOU have granted access to YOUR private pictures. wheres = append(wheres, "source_user_id = ?") - placeholders = append(placeholders, userID) + placeholders = append(placeholders, user.ID) } query = DB.Where( @@ -137,7 +137,7 @@ func PaginatePrivatePhotoList(userID uint64, grantee bool, pager *Pagination) ([ } } - return GetUsers(userIDs) + return GetUsers(user, userIDs) } // Save photo. diff --git a/pkg/models/thread.go b/pkg/models/thread.go index 22f5f3b..8a57bc6 100644 --- a/pkg/models/thread.go +++ b/pkg/models/thread.go @@ -151,6 +151,10 @@ func PaginateThreads(user *User, forum *Forum, pager *Pagination) ([]*Thread, er query.Model(&Thread{}).Count(&pager.Total) result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&ts) + + // Inject user relationships into these threads' comments' users. + SetUserRelationshipsInThreads(user, ts) + return ts, result.Error } diff --git a/pkg/models/user.go b/pkg/models/user.go index de32bb6..53dfe50 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -34,6 +34,9 @@ type User struct { ProfileField []ProfileField ProfilePhotoID *uint64 ProfilePhoto Photo `gorm:"foreignKey:profile_photo_id"` + + // Current user's relationship to this user -- not stored in DB. + UserRelationship UserRelationship `gorm:"-"` } type UserVisibility string @@ -97,8 +100,8 @@ func GetUser(userId uint64) (*User, error) { } // GetUsers queries for multiple user IDs and returns users in the same order. -func GetUsers(userIDs []uint64) ([]*User, error) { - userMap, err := MapUsers(userIDs) +func GetUsers(currentUser *User, userIDs []uint64) ([]*User, error) { + userMap, err := MapUsers(currentUser, userIDs) if err != nil { return nil, err } @@ -141,7 +144,7 @@ type UserSearch struct { } // SearchUsers from the perspective of a given user. -func SearchUsers(userID uint64, search *UserSearch, pager *Pagination) ([]*User, error) { +func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, error) { if search == nil { search = &UserSearch{} } @@ -151,7 +154,7 @@ func SearchUsers(userID uint64, search *UserSearch, pager *Pagination) ([]*User, query *gorm.DB wheres = []string{} placeholders = []interface{}{} - blockedUserIDs = BlockedUserIDs(userID) + blockedUserIDs = BlockedUserIDs(user.ID) ) if len(blockedUserIDs) > 0 { @@ -218,6 +221,10 @@ func SearchUsers(userID uint64, search *UserSearch, pager *Pagination) ([]*User, ).Order(pager.Sort) query.Model(&User{}).Count(&pager.Total) result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&users) + + // Inject relationship booleans. + SetUserRelationships(user, users) + return users, result.Error } @@ -227,7 +234,7 @@ type UserMap map[uint64]*User // MapUsers looks up a set of user IDs in bulk and returns a UserMap suitable for templates. // Useful to avoid circular reference issues with Photos especially; the Site Gallery queries // photos of ALL users and MapUsers helps stitch them together for the frontend. -func MapUsers(userIDs []uint64) (UserMap, error) { +func MapUsers(user *User, userIDs []uint64) (UserMap, error) { var ( usermap = UserMap{} set = map[uint64]interface{}{} @@ -248,6 +255,11 @@ func MapUsers(userIDs []uint64) (UserMap, error) { result = (&User{}).Preload().Where("id IN ?", distinct).Find(&users) ) + // Inject user relationships. + if user != nil { + SetUserRelationships(user, users) + } + if result.Error == nil { for _, row := range users { usermap[row.ID] = row diff --git a/pkg/models/user_relationship.go b/pkg/models/user_relationship.go new file mode 100644 index 0000000..359974c --- /dev/null +++ b/pkg/models/user_relationship.go @@ -0,0 +1,87 @@ +package models + +// UserRelationship fields - how a target User relates to the CurrentUser, especially +// with regards to whether the User's friends-only or private profile picture should show. +// The zero-values should fail safely: in case the UserRelationship isn't populated correctly, +// private profile pics show as private by default. +type UserRelationship struct { + IsFriend bool // if true, a friends-only profile pic can show + IsPrivateGranted bool // if true, a private profile pic can show +} + +// SetUserRelationships updates a set of User objects to populate their UserRelationships in +// relationship to the current user who is looking. +func SetUserRelationships(user *User, users []*User) error { + // Collect the current user's Friendships and Private Grants. + var ( + friendIDs = FriendIDs(user.ID) + privateGrants = PrivateGrantedUserIDs(user.ID) + ) + + // Map them for easier lookup. + var ( + friendMap = map[uint64]interface{}{} + privateMap = map[uint64]interface{}{} + ) + + for _, id := range friendIDs { + friendMap[id] = nil + } + + for _, id := range privateGrants { + privateMap[id] = nil + } + + // Inject the UserRelationships. + for _, u := range users { + if u.ID == user.ID { + // Current user - set both bools to true - you can always see your own profile pic. + u.UserRelationship.IsFriend = true + u.UserRelationship.IsPrivateGranted = true + continue + } + + if _, ok := friendMap[u.ID]; ok { + u.UserRelationship.IsFriend = true + } + + if _, ok := privateMap[u.ID]; ok { + u.UserRelationship.IsPrivateGranted = true + } + } + return nil +} + +// SetUserRelationshipsInComments takes a set of Comments and sets relationship booleans on their Users. +func SetUserRelationshipsInComments(user *User, comments []*Comment) { + // Gather and map the users. + var ( + users = []*User{} + userMap = map[uint64]*User{} + ) + + for _, c := range comments { + users = append(users, &c.User) + userMap[c.User.ID] = &c.User + } + + // Inject relationships. + SetUserRelationships(user, users) +} + +// SetUserRelationshipsInThreads takes a set of Threads and sets relationship booleans on their Users. +func SetUserRelationshipsInThreads(user *User, threads []*Thread) { + // Gather and map the thread parent comments. + var ( + comments = []*Comment{} + comMap = map[uint64]*Comment{} + ) + + for _, c := range threads { + comments = append(comments, &c.Comment) + comMap[c.Comment.ID] = &c.Comment + } + + // Inject relationships into those comments' users. + SetUserRelationshipsInComments(user, comments) +} diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go index 575a7dc..3a244af 100644 --- a/pkg/templates/templates.go +++ b/pkg/templates/templates.go @@ -30,7 +30,7 @@ func LoadTemplate(filename string) (*Template, error) { filepath := config.TemplatePath + "/" + filename stat, err := os.Stat(filepath) if err != nil { - return nil, fmt.Errorf("LoadTemplate(%s): %s", err) + return nil, fmt.Errorf("LoadTemplate(%s): %s", filename, err) } files := templates(config.TemplatePath + "/" + filename) @@ -118,6 +118,7 @@ func (t *Template) Reload() error { // Base template layout. var baseTemplates = []string{ config.TemplatePath + "/base.html", + config.TemplatePath + "/partials/user_avatar.html", } // templates returns a template chain with the base templates preceding yours. diff --git a/web/static/img/shy-friends.png b/web/static/img/shy-friends.png new file mode 100644 index 0000000..34af734 Binary files /dev/null and b/web/static/img/shy-friends.png differ diff --git a/web/static/img/shy-private.png b/web/static/img/shy-private.png new file mode 100644 index 0000000..a597580 Binary files /dev/null and b/web/static/img/shy-private.png differ diff --git a/web/templates/account/block_list.html b/web/templates/account/block_list.html index 2675369..8555009 100644 --- a/web/templates/account/block_list.html +++ b/web/templates/account/block_list.html @@ -51,13 +51,7 @@
{{.NameOrUsername}}
diff --git a/web/templates/account/dashboard.html b/web/templates/account/dashboard.html index 38f39ca..732ab06 100644 --- a/web/templates/account/dashboard.html +++ b/web/templates/account/dashboard.html @@ -184,13 +184,7 @@