mirror of
https://github.com/go-gitea/gitea.git
synced 2025-06-22 05:58:02 +02:00
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:    --------- Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
@ -42,120 +42,132 @@
|
||||
|
||||
-
|
||||
id: 8
|
||||
user_id: 15
|
||||
user_id: 10
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 9
|
||||
user_id: 15
|
||||
repo_id: 22
|
||||
user_id: 10
|
||||
repo_id: 32
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 10
|
||||
user_id: 15
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 11
|
||||
user_id: 15
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 12
|
||||
user_id: 15
|
||||
repo_id: 23
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 11
|
||||
id: 13
|
||||
user_id: 15
|
||||
repo_id: 24
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 12
|
||||
id: 14
|
||||
user_id: 15
|
||||
repo_id: 32
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 13
|
||||
id: 15
|
||||
user_id: 18
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 14
|
||||
id: 16
|
||||
user_id: 18
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 15
|
||||
id: 17
|
||||
user_id: 18
|
||||
repo_id: 23
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 16
|
||||
id: 18
|
||||
user_id: 18
|
||||
repo_id: 24
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 17
|
||||
id: 19
|
||||
user_id: 20
|
||||
repo_id: 24
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 18
|
||||
id: 20
|
||||
user_id: 20
|
||||
repo_id: 27
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 19
|
||||
id: 21
|
||||
user_id: 20
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 20
|
||||
id: 22
|
||||
user_id: 29
|
||||
repo_id: 4
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 21
|
||||
id: 23
|
||||
user_id: 29
|
||||
repo_id: 24
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 22
|
||||
id: 24
|
||||
user_id: 31
|
||||
repo_id: 27
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 23
|
||||
id: 25
|
||||
user_id: 31
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 24
|
||||
id: 26
|
||||
user_id: 38
|
||||
repo_id: 60
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 25
|
||||
id: 27
|
||||
user_id: 38
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 26
|
||||
id: 28
|
||||
user_id: 39
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 27
|
||||
id: 29
|
||||
user_id: 40
|
||||
repo_id: 61
|
||||
mode: 4
|
||||
|
@ -51,3 +51,15 @@
|
||||
repo_id: 60
|
||||
user_id: 38
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 10
|
||||
repo_id: 21
|
||||
user_id: 10
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 11
|
||||
repo_id: 32
|
||||
user_id: 10
|
||||
mode: 2 # write
|
||||
|
@ -14,3 +14,7 @@
|
||||
id: 4
|
||||
assignee_id: 2
|
||||
issue_id: 17
|
||||
-
|
||||
id: 5
|
||||
assignee_id: 10
|
||||
issue_id: 6
|
||||
|
@ -5,3 +5,19 @@
|
||||
repo_id: 3
|
||||
created_unix: 1553610671
|
||||
updated_unix: 1553610671
|
||||
|
||||
-
|
||||
id: 2
|
||||
doer_id: 16
|
||||
recipient_id: 10
|
||||
repo_id: 21
|
||||
created_unix: 1553610671
|
||||
updated_unix: 1553610671
|
||||
|
||||
-
|
||||
id: 3
|
||||
doer_id: 3
|
||||
recipient_id: 10
|
||||
repo_id: 32
|
||||
created_unix: 1553610671
|
||||
updated_unix: 1553610671
|
||||
|
@ -614,8 +614,8 @@
|
||||
owner_name: user16
|
||||
lower_name: big_test_public_3
|
||||
name: big_test_public_3
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_watches: 1
|
||||
num_stars: 1
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
@ -945,8 +945,8 @@
|
||||
owner_name: org3
|
||||
lower_name: repo21
|
||||
name: repo21
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_watches: 1
|
||||
num_stars: 1
|
||||
num_forks: 0
|
||||
num_issues: 2
|
||||
num_closed_issues: 0
|
||||
|
@ -7,3 +7,13 @@
|
||||
id: 2
|
||||
uid: 2
|
||||
repo_id: 4
|
||||
|
||||
-
|
||||
id: 3
|
||||
uid: 10
|
||||
repo_id: 21
|
||||
|
||||
-
|
||||
id: 4
|
||||
uid: 10
|
||||
repo_id: 32
|
||||
|
@ -361,7 +361,7 @@
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_stars: 2
|
||||
num_repos: 3
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
|
19
models/fixtures/user_blocking.yml
Normal file
19
models/fixtures/user_blocking.yml
Normal file
@ -0,0 +1,19 @@
|
||||
-
|
||||
id: 1
|
||||
blocker_id: 2
|
||||
blockee_id: 29
|
||||
|
||||
-
|
||||
id: 2
|
||||
blocker_id: 17
|
||||
blockee_id: 28
|
||||
|
||||
-
|
||||
id: 3
|
||||
blocker_id: 2
|
||||
blockee_id: 34
|
||||
|
||||
-
|
||||
id: 4
|
||||
blocker_id: 50
|
||||
blockee_id: 34
|
@ -27,3 +27,15 @@
|
||||
user_id: 11
|
||||
repo_id: 1
|
||||
mode: 3 # auto
|
||||
|
||||
-
|
||||
id: 6
|
||||
user_id: 10
|
||||
repo_id: 21
|
||||
mode: 1 # normal
|
||||
|
||||
-
|
||||
id: 7
|
||||
user_id: 10
|
||||
repo_id: 32
|
||||
mode: 1 # normal
|
||||
|
@ -64,6 +64,27 @@ func IsUserAssignedToIssue(ctx context.Context, issue *Issue, user *user_model.U
|
||||
return db.Exist[IssueAssignees](ctx, builder.Eq{"assignee_id": user.ID, "issue_id": issue.ID})
|
||||
}
|
||||
|
||||
type AssignedIssuesOptions struct {
|
||||
db.ListOptions
|
||||
AssigneeID int64
|
||||
RepoOwnerID int64
|
||||
}
|
||||
|
||||
func (opts *AssignedIssuesOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.AssigneeID != 0 {
|
||||
cond = cond.And(builder.In("issue.id", builder.Select("issue_id").From("issue_assignees").Where(builder.Eq{"assignee_id": opts.AssigneeID})))
|
||||
}
|
||||
if opts.RepoOwnerID != 0 {
|
||||
cond = cond.And(builder.In("issue.repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": opts.RepoOwnerID})))
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func GetAssignedIssues(ctx context.Context, opts *AssignedIssuesOptions) ([]*Issue, int64, error) {
|
||||
return db.FindAndCount[Issue](ctx, opts)
|
||||
}
|
||||
|
||||
// ToggleIssueAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
|
@ -517,6 +517,15 @@ func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_mo
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %w", issue.ID, err)
|
||||
}
|
||||
|
||||
notBlocked := make([]*user_model.User, 0, len(mentions))
|
||||
for _, user := range mentions {
|
||||
if !user_model.IsUserBlockedBy(ctx, doer, user.ID) {
|
||||
notBlocked = append(notBlocked, user)
|
||||
}
|
||||
}
|
||||
mentions = notBlocked
|
||||
|
||||
if err = UpdateIssueMentions(ctx, issue.ID, mentions); err != nil {
|
||||
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %w", issue.ID, err)
|
||||
}
|
||||
|
@ -214,6 +214,10 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
|
||||
if !perm.CanReadIssuesOrPulls(refIssue.IsPull) {
|
||||
return nil, references.XRefActionNone, nil
|
||||
}
|
||||
if user_model.IsUserBlockedBy(stdCtx, ctx.Doer, refIssue.PosterID, refIssue.Repo.OwnerID) {
|
||||
return nil, references.XRefActionNone, nil
|
||||
}
|
||||
|
||||
// Accept close/reopening actions only if the poster is able to close the
|
||||
// referenced issue manually at this moment. The only exception is
|
||||
// the poster of a new PR referencing an issue on the same repo: then the merger
|
||||
|
@ -240,25 +240,6 @@ func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro
|
||||
return reaction, nil
|
||||
}
|
||||
|
||||
// CreateIssueReaction creates a reaction on issue.
|
||||
func CreateIssueReaction(ctx context.Context, doerID, issueID int64, content string) (*Reaction, error) {
|
||||
return CreateReaction(ctx, &ReactionOptions{
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCommentReaction creates a reaction on comment.
|
||||
func CreateCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) (*Reaction, error) {
|
||||
return CreateReaction(ctx, &ReactionOptions{
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
CommentID: commentID,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteReaction deletes reaction for issue or comment.
|
||||
func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
|
||||
reaction := &Reaction{
|
||||
|
@ -1,174 +0,0 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func addReaction(t *testing.T, doerID, issueID, commentID int64, content string) {
|
||||
var reaction *issues_model.Reaction
|
||||
var err error
|
||||
if commentID == 0 {
|
||||
reaction, err = issues_model.CreateIssueReaction(db.DefaultContext, doerID, issueID, content)
|
||||
} else {
|
||||
reaction, err = issues_model.CreateCommentReaction(db.DefaultContext, doerID, issueID, commentID, content)
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, reaction)
|
||||
}
|
||||
|
||||
func TestIssueAddReaction(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, 0, "heart")
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID})
|
||||
}
|
||||
|
||||
func TestIssueAddDuplicateReaction(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, 0, "heart")
|
||||
|
||||
reaction, err := issues_model.CreateReaction(db.DefaultContext, &issues_model.ReactionOptions{
|
||||
DoerID: user1.ID,
|
||||
IssueID: issue1ID,
|
||||
Type: "heart",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, issues_model.ErrReactionAlreadyExist{Reaction: "heart"}, err)
|
||||
|
||||
existingR := unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID})
|
||||
assert.Equal(t, existingR.ID, reaction.ID)
|
||||
}
|
||||
|
||||
func TestIssueDeleteReaction(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, 0, "heart")
|
||||
|
||||
err := issues_model.DeleteIssueReaction(db.DefaultContext, user1.ID, issue1ID, "heart")
|
||||
assert.NoError(t, err)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID})
|
||||
}
|
||||
|
||||
func TestIssueReactionCount(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
setting.UI.ReactionMaxUserNum = 2
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
ghost := user_model.NewGhostUser()
|
||||
|
||||
var issueID int64 = 2
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
addReaction(t, user1.ID, issueID, 0, "heart")
|
||||
addReaction(t, user2.ID, issueID, 0, "heart")
|
||||
addReaction(t, org3.ID, issueID, 0, "heart")
|
||||
addReaction(t, org3.ID, issueID, 0, "+1")
|
||||
addReaction(t, user4.ID, issueID, 0, "+1")
|
||||
addReaction(t, user4.ID, issueID, 0, "heart")
|
||||
addReaction(t, ghost.ID, issueID, 0, "-1")
|
||||
|
||||
reactionsList, _, err := issues_model.FindReactions(db.DefaultContext, issues_model.FindReactionsOptions{
|
||||
IssueID: issueID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reactionsList, 7)
|
||||
_, err = reactionsList.LoadUsers(db.DefaultContext, repo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reactions := reactionsList.GroupByType()
|
||||
assert.Len(t, reactions["heart"], 4)
|
||||
assert.Equal(t, 2, reactions["heart"].GetMoreUserCount())
|
||||
assert.Equal(t, user1.Name+", "+user2.Name, reactions["heart"].GetFirstUsers())
|
||||
assert.True(t, reactions["heart"].HasUser(1))
|
||||
assert.False(t, reactions["heart"].HasUser(5))
|
||||
assert.False(t, reactions["heart"].HasUser(0))
|
||||
assert.Len(t, reactions["+1"], 2)
|
||||
assert.Equal(t, 0, reactions["+1"].GetMoreUserCount())
|
||||
assert.Len(t, reactions["-1"], 1)
|
||||
}
|
||||
|
||||
func TestIssueCommentAddReaction(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
var comment1ID int64 = 1
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID})
|
||||
}
|
||||
|
||||
func TestIssueCommentDeleteReaction(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
var comment1ID int64 = 1
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, user2.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, org3.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, user4.ID, issue1ID, comment1ID, "+1")
|
||||
|
||||
reactionsList, _, err := issues_model.FindReactions(db.DefaultContext, issues_model.FindReactionsOptions{
|
||||
IssueID: issue1ID,
|
||||
CommentID: comment1ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reactionsList, 4)
|
||||
|
||||
reactions := reactionsList.GroupByType()
|
||||
assert.Len(t, reactions["heart"], 3)
|
||||
assert.Len(t, reactions["+1"], 1)
|
||||
}
|
||||
|
||||
func TestIssueCommentReactionCount(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
var comment1ID int64 = 1
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
|
||||
assert.NoError(t, issues_model.DeleteCommentReaction(db.DefaultContext, user1.ID, issue1ID, comment1ID, "heart"))
|
||||
|
||||
unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID})
|
||||
}
|
@ -560,6 +560,8 @@ var migrations = []Migration{
|
||||
NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
|
||||
// v287 -> v288
|
||||
NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges),
|
||||
// v288 -> v289
|
||||
NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
26
models/migrations/v1_22/v288.go
Normal file
26
models/migrations/v1_22/v288.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type Blocking struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
BlockerID int64 `xorm:"UNIQUE(block)"`
|
||||
BlockeeID int64 `xorm:"UNIQUE(block)"`
|
||||
Note string
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
}
|
||||
|
||||
func (*Blocking) TableName() string {
|
||||
return "user_blocking"
|
||||
}
|
||||
|
||||
func AddUserBlockingTable(x *xorm.Engine) error {
|
||||
return x.Sync(&Blocking{})
|
||||
}
|
@ -12,15 +12,16 @@ import (
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
// RemoveOrgUser removes user from given organization.
|
||||
func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *user_model.User) error {
|
||||
ou := new(organization.OrgUser)
|
||||
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("uid=?", userID).
|
||||
And("org_id=?", orgID).
|
||||
Where("uid=?", user.ID).
|
||||
And("org_id=?", org.ID).
|
||||
Get(ou)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get org-user: %w", err)
|
||||
@ -28,13 +29,8 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserByID [%d]: %w", orgID, err)
|
||||
}
|
||||
|
||||
// Check if the user to delete is the last member in owner team.
|
||||
if isOwner, err := organization.IsOrganizationOwner(ctx, orgID, userID); err != nil {
|
||||
if isOwner, err := organization.IsOrganizationOwner(ctx, org.ID, user.ID); err != nil {
|
||||
return err
|
||||
} else if isOwner {
|
||||
t, err := organization.GetOwnerTeam(ctx, org.ID)
|
||||
@ -45,8 +41,8 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
if err := t.LoadMembers(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if t.Members[0].ID == userID {
|
||||
return organization.ErrLastOrgOwner{UID: userID}
|
||||
if t.Members[0].ID == user.ID {
|
||||
return organization.ErrLastOrgOwner{UID: user.ID}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,28 +55,32 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
|
||||
if _, err := db.DeleteByID[organization.OrgUser](ctx, ou.ID); err != nil {
|
||||
return err
|
||||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
|
||||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", org.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all repository accesses and unwatch them.
|
||||
env, err := organization.AccessibleReposEnv(ctx, org, userID)
|
||||
env, err := organization.AccessibleReposEnv(ctx, org, user.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AccessibleReposEnv: %w", err)
|
||||
}
|
||||
repoIDs, err := env.RepoIDs(1, org.NumRepos)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserRepositories [%d]: %w", userID, err)
|
||||
return fmt.Errorf("GetUserRepositories [%d]: %w", user.ID, err)
|
||||
}
|
||||
for _, repoID := range repoIDs {
|
||||
if err = repo_model.WatchRepo(ctx, userID, repoID, false); err != nil {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(repoIDs) > 0 {
|
||||
if _, err = db.GetEngine(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Where("user_id = ?", user.ID).
|
||||
In("repo_id", repoIDs).
|
||||
Delete(new(access_model.Access)); err != nil {
|
||||
return err
|
||||
@ -88,12 +88,12 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
}
|
||||
|
||||
// Delete member in their teams.
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.ID, userID)
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.ID, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range teams {
|
||||
if err = removeTeamMember(ctx, t, userID); err != nil {
|
||||
if err = removeTeamMember(ctx, t, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func AddRepository(ctx context.Context, t *organization.Team, repo *repo_model.R
|
||||
return fmt.Errorf("getMembers: %w", err)
|
||||
}
|
||||
for _, u := range t.Members {
|
||||
if err = repo_model.WatchRepo(ctx, u.ID, repo.ID, true); err != nil {
|
||||
if err = repo_model.WatchRepo(ctx, u, repo, true); err != nil {
|
||||
return fmt.Errorf("watchRepo: %w", err)
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,7 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error
|
||||
continue
|
||||
}
|
||||
|
||||
if err = repo_model.WatchRepo(ctx, user.ID, repo.ID, false); err != nil {
|
||||
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -341,7 +341,7 @@ func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||
}
|
||||
|
||||
for _, tm := range t.Members {
|
||||
if err := removeInvalidOrgUser(ctx, tm.ID, t.OrgID); err != nil {
|
||||
if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -356,19 +356,23 @@ func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||
|
||||
// AddTeamMember adds new membership of given team to given organization,
|
||||
// the user will have membership to given organization automatically when needed.
|
||||
func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
|
||||
isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
|
||||
func AddTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
if user_model.IsUserBlockedBy(ctx, user, team.OrgID) {
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
|
||||
isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||
if err != nil || isAlreadyMember {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := organization.AddOrgUser(ctx, team.OrgID, userID); err != nil {
|
||||
if err := organization.AddOrgUser(ctx, team.OrgID, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
// check in transaction
|
||||
isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
|
||||
isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||
if err != nil || isAlreadyMember {
|
||||
return err
|
||||
}
|
||||
@ -376,7 +380,7 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if err := db.Insert(ctx, &organization.TeamUser{
|
||||
UID: userID,
|
||||
UID: user.ID,
|
||||
OrgID: team.OrgID,
|
||||
TeamID: team.ID,
|
||||
}); err != nil {
|
||||
@ -392,7 +396,7 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
subQuery := builder.Select("repo_id").From("team_repo").
|
||||
Where(builder.Eq{"team_id": team.ID})
|
||||
|
||||
if _, err := sess.Where("user_id=?", userID).
|
||||
if _, err := sess.Where("user_id=?", user.ID).
|
||||
In("repo_id", subQuery).
|
||||
And("mode < ?", team.AccessMode).
|
||||
SetExpr("mode", team.AccessMode).
|
||||
@ -402,14 +406,14 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
|
||||
// for not exist access
|
||||
var repoIDs []int64
|
||||
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
|
||||
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID})
|
||||
if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
|
||||
return fmt.Errorf("select id accesses: %w", err)
|
||||
}
|
||||
|
||||
accesses := make([]*access_model.Access, 0, 100)
|
||||
for i, repoID := range repoIDs {
|
||||
accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
|
||||
accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode})
|
||||
if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
|
||||
if err = db.Insert(ctx, accesses); err != nil {
|
||||
return fmt.Errorf("insert new user accesses: %w", err)
|
||||
@ -430,10 +434,11 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
if err := team.LoadRepositories(ctx); err != nil {
|
||||
log.Error("team.LoadRepositories failed: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment
|
||||
go func(repos []*repo_model.Repository) {
|
||||
for _, repo := range repos {
|
||||
if err = repo_model.WatchRepo(db.DefaultContext, userID, repo.ID, true); err != nil {
|
||||
if err = repo_model.WatchRepo(db.DefaultContext, user, repo, true); err != nil {
|
||||
log.Error("watch repo failed: %v", err)
|
||||
}
|
||||
}
|
||||
@ -443,16 +448,16 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
|
||||
func removeTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
e := db.GetEngine(ctx)
|
||||
isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
|
||||
isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||
if err != nil || !isMember {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the user to delete is the last member in owner team.
|
||||
if team.IsOwnerTeam() && team.NumMembers == 1 {
|
||||
return organization.ErrLastOrgOwner{UID: userID}
|
||||
return organization.ErrLastOrgOwner{UID: user.ID}
|
||||
}
|
||||
|
||||
team.NumMembers--
|
||||
@ -462,7 +467,7 @@ func removeTeamMember(ctx context.Context, team *organization.Team, userID int64
|
||||
}
|
||||
|
||||
if _, err := e.Delete(&organization.TeamUser{
|
||||
UID: userID,
|
||||
UID: user.ID,
|
||||
OrgID: team.OrgID,
|
||||
TeamID: team.ID,
|
||||
}); err != nil {
|
||||
@ -476,76 +481,76 @@ func removeTeamMember(ctx context.Context, team *organization.Team, userID int64
|
||||
|
||||
// Delete access to team repositories.
|
||||
for _, repo := range team.Repos {
|
||||
if err := access_model.RecalculateUserAccess(ctx, repo, userID); err != nil {
|
||||
if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove watches from now unaccessible
|
||||
if err := ReconsiderWatches(ctx, repo, userID); err != nil {
|
||||
if err := ReconsiderWatches(ctx, repo, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove issue assignments from now unaccessible
|
||||
if err := ReconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
|
||||
if err := ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return removeInvalidOrgUser(ctx, userID, team.OrgID)
|
||||
return removeInvalidOrgUser(ctx, team.OrgID, user)
|
||||
}
|
||||
|
||||
func removeInvalidOrgUser(ctx context.Context, userID, orgID int64) error {
|
||||
func removeInvalidOrgUser(ctx context.Context, orgID int64, user *user_model.User) error {
|
||||
// Check if the user is a member of any team in the organization.
|
||||
if count, err := db.GetEngine(ctx).Count(&organization.TeamUser{
|
||||
UID: userID,
|
||||
UID: user.ID,
|
||||
OrgID: orgID,
|
||||
}); err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return RemoveOrgUser(ctx, orgID, userID)
|
||||
org, err := organization.GetOrgByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RemoveOrgUser(ctx, org, user)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTeamMember removes member from given team of given organization.
|
||||
func RemoveTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
|
||||
func RemoveTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
if err := removeTeamMember(ctx, team, userID); err != nil {
|
||||
if err := removeTeamMember(ctx, team, user); err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
user, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": user.ID}).
|
||||
In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
|
||||
Delete(&issues_model.IssueAssignees{}); err != nil {
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", user.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
|
||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||
if has, err := access_model.HasAccess(ctx, user.ID, repo); err != nil || has {
|
||||
return err
|
||||
}
|
||||
if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
if err := repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repository
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID)
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID)
|
||||
}
|
||||
|
@ -21,33 +21,42 @@ import (
|
||||
func TestTeam_AddMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
test := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID})
|
||||
test := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}, &user_model.User{ID: team.OrgID})
|
||||
}
|
||||
test(1, 2)
|
||||
test(1, 4)
|
||||
test(3, 2)
|
||||
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
test(team1, user2)
|
||||
test(team1, user4)
|
||||
test(team3, user2)
|
||||
}
|
||||
|
||||
func TestTeam_RemoveMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testSuccess := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID})
|
||||
testSuccess := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
||||
}
|
||||
testSuccess(1, 4)
|
||||
testSuccess(2, 2)
|
||||
testSuccess(3, 2)
|
||||
testSuccess(3, unittest.NonexistentID)
|
||||
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
err := RemoveTeamMember(db.DefaultContext, team, 2)
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team2 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
testSuccess(team1, user4)
|
||||
testSuccess(team2, user2)
|
||||
testSuccess(team3, user2)
|
||||
|
||||
err := RemoveTeamMember(db.DefaultContext, team1, user2)
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
}
|
||||
|
||||
@ -120,33 +129,42 @@ func TestDeleteTeam(t *testing.T) {
|
||||
func TestAddTeamMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
test := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID})
|
||||
test := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}, &user_model.User{ID: team.OrgID})
|
||||
}
|
||||
test(1, 2)
|
||||
test(1, 4)
|
||||
test(3, 2)
|
||||
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
test(team1, user2)
|
||||
test(team1, user4)
|
||||
test(team3, user2)
|
||||
}
|
||||
|
||||
func TestRemoveTeamMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testSuccess := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID})
|
||||
testSuccess := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
||||
}
|
||||
testSuccess(1, 4)
|
||||
testSuccess(2, 2)
|
||||
testSuccess(3, 2)
|
||||
testSuccess(3, unittest.NonexistentID)
|
||||
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
err := RemoveTeamMember(db.DefaultContext, team, 2)
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team2 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
testSuccess(team1, user4)
|
||||
testSuccess(team2, user2)
|
||||
testSuccess(team3, user2)
|
||||
|
||||
err := RemoveTeamMember(db.DefaultContext, team1, user2)
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
}
|
||||
|
||||
@ -155,15 +173,15 @@ func TestRepository_RecalculateAccesses3(t *testing.T) {
|
||||
team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5})
|
||||
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
|
||||
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
// adding user29 to team5 should add an explicit access row for repo 23
|
||||
// even though repo 23 is public
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29.ID))
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29))
|
||||
|
||||
has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
|
||||
has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
@ -16,22 +16,27 @@ import (
|
||||
|
||||
func TestUser_RemoveMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
|
||||
// remove a user that is a member
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: 4, OrgID: 3})
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: user4.ID, OrgID: org.ID})
|
||||
prevNumMembers := org.NumMembers
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org.ID, 4))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 4, OrgID: 3})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user4))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: user4.ID, OrgID: org.ID})
|
||||
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID})
|
||||
assert.Equal(t, prevNumMembers-1, org.NumMembers)
|
||||
|
||||
// remove a user that is not a member
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3})
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: user5.ID, OrgID: org.ID})
|
||||
prevNumMembers = org.NumMembers
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org.ID, 5))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user5))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: user5.ID, OrgID: org.ID})
|
||||
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID})
|
||||
assert.Equal(t, prevNumMembers, org.NumMembers)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
|
||||
@ -39,23 +44,31 @@ func TestUser_RemoveMember(t *testing.T) {
|
||||
|
||||
func TestRemoveOrgUser(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
testSuccess := func(orgID, userID int64) {
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
|
||||
|
||||
testSuccess := func(org *organization.Organization, user *user_model.User) {
|
||||
expectedNumMembers := org.NumMembers
|
||||
if unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) {
|
||||
if unittest.BeanExists(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID}) {
|
||||
expectedNumMembers--
|
||||
}
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, orgID, userID))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: orgID, UID: userID})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID})
|
||||
assert.EqualValues(t, expectedNumMembers, org.NumMembers)
|
||||
}
|
||||
testSuccess(3, 4)
|
||||
testSuccess(3, 4)
|
||||
|
||||
err := RemoveOrgUser(db.DefaultContext, 7, 5)
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
org7 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 7})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
|
||||
testSuccess(org3, user4)
|
||||
|
||||
org3 = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
testSuccess(org3, user4)
|
||||
|
||||
err := RemoveOrgUser(db.DefaultContext, org7, user5)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: 7, UID: 5})
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: org7.ID, UID: user5.ID})
|
||||
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
|
||||
}
|
||||
|
@ -400,6 +400,7 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
|
||||
&TeamUnit{OrgID: org.ID},
|
||||
&TeamInvite{OrgID: org.ID},
|
||||
&secret_model.Secret{OwnerID: org.ID},
|
||||
&user_model.Blocking{BlockerID: org.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("DeleteBeans: %w", err)
|
||||
}
|
||||
|
@ -30,14 +30,6 @@ func IsTeamMember(ctx context.Context, orgID, teamID, userID int64) (bool, error
|
||||
Exist()
|
||||
}
|
||||
|
||||
// GetTeamUsersByTeamID returns team users for a team
|
||||
func GetTeamUsersByTeamID(ctx context.Context, teamID int64) ([]*TeamUser, error) {
|
||||
teamUsers := make([]*TeamUser, 0, 10)
|
||||
return teamUsers, db.GetEngine(ctx).
|
||||
Where("team_id=?", teamID).
|
||||
Find(&teamUsers)
|
||||
}
|
||||
|
||||
// SearchMembersOptions holds the search options
|
||||
type SearchMembersOptions struct {
|
||||
db.ListOptions
|
||||
|
@ -128,9 +128,9 @@ func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap
|
||||
|
||||
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
|
||||
func refreshCollaboratorAccesses(ctx context.Context, repoID int64, accessMap map[int64]*userAccess) error {
|
||||
collaborators, err := repo_model.GetCollaborators(ctx, repoID, db.ListOptions{})
|
||||
collaborators, _, err := repo_model.GetCollaborators(ctx, &repo_model.FindCollaborationOptions{RepoID: repoID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getCollaborations: %w", err)
|
||||
return fmt.Errorf("GetCollaborators: %w", err)
|
||||
}
|
||||
for _, c := range collaborators {
|
||||
if c.User.IsGhost() {
|
||||
|
@ -36,14 +36,44 @@ type Collaborator struct {
|
||||
Collaboration *Collaboration
|
||||
}
|
||||
|
||||
type FindCollaborationOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
RepoOwnerID int64
|
||||
CollaboratorID int64
|
||||
}
|
||||
|
||||
func (opts *FindCollaborationOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"collaboration.repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.RepoOwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"repository.owner_id": opts.RepoOwnerID})
|
||||
}
|
||||
if opts.CollaboratorID != 0 {
|
||||
cond = cond.And(builder.Eq{"collaboration.user_id": opts.CollaboratorID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts *FindCollaborationOptions) ToJoins() []db.JoinFunc {
|
||||
if opts.RepoOwnerID != 0 {
|
||||
return []db.JoinFunc{
|
||||
func(e db.Engine) error {
|
||||
e.Join("INNER", "repository", "repository.id = collaboration.repo_id")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCollaborators returns the collaborators for a repository
|
||||
func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
|
||||
collaborations, err := db.Find[Collaboration](ctx, FindCollaborationOptions{
|
||||
ListOptions: listOptions,
|
||||
RepoID: repoID,
|
||||
})
|
||||
func GetCollaborators(ctx context.Context, opts *FindCollaborationOptions) ([]*Collaborator, int64, error) {
|
||||
collaborations, total, err := db.FindAndCount[Collaboration](ctx, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db.Find[Collaboration]: %w", err)
|
||||
return nil, 0, fmt.Errorf("db.FindAndCount[Collaboration]: %w", err)
|
||||
}
|
||||
|
||||
collaborators := make([]*Collaborator, 0, len(collaborations))
|
||||
@ -54,7 +84,7 @@ func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOpti
|
||||
|
||||
usersMap := make(map[int64]*user_model.User)
|
||||
if err := db.GetEngine(ctx).In("id", userIDs).Find(&usersMap); err != nil {
|
||||
return nil, fmt.Errorf("Find users map by user ids: %w", err)
|
||||
return nil, 0, fmt.Errorf("Find users map by user ids: %w", err)
|
||||
}
|
||||
|
||||
for _, c := range collaborations {
|
||||
@ -67,7 +97,7 @@ func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOpti
|
||||
Collaboration: c,
|
||||
})
|
||||
}
|
||||
return collaborators, nil
|
||||
return collaborators, total, nil
|
||||
}
|
||||
|
||||
// GetCollaboration get collaboration for a repository id with a user id
|
||||
@ -88,15 +118,6 @@ func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
|
||||
}
|
||||
|
||||
type FindCollaborationOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func (opts FindCollaborationOptions) ToConds() builder.Cond {
|
||||
return builder.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
||||
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
|
||||
func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid int64, mode perm.AccessMode) error {
|
||||
// Discard invalid input
|
||||
|
@ -19,7 +19,7 @@ func TestRepository_GetCollaborators(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
test := func(repoID int64) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
collaborators, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{})
|
||||
collaborators, _, err := repo_model.GetCollaborators(db.DefaultContext, &repo_model.FindCollaborationOptions{RepoID: repo.ID})
|
||||
assert.NoError(t, err)
|
||||
expectedLen, err := db.GetEngine(db.DefaultContext).Count(&repo_model.Collaboration{RepoID: repoID})
|
||||
assert.NoError(t, err)
|
||||
@ -37,11 +37,17 @@ func TestRepository_GetCollaborators(t *testing.T) {
|
||||
// Test db.ListOptions
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
|
||||
|
||||
collaborators1, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{PageSize: 1, Page: 1})
|
||||
collaborators1, _, err := repo_model.GetCollaborators(db.DefaultContext, &repo_model.FindCollaborationOptions{
|
||||
ListOptions: db.ListOptions{PageSize: 1, Page: 1},
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, collaborators1, 1)
|
||||
|
||||
collaborators2, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{PageSize: 1, Page: 2})
|
||||
collaborators2, _, err := repo_model.GetCollaborators(db.DefaultContext, &repo_model.FindCollaborationOptions{
|
||||
ListOptions: db.ListOptions{PageSize: 1, Page: 2},
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, collaborators2, 1)
|
||||
|
||||
@ -85,31 +91,6 @@ func TestRepository_ChangeCollaborationAccessMode(t *testing.T) {
|
||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
||||
}
|
||||
|
||||
func TestRepository_CountCollaborators(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
count, err := db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{
|
||||
RepoID: repo1.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, count)
|
||||
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
|
||||
count, err = db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{
|
||||
RepoID: repo2.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, count)
|
||||
|
||||
// Non-existent repository.
|
||||
count, err = db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{
|
||||
RepoID: unittest.NonexistentID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, count)
|
||||
}
|
||||
|
||||
func TestRepository_IsOwnerMemberCollaborator(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
@ -64,16 +64,17 @@ func TestRepoAPIURL(t *testing.T) {
|
||||
|
||||
func TestWatchRepo(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
const repoID = 3
|
||||
const userID = 2
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, userID, repoID, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repoID, UserID: userID})
|
||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, userID, repoID, false))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repoID, UserID: userID})
|
||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID})
|
||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, false))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
||||
}
|
||||
|
||||
func TestMetas(t *testing.T) {
|
||||
|
@ -24,26 +24,30 @@ func init() {
|
||||
}
|
||||
|
||||
// StarRepo or unstar repository.
|
||||
func StarRepo(ctx context.Context, userID, repoID int64, star bool) error {
|
||||
func StarRepo(ctx context.Context, doer *user_model.User, repo *Repository, star bool) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
staring := IsStaring(ctx, userID, repoID)
|
||||
staring := IsStaring(ctx, doer.ID, repo.ID)
|
||||
|
||||
if star {
|
||||
if user_model.IsUserBlockedBy(ctx, doer, repo.OwnerID) {
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
|
||||
if staring {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.Insert(ctx, &Star{UID: userID, RepoID: repoID}); err != nil {
|
||||
if err := db.Insert(ctx, &Star{UID: doer.ID, RepoID: repo.ID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoID); err != nil {
|
||||
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", userID); err != nil {
|
||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", doer.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@ -51,13 +55,13 @@ func StarRepo(ctx context.Context, userID, repoID int64, star bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := db.DeleteByBean(ctx, &Star{UID: userID, RepoID: repoID}); err != nil {
|
||||
if _, err := db.DeleteByBean(ctx, &Star{UID: doer.ID, RepoID: repo.ID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil {
|
||||
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID); err != nil {
|
||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", doer.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -9,21 +9,24 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStarRepo(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
const userID = 2
|
||||
const repoID = 1
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, false))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, user, repo, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, user, repo, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, user, repo, false))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
}
|
||||
|
||||
func TestIsStaring(t *testing.T) {
|
||||
@ -54,17 +57,18 @@ func TestRepository_GetStargazers2(t *testing.T) {
|
||||
|
||||
func TestClearRepoStars(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
const userID = 2
|
||||
const repoID = 1
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, false))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
assert.NoError(t, repo_model.ClearRepoStars(db.DefaultContext, repoID))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID})
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, user, repo, true))
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, user, repo, false))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
assert.NoError(t, repo_model.ClearRepoStars(db.DefaultContext, repo.ID))
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
|
||||
|
||||
gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, gazers, 0)
|
||||
|
@ -16,47 +16,82 @@ import (
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type StarredReposOptions struct {
|
||||
db.ListOptions
|
||||
StarrerID int64
|
||||
RepoOwnerID int64
|
||||
IncludePrivate bool
|
||||
}
|
||||
|
||||
func (opts *StarredReposOptions) ToConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"star.uid": opts.StarrerID,
|
||||
}
|
||||
if opts.RepoOwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{
|
||||
"repository.owner_id": opts.RepoOwnerID,
|
||||
})
|
||||
}
|
||||
if !opts.IncludePrivate {
|
||||
cond = cond.And(builder.Eq{
|
||||
"repository.is_private": false,
|
||||
})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts *StarredReposOptions) ToJoins() []db.JoinFunc {
|
||||
return []db.JoinFunc{
|
||||
func(e db.Engine) error {
|
||||
e.Join("INNER", "star", "`repository`.id=`star`.repo_id")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetStarredRepos returns the repos starred by a particular user
|
||||
func GetStarredRepos(ctx context.Context, userID int64, private bool, listOptions db.ListOptions) ([]*Repository, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where("star.uid=?", userID).
|
||||
Join("LEFT", "star", "`repository`.id=`star`.repo_id")
|
||||
if !private {
|
||||
sess = sess.And("is_private=?", false)
|
||||
func GetStarredRepos(ctx context.Context, opts *StarredReposOptions) ([]*Repository, error) {
|
||||
return db.Find[Repository](ctx, opts)
|
||||
}
|
||||
|
||||
type WatchedReposOptions struct {
|
||||
db.ListOptions
|
||||
WatcherID int64
|
||||
RepoOwnerID int64
|
||||
IncludePrivate bool
|
||||
}
|
||||
|
||||
func (opts *WatchedReposOptions) ToConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"watch.user_id": opts.WatcherID,
|
||||
}
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
||||
repos := make([]*Repository, 0, listOptions.PageSize)
|
||||
return repos, sess.Find(&repos)
|
||||
if opts.RepoOwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{
|
||||
"repository.owner_id": opts.RepoOwnerID,
|
||||
})
|
||||
}
|
||||
if !opts.IncludePrivate {
|
||||
cond = cond.And(builder.Eq{
|
||||
"repository.is_private": false,
|
||||
})
|
||||
}
|
||||
return cond.And(builder.Neq{
|
||||
"watch.mode": WatchModeDont,
|
||||
})
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, 10)
|
||||
return repos, sess.Find(&repos)
|
||||
func (opts *WatchedReposOptions) ToJoins() []db.JoinFunc {
|
||||
return []db.JoinFunc{
|
||||
func(e db.Engine) error {
|
||||
e.Join("INNER", "watch", "`repository`.id=`watch`.repo_id")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetWatchedRepos returns the repos watched by a particular user
|
||||
func GetWatchedRepos(ctx context.Context, userID int64, private bool, listOptions db.ListOptions) ([]*Repository, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where("watch.user_id=?", userID).
|
||||
And("`watch`.mode<>?", WatchModeDont).
|
||||
Join("LEFT", "watch", "`repository`.id=`watch`.repo_id")
|
||||
if !private {
|
||||
sess = sess.And("is_private=?", false)
|
||||
}
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
||||
repos := make([]*Repository, 0, listOptions.PageSize)
|
||||
total, err := sess.FindAndCount(&repos)
|
||||
return repos, total, err
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, 10)
|
||||
total, err := sess.FindAndCount(&repos)
|
||||
return repos, total, err
|
||||
func GetWatchedRepos(ctx context.Context, opts *WatchedReposOptions) ([]*Repository, int64, error) {
|
||||
return db.FindAndCount[Repository](ctx, opts)
|
||||
}
|
||||
|
||||
// GetRepoAssignees returns all users that have write access and can be assigned to issues
|
||||
|
@ -25,10 +25,8 @@ func TestRepoAssignees(t *testing.T) {
|
||||
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
|
||||
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 3)
|
||||
assert.Equal(t, users[0].ID, int64(15))
|
||||
assert.Equal(t, users[1].ID, int64(18))
|
||||
assert.Equal(t, users[2].ID, int64(16))
|
||||
assert.Len(t, users, 4)
|
||||
assert.ElementsMatch(t, []int64{10, 15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID})
|
||||
}
|
||||
|
||||
func TestRepoGetReviewers(t *testing.T) {
|
||||
|
@ -104,29 +104,23 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error)
|
||||
return err
|
||||
}
|
||||
|
||||
// WatchRepoMode watch repository in specific mode.
|
||||
func WatchRepoMode(ctx context.Context, userID, repoID int64, mode WatchMode) (err error) {
|
||||
var watch Watch
|
||||
if watch, err = GetWatch(ctx, userID, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
return watchRepoMode(ctx, watch, mode)
|
||||
}
|
||||
|
||||
// WatchRepo watch or unwatch repository.
|
||||
func WatchRepo(ctx context.Context, userID, repoID int64, doWatch bool) (err error) {
|
||||
var watch Watch
|
||||
if watch, err = GetWatch(ctx, userID, repoID); err != nil {
|
||||
func WatchRepo(ctx context.Context, doer *user_model.User, repo *Repository, doWatch bool) error {
|
||||
watch, err := GetWatch(ctx, doer.ID, repo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !doWatch && watch.Mode == WatchModeAuto {
|
||||
err = watchRepoMode(ctx, watch, WatchModeDont)
|
||||
return watchRepoMode(ctx, watch, WatchModeDont)
|
||||
} else if !doWatch {
|
||||
err = watchRepoMode(ctx, watch, WatchModeNone)
|
||||
} else {
|
||||
err = watchRepoMode(ctx, watch, WatchModeNormal)
|
||||
return watchRepoMode(ctx, watch, WatchModeNone)
|
||||
}
|
||||
return err
|
||||
|
||||
if user_model.IsUserBlockedBy(ctx, doer, repo.OwnerID) {
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
|
||||
return watchRepoMode(ctx, watch, WatchModeNormal)
|
||||
}
|
||||
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -64,6 +65,8 @@ func TestWatchIfAuto(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
user12 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 12})
|
||||
|
||||
watchers, err := repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, repo.NumWatches)
|
||||
@ -105,7 +108,7 @@ func TestWatchIfAuto(t *testing.T) {
|
||||
assert.Len(t, watchers, prevCount+1)
|
||||
|
||||
// Should remove watch, inhibit from adding auto
|
||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, 12, 1, false))
|
||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user12, repo, false))
|
||||
watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
@ -116,24 +119,3 @@ func TestWatchIfAuto(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
}
|
||||
|
||||
func TestWatchRepoMode(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0)
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeAuto))
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1)
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeAuto}, 1)
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeNormal))
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1)
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeNormal}, 1)
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeDont))
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1)
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeDont}, 1)
|
||||
|
||||
assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeNone))
|
||||
unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0)
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// RepoTransfer is used to manage repository transfers
|
||||
@ -94,21 +96,46 @@ func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.
|
||||
return allowed
|
||||
}
|
||||
|
||||
type PendingRepositoryTransferOptions struct {
|
||||
RepoID int64
|
||||
SenderID int64
|
||||
RecipientID int64
|
||||
}
|
||||
|
||||
func (opts *PendingRepositoryTransferOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.SenderID != 0 {
|
||||
cond = cond.And(builder.Eq{"doer_id": opts.SenderID})
|
||||
}
|
||||
if opts.RecipientID != 0 {
|
||||
cond = cond.And(builder.Eq{"recipient_id": opts.RecipientID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func GetPendingRepositoryTransfers(ctx context.Context, opts *PendingRepositoryTransferOptions) ([]*RepoTransfer, error) {
|
||||
transfers := make([]*RepoTransfer, 0, 10)
|
||||
return transfers, db.GetEngine(ctx).
|
||||
Where(opts.ToConds()).
|
||||
Find(&transfers)
|
||||
}
|
||||
|
||||
// GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
|
||||
// process for the repository
|
||||
func GetPendingRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) (*RepoTransfer, error) {
|
||||
transfer := new(RepoTransfer)
|
||||
|
||||
has, err := db.GetEngine(ctx).Where("repo_id = ? ", repo.ID).Get(transfer)
|
||||
transfers, err := GetPendingRepositoryTransfers(ctx, &PendingRepositoryTransferOptions{RepoID: repo.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
if len(transfers) != 1 {
|
||||
return nil, ErrNoPendingRepoTransfer{RepoID: repo.ID}
|
||||
}
|
||||
|
||||
return transfer, nil
|
||||
return transfers[0], nil
|
||||
}
|
||||
|
||||
func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error {
|
||||
|
123
models/user/block.go
Normal file
123
models/user/block.go
Normal file
@ -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
|
||||
}
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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{})
|
||||
}
|
||||
|
Reference in New Issue
Block a user