diff --git a/models/user/user.go b/models/user/user.go index 9460bd38fe428..3ef8dda985647 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -316,37 +316,94 @@ func (u *User) GenerateEmailActivateCode(email string) string { } // GetUserFollowers returns range of user's followers. -func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) { - sess := db.GetEngine(db.DefaultContext). - Where("follow.follow_id=?", u.ID). +func GetUserFollowers(ctx context.Context, opts GetUserFollowOptions) ([]*User, int64, error) { + sess := db.GetEngine(ctx).Where(opts.toCond(true)). Join("LEFT", "follow", "`user`.id=follow.user_id") - if listOptions.Page != 0 { - sess = db.SetSessionPagination(sess, &listOptions) + if opts.Page != 0 { + sess = db.SetSessionPagination(sess, &opts.ListOptions) - users := make([]*User, 0, listOptions.PageSize) - return users, sess.Find(&users) + users := make([]*User, 0, opts.PageSize) + count, err := sess.FindAndCount(&users) + return users, count, err } users := make([]*User, 0, 8) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err +} + +// GetFeedsOptions options for getting the user's followers or followings. +type GetUserFollowOptions struct { + db.ListOptions + Actor *User // the user viewing the followings + RequestedUser *User // the user we want followings for +} + +func (opts GetUserFollowOptions) toCond(checkFollowers bool) builder.Cond { + cond := builder.NewCond() + + if checkFollowers { + cond = cond.And(builder.Eq{"`follow`.follow_id": opts.RequestedUser.ID}) + } else { + cond = cond.And(builder.Eq{"`follow`.user_id": opts.RequestedUser.ID}) + } + + // If the actor is not signed in. Only show users that have their visibility + // set to public. + if opts.Actor == nil { + return cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}) + } + + // Fast path for admins. + if opts.Actor.IsAdmin { + return cond + } + + // If the actor is signed in, then we allow all limited & public accounts to be seen. + // However the actor can also see private accounts if they have are in the same organisation + // as that user. + cond = cond.And( + builder.Or( + // Include all limited & public accounts. + builder.Lte{"`user`.visibility": structs.VisibleTypeLimited}, + // Check which private users can be included. + // Get the actor's orginisations and check if the private + // users are in those orgs. + builder.And( + // Specify all private users. + builder.Eq{"`user`.visibility": structs.VisibleTypePrivate}, + // Is the user's id in the organisation that the actor is in? + builder.In("`user`.id", + builder.Select("uid").From("org_user").Where( + builder.In("org_id", + // Get the actor's orginaisations. + builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": opts.Actor.ID})), + )), + ), + ), + ) + + return cond } // GetUserFollowing returns range of user's following. -func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) { - sess := db.GetEngine(db.DefaultContext). - Where("follow.user_id=?", u.ID). - Join("LEFT", "follow", "`user`.id=follow.follow_id") +func GetUserFollowing(ctx context.Context, opts GetUserFollowOptions) ([]*User, int64, error) { + sess := db.GetEngine(ctx). + Join("LEFT", "follow", "`user`.id=follow.follow_id"). + Where(opts.toCond(false)) - if listOptions.Page != 0 { - sess = db.SetSessionPagination(sess, &listOptions) + if opts.Page != 0 { + sess = db.SetSessionPagination(sess, &opts.ListOptions) - users := make([]*User, 0, listOptions.PageSize) - return users, sess.Find(&users) + users := make([]*User, 0, opts.PageSize) + count, err := sess.FindAndCount(&users) + return users, count, err } users := make([]*User, 0, 8) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } // NewGitSig generates and returns the signature of given user. diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 3c81b27f8dad4..343ff05d7faef 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -24,13 +24,17 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) { } func listUserFollowers(ctx *context.APIContext, u *user_model.User) { - users, err := user_model.GetUserFollowers(u, utils.GetListOptions(ctx)) + users, total, err := user_model.GetUserFollowers(ctx, user_model.GetUserFollowOptions{ + RequestedUser: u, + Actor: ctx.Doer, + ListOptions: utils.GetListOptions(ctx), + }) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err) return } - ctx.SetTotalCountHeader(int64(u.NumFollowers)) + ctx.SetTotalCountHeader(total) responseAPIUsers(ctx, users) } @@ -86,13 +90,17 @@ func ListFollowers(ctx *context.APIContext) { } func listUserFollowing(ctx *context.APIContext, u *user_model.User) { - users, err := user_model.GetUserFollowing(u, utils.GetListOptions(ctx)) + users, total, err := user_model.GetUserFollowing(ctx, user_model.GetUserFollowOptions{ + Actor: ctx.Doer, + RequestedUser: u, + ListOptions: utils.GetListOptions(ctx), + }) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err) return } - ctx.SetTotalCountHeader(int64(u.NumFollowing)) + ctx.SetTotalCountHeader(total) responseAPIUsers(ctx, users) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 609f3242c65e2..a9fe996bfa4ff 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -157,9 +157,10 @@ func Profile(ctx *context.Context) { switch tab { case "followers": - items, err := user_model.GetUserFollowers(ctx.ContextUser, db.ListOptions{ - PageSize: setting.UI.User.RepoPagingNum, - Page: page, + items, amountFollowers, err := user_model.GetUserFollowers(ctx, user_model.GetUserFollowOptions{ + RequestedUser: ctx.ContextUser, + Actor: ctx.Doer, + ListOptions: db.ListOptions{PageSize: setting.UI.User.RepoPagingNum, Page: page}, }) if err != nil { ctx.ServerError("GetUserFollowers", err) @@ -167,11 +168,12 @@ func Profile(ctx *context.Context) { } ctx.Data["Cards"] = items - total = ctx.ContextUser.NumFollowers + total = int(amountFollowers) case "following": - items, err := user_model.GetUserFollowing(ctx.ContextUser, db.ListOptions{ - PageSize: setting.UI.User.RepoPagingNum, - Page: page, + items, amountFollowings, err := user_model.GetUserFollowing(ctx, user_model.GetUserFollowOptions{ + Actor: ctx.Doer, + RequestedUser: ctx.ContextUser, + ListOptions: db.ListOptions{PageSize: setting.UI.User.RepoPagingNum, Page: page}, }) if err != nil { ctx.ServerError("GetUserFollowing", err) @@ -179,7 +181,7 @@ func Profile(ctx *context.Context) { } ctx.Data["Cards"] = items - total = ctx.ContextUser.NumFollowing + total = int(amountFollowings) case "activity": ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctx.ContextUser,