Skip to content

Commit dc9499b

Browse files
Gustedearl-warren
authored andcommitted
[MODERATION] user blocking
- Add the ability to block a user via their profile page. - This will unstar their repositories and visa versa. - Blocked users cannot create issues or pull requests on your the doer's repositories (mind that this is not the case for organizations). - Blocked users cannot comment on the doer's opened issues or pull requests. - Blocked users cannot add reactions to doer's comments. - Blocked users cannot cause a notification trough mentioning the doer. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/540 (cherry picked from commit 687d852480388897db4d7b0cb397cf7135ab97b1) (cherry picked from commit 0c32a4fde531018f74e01d9db6520895fcfa10cc) (cherry picked from commit 1791130e3cb8470b9b39742e0004d5e4c7d1e64d) (cherry picked from commit 00f411819f62c02016d46602ab4daf49effe0550) (cherry picked from commit e0c039b0e899e787a8df1efdd6b47388d93e08fa) (cherry picked from commit b5a058ef0039e95be23893e6fefdcb62a7de071a) (cherry picked from commit 5ff5460d28a482526da7e77bffb18d08de14aaaa) (cherry picked from commit 97bc6e619d2970839b8692b7b025ff0ec1c96d12)
1 parent cfe5e17 commit dc9499b

37 files changed

+656
-52
lines changed

models/activities/action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
580580

581581
if repoChanged {
582582
// Add feeds for user self and all watchers.
583-
watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
583+
watchers, err = repo_model.GetWatchersExcludeBlocked(ctx, act.RepoID, act.ActUserID)
584584
if err != nil {
585585
return fmt.Errorf("get watchers: %w", err)
586586
}

models/activities/notification.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
235235
for _, id := range issueUnWatches {
236236
toNotify.Remove(id)
237237
}
238+
239+
// Remove users who have the notification author blocked.
240+
blockedAuthorIDs, err := user_model.ListBlockedByUsersID(ctx, notificationAuthorID)
241+
if err != nil {
242+
return err
243+
}
244+
for _, id := range blockedAuthorIDs {
245+
toNotify.Remove(id)
246+
}
238247
}
239248

240249
err = issue.LoadRepo(ctx)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-
2+
id: 1
3+
user_id: 4
4+
block_id: 1
5+
created_unix: 1671607299

models/forgejo_migrations/migrate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"os"
1010

11+
forgejo_v1_20 "code.gitea.io/gitea/models/forgejo_migrations/v1_20"
1112
"code.gitea.io/gitea/modules/git"
1213
"code.gitea.io/gitea/modules/log"
1314
"code.gitea.io/gitea/modules/setting"
@@ -34,7 +35,9 @@ func NewMigration(desc string, fn func(*xorm.Engine) error) *Migration {
3435

3536
// This is a sequence of additional Forgejo migrations.
3637
// Add new migrations to the bottom of the list.
37-
var migrations = []*Migration{}
38+
var migrations = []*Migration{
39+
NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser),
40+
}
3841

3942
// GetCurrentDBVersion returns the current Forgejo database version.
4043
func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {

models/forgejo_migrations/v1_20/v1.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2023 The Forgejo Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package forgejo_v1_20 //nolint:revive
5+
6+
import (
7+
"code.gitea.io/gitea/modules/timeutil"
8+
9+
"xorm.io/xorm"
10+
)
11+
12+
func AddForgejoBlockedUser(x *xorm.Engine) error {
13+
type ForgejoBlockedUser struct {
14+
ID int64 `xorm:"pk autoincr"`
15+
BlockID int64 `xorm:"index"`
16+
UserID int64 `xorm:"index"`
17+
CreatedUnix timeutil.TimeStamp `xorm:"created"`
18+
}
19+
20+
return x.Sync(new(ForgejoBlockedUser))
21+
}

models/issues/issue_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ func TestIssue_ResolveMentions(t *testing.T) {
452452
testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{})
453453
// Public repo, doer
454454
testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{})
455+
// Public repo, blocked user
456+
testSuccess("user2", "repo1", "user1", []string{"user4"}, []int64{})
455457
// Private repo, team member
456458
testSuccess("user17", "big_test_private_4", "user20", []string{"user2"}, []int64{2})
457459
// Private repo, not a team member

models/issues/issue_update.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,9 +608,11 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
608608
teamusers := make([]*user_model.User, 0, 20)
609609
if err := db.GetEngine(ctx).
610610
Join("INNER", "team_user", "team_user.uid = `user`.id").
611+
Join("LEFT", "forgejo_blocked_user", "forgejo_blocked_user.user_id = `user`.id").
611612
In("`team_user`.team_id", checked).
612613
And("`user`.is_active = ?", true).
613614
And("`user`.prohibit_login = ?", false).
615+
And(builder.Or(builder.IsNull{"`forgejo_blocked_user`.block_id"}, builder.Neq{"`forgejo_blocked_user`.block_id": doer.ID})).
614616
Find(&teamusers); err != nil {
615617
return nil, fmt.Errorf("get teams users: %w", err)
616618
}
@@ -644,8 +646,10 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
644646

645647
unchecked := make([]*user_model.User, 0, len(mentionUsers))
646648
if err := db.GetEngine(ctx).
649+
Join("LEFT", "forgejo_blocked_user", "forgejo_blocked_user.user_id = `user`.id").
647650
Where("`user`.is_active = ?", true).
648651
And("`user`.prohibit_login = ?", false).
652+
And(builder.Or(builder.IsNull{"`forgejo_blocked_user`.block_id"}, builder.Neq{"`forgejo_blocked_user`.block_id": doer.ID})).
649653
In("`user`.lower_name", mentionUsers).
650654
Find(&unchecked); err != nil {
651655
return nil, fmt.Errorf("find mentioned users: %w", err)

models/issues/reaction.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,12 @@ type ReactionOptions struct {
218218
}
219219

220220
// CreateReaction creates reaction for issue or comment.
221-
func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
221+
func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
222222
if !setting.UI.ReactionsLookup.Contains(opts.Type) {
223223
return nil, ErrForbiddenIssueReaction{opts.Type}
224224
}
225225

226-
ctx, committer, err := db.TxContext(db.DefaultContext)
226+
ctx, committer, err := db.TxContext(ctx)
227227
if err != nil {
228228
return nil, err
229229
}
@@ -240,25 +240,6 @@ func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
240240
return reaction, nil
241241
}
242242

243-
// CreateIssueReaction creates a reaction on issue.
244-
func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, error) {
245-
return CreateReaction(&ReactionOptions{
246-
Type: content,
247-
DoerID: doerID,
248-
IssueID: issueID,
249-
})
250-
}
251-
252-
// CreateCommentReaction creates a reaction on comment.
253-
func CreateCommentReaction(doerID, issueID, commentID int64, content string) (*Reaction, error) {
254-
return CreateReaction(&ReactionOptions{
255-
Type: content,
256-
DoerID: doerID,
257-
IssueID: issueID,
258-
CommentID: commentID,
259-
})
260-
}
261-
262243
// DeleteReaction deletes reaction for issue or comment.
263244
func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
264245
reaction := &Reaction{

models/issues/reaction_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ import (
1919
func addReaction(t *testing.T, doerID, issueID, commentID int64, content string) {
2020
var reaction *issues_model.Reaction
2121
var err error
22-
if commentID == 0 {
23-
reaction, err = issues_model.CreateIssueReaction(doerID, issueID, content)
24-
} else {
25-
reaction, err = issues_model.CreateCommentReaction(doerID, issueID, commentID, content)
26-
}
22+
// NOTE: This doesn't do user blocking checking.
23+
reaction, err = issues_model.CreateReaction(db.DefaultContext, &issues_model.ReactionOptions{
24+
DoerID: doerID,
25+
IssueID: issueID,
26+
CommentID: commentID,
27+
Type: content,
28+
})
29+
2730
assert.NoError(t, err)
2831
assert.NotNil(t, reaction)
2932
}
@@ -49,7 +52,7 @@ func TestIssueAddDuplicateReaction(t *testing.T) {
4952

5053
addReaction(t, user1.ID, issue1ID, 0, "heart")
5154

52-
reaction, err := issues_model.CreateReaction(&issues_model.ReactionOptions{
55+
reaction, err := issues_model.CreateReaction(db.DefaultContext, &issues_model.ReactionOptions{
5356
DoerID: user1.ID,
5457
IssueID: issue1ID,
5558
Type: "heart",

models/repo/watch.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
user_model "code.gitea.io/gitea/models/user"
1111
"code.gitea.io/gitea/modules/setting"
1212
"code.gitea.io/gitea/modules/timeutil"
13+
14+
"xorm.io/builder"
1315
)
1416

1517
// WatchMode specifies what kind of watch the user has on a repository
@@ -142,6 +144,21 @@ func GetWatchers(ctx context.Context, repoID int64) ([]*Watch, error) {
142144
Find(&watches)
143145
}
144146

147+
// GetWatchersExcludeBlocked returns all watchers of given repository, whereby
148+
// the doer isn't blocked by one of the watchers.
149+
func GetWatchersExcludeBlocked(ctx context.Context, repoID, doerID int64) ([]*Watch, error) {
150+
watches := make([]*Watch, 0, 10)
151+
return watches, db.GetEngine(ctx).
152+
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
153+
Join("LEFT", "forgejo_blocked_user", "forgejo_blocked_user.user_id = `watch`.user_id").
154+
Where("`watch`.repo_id=?", repoID).
155+
And("`watch`.mode<>?", WatchModeDont).
156+
And("`user`.is_active=?", true).
157+
And("`user`.prohibit_login=?", false).
158+
And(builder.Or(builder.IsNull{"`forgejo_blocked_user`.block_id"}, builder.Neq{"`forgejo_blocked_user`.block_id": doerID})).
159+
Find(&watches)
160+
}
161+
145162
// GetRepoWatchersIDs returns IDs of watchers for a given repo ID
146163
// but avoids joining with `user` for performance reasons
147164
// User permissions must be verified elsewhere if required

0 commit comments

Comments
 (0)