aboutsummaryrefslogtreecommitdiffstats
path: root/models/user
diff options
context:
space:
mode:
authorKN4CK3R <admin@oldschoolhack.me>2024-03-04 09:16:03 +0100
committerGitHub <noreply@github.com>2024-03-04 08:16:03 +0000
commitc337ff0ec70618ef2ead7850f90ab2a8458db192 (patch)
treecf4618cf7dc258018d5f9ec827b0fda4a9ebd196 /models/user
parent8e12ba34bab7e728ac93ccfaecbe91e053ef1c89 (diff)
downloadgitea-c337ff0ec70618ef2ead7850f90ab2a8458db192.tar.gz
gitea-c337ff0ec70618ef2ead7850f90ab2a8458db192.zip
Add user blocking (#29028)
Fixes #17453 This PR adds the abbility to block a user from a personal account or organization to restrict how the blocked user can interact with the blocker. The docs explain what's the consequence of blocking a user. Screenshots: ![grafik](https://github.com/go-gitea/gitea/assets/1666336/4ed884f3-e06a-4862-afd3-3b8aa2488dc6) ![grafik](https://github.com/go-gitea/gitea/assets/1666336/ae6d4981-f252-4f50-a429-04f0f9f1cdf1) ![grafik](https://github.com/go-gitea/gitea/assets/1666336/ca153599-5b0f-4b4a-90fe-18bdfd6f0b6b) --------- Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'models/user')
-rw-r--r--models/user/block.go123
-rw-r--r--models/user/follow.go14
-rw-r--r--models/user/user.go2
-rw-r--r--models/user/user_test.go17
4 files changed, 144 insertions, 12 deletions
diff --git a/models/user/block.go b/models/user/block.go
new file mode 100644
index 0000000000..5f2b65a199
--- /dev/null
+++ b/models/user/block.go
@@ -0,0 +1,123 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package user
+
+import (
+ "context"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/builder"
+)
+
+var (
+ ErrBlockOrganization = util.NewInvalidArgumentErrorf("cannot block an organization")
+ ErrCanNotBlock = util.NewInvalidArgumentErrorf("cannot block the user")
+ ErrCanNotUnblock = util.NewInvalidArgumentErrorf("cannot unblock the user")
+ ErrBlockedUser = util.NewPermissionDeniedErrorf("user is blocked")
+)
+
+type Blocking struct {
+ ID int64 `xorm:"pk autoincr"`
+ BlockerID int64 `xorm:"UNIQUE(block)"`
+ Blocker *User `xorm:"-"`
+ BlockeeID int64 `xorm:"UNIQUE(block)"`
+ Blockee *User `xorm:"-"`
+ Note string
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+}
+
+func (*Blocking) TableName() string {
+ return "user_blocking"
+}
+
+func init() {
+ db.RegisterModel(new(Blocking))
+}
+
+func UpdateBlockingNote(ctx context.Context, id int64, note string) error {
+ _, err := db.GetEngine(ctx).ID(id).Cols("note").Update(&Blocking{Note: note})
+ return err
+}
+
+func IsUserBlockedBy(ctx context.Context, blockee *User, blockerIDs ...int64) bool {
+ if len(blockerIDs) == 0 {
+ return false
+ }
+
+ if blockee.IsAdmin {
+ return false
+ }
+
+ cond := builder.Eq{"user_blocking.blockee_id": blockee.ID}.
+ And(builder.In("user_blocking.blocker_id", blockerIDs))
+
+ has, _ := db.GetEngine(ctx).Where(cond).Exist(&Blocking{})
+ return has
+}
+
+type FindBlockingOptions struct {
+ db.ListOptions
+ BlockerID int64
+ BlockeeID int64
+}
+
+func (opts *FindBlockingOptions) ToConds() builder.Cond {
+ cond := builder.NewCond()
+ if opts.BlockerID != 0 {
+ cond = cond.And(builder.Eq{"user_blocking.blocker_id": opts.BlockerID})
+ }
+ if opts.BlockeeID != 0 {
+ cond = cond.And(builder.Eq{"user_blocking.blockee_id": opts.BlockeeID})
+ }
+ return cond
+}
+
+func FindBlockings(ctx context.Context, opts *FindBlockingOptions) ([]*Blocking, int64, error) {
+ return db.FindAndCount[Blocking](ctx, opts)
+}
+
+func GetBlocking(ctx context.Context, blockerID, blockeeID int64) (*Blocking, error) {
+ blocks, _, err := FindBlockings(ctx, &FindBlockingOptions{
+ BlockerID: blockerID,
+ BlockeeID: blockeeID,
+ })
+ if err != nil {
+ return nil, err
+ }
+ if len(blocks) == 0 {
+ return nil, nil
+ }
+ return blocks[0], nil
+}
+
+type BlockingList []*Blocking
+
+func (blocks BlockingList) LoadAttributes(ctx context.Context) error {
+ ids := make(container.Set[int64], len(blocks)*2)
+ for _, b := range blocks {
+ ids.Add(b.BlockerID)
+ ids.Add(b.BlockeeID)
+ }
+
+ userList, err := GetUsersByIDs(ctx, ids.Values())
+ if err != nil {
+ return err
+ }
+
+ userMap := make(map[int64]*User, len(userList))
+ for _, u := range userList {
+ userMap[u.ID] = u
+ }
+
+ for _, b := range blocks {
+ b.Blocker = userMap[b.BlockerID]
+ b.Blockee = userMap[b.BlockeeID]
+ }
+
+ return nil
+}
diff --git a/models/user/follow.go b/models/user/follow.go
index f4dd2891ff..cf9672109a 100644
--- a/models/user/follow.go
+++ b/models/user/follow.go
@@ -29,26 +29,30 @@ func IsFollowing(ctx context.Context, userID, followID int64) bool {
}
// FollowUser marks someone be another's follower.
-func FollowUser(ctx context.Context, userID, followID int64) (err error) {
- if userID == followID || IsFollowing(ctx, userID, followID) {
+func FollowUser(ctx context.Context, user, follow *User) (err error) {
+ if user.ID == follow.ID || IsFollowing(ctx, user.ID, follow.ID) {
return nil
}
+ if IsUserBlockedBy(ctx, user, follow.ID) || IsUserBlockedBy(ctx, follow, user.ID) {
+ return ErrBlockedUser
+ }
+
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
- if err = db.Insert(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil {
+ if err = db.Insert(ctx, &Follow{UserID: user.ID, FollowID: follow.ID}); err != nil {
return err
}
- if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil {
+ if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", follow.ID); err != nil {
return err
}
- if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil {
+ if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", user.ID); err != nil {
return err
}
return committer.Commit()
diff --git a/models/user/user.go b/models/user/user.go
index a898e71a2d..2e1d6af176 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -1167,7 +1167,7 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
return false
}
- // If they follow - they see each over
+ // If they follow - they see each other
follower := IsFollowing(ctx, u.ID, viewer.ID)
if follower {
return true
diff --git a/models/user/user_test.go b/models/user/user_test.go
index f522f743d5..f4efd071ea 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -399,14 +399,19 @@ func TestGetUserByOpenID(t *testing.T) {
func TestFollowUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- testSuccess := func(followerID, followedID int64) {
- assert.NoError(t, user_model.FollowUser(db.DefaultContext, followerID, followedID))
- unittest.AssertExistsAndLoadBean(t, &user_model.Follow{UserID: followerID, FollowID: followedID})
+ testSuccess := func(follower, followed *user_model.User) {
+ assert.NoError(t, user_model.FollowUser(db.DefaultContext, follower, followed))
+ unittest.AssertExistsAndLoadBean(t, &user_model.Follow{UserID: follower.ID, FollowID: followed.ID})
}
- testSuccess(4, 2)
- testSuccess(5, 2)
- assert.NoError(t, user_model.FollowUser(db.DefaultContext, 2, 2))
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
+ user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+
+ testSuccess(user4, user2)
+ testSuccess(user5, user2)
+
+ assert.NoError(t, user_model.FollowUser(db.DefaultContext, user2, user2))
unittest.CheckConsistencyFor(t, &user_model.User{})
}