code.gitea.io/gitea@v1.22.3/models/issues/assignees.go (about) 1 // Copyright 2018 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package issues 5 6 import ( 7 "context" 8 "fmt" 9 10 "code.gitea.io/gitea/models/db" 11 user_model "code.gitea.io/gitea/models/user" 12 "code.gitea.io/gitea/modules/util" 13 14 "xorm.io/builder" 15 ) 16 17 // IssueAssignees saves all issue assignees 18 type IssueAssignees struct { 19 ID int64 `xorm:"pk autoincr"` 20 AssigneeID int64 `xorm:"INDEX"` 21 IssueID int64 `xorm:"INDEX"` 22 } 23 24 func init() { 25 db.RegisterModel(new(IssueAssignees)) 26 } 27 28 // LoadAssignees load assignees of this issue. 29 func (issue *Issue) LoadAssignees(ctx context.Context) (err error) { 30 // Reset maybe preexisting assignees 31 issue.Assignees = []*user_model.User{} 32 issue.Assignee = nil 33 34 err = db.GetEngine(ctx).Table("`user`"). 35 Join("INNER", "issue_assignees", "assignee_id = `user`.id"). 36 Where("issue_assignees.issue_id = ?", issue.ID). 37 Find(&issue.Assignees) 38 if err != nil { 39 return err 40 } 41 42 // Check if we have at least one assignee and if yes put it in as `Assignee` 43 if len(issue.Assignees) > 0 { 44 issue.Assignee = issue.Assignees[0] 45 } 46 return err 47 } 48 49 // GetAssigneeIDsByIssue returns the IDs of users assigned to an issue 50 // but skips joining with `user` for performance reasons. 51 // User permissions must be verified elsewhere if required. 52 func GetAssigneeIDsByIssue(ctx context.Context, issueID int64) ([]int64, error) { 53 userIDs := make([]int64, 0, 5) 54 return userIDs, db.GetEngine(ctx). 55 Table("issue_assignees"). 56 Cols("assignee_id"). 57 Where("issue_id = ?", issueID). 58 Distinct("assignee_id"). 59 Find(&userIDs) 60 } 61 62 // IsUserAssignedToIssue returns true when the user is assigned to the issue 63 func IsUserAssignedToIssue(ctx context.Context, issue *Issue, user *user_model.User) (isAssigned bool, err error) { 64 return db.Exist[IssueAssignees](ctx, builder.Eq{"assignee_id": user.ID, "issue_id": issue.ID}) 65 } 66 67 type AssignedIssuesOptions struct { 68 db.ListOptions 69 AssigneeID int64 70 RepoOwnerID int64 71 } 72 73 func (opts *AssignedIssuesOptions) ToConds() builder.Cond { 74 cond := builder.NewCond() 75 if opts.AssigneeID != 0 { 76 cond = cond.And(builder.In("issue.id", builder.Select("issue_id").From("issue_assignees").Where(builder.Eq{"assignee_id": opts.AssigneeID}))) 77 } 78 if opts.RepoOwnerID != 0 { 79 cond = cond.And(builder.In("issue.repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": opts.RepoOwnerID}))) 80 } 81 return cond 82 } 83 84 func GetAssignedIssues(ctx context.Context, opts *AssignedIssuesOptions) ([]*Issue, int64, error) { 85 return db.FindAndCount[Issue](ctx, opts) 86 } 87 88 // ToggleIssueAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. 89 func ToggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) { 90 ctx, committer, err := db.TxContext(ctx) 91 if err != nil { 92 return false, nil, err 93 } 94 defer committer.Close() 95 96 removed, comment, err = toggleIssueAssignee(ctx, issue, doer, assigneeID, false) 97 if err != nil { 98 return false, nil, err 99 } 100 101 if err := committer.Commit(); err != nil { 102 return false, nil, err 103 } 104 105 return removed, comment, nil 106 } 107 108 func toggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64, isCreate bool) (removed bool, comment *Comment, err error) { 109 removed, err = toggleUserAssignee(ctx, issue, assigneeID) 110 if err != nil { 111 return false, nil, fmt.Errorf("UpdateIssueUserByAssignee: %w", err) 112 } 113 114 // Repo infos 115 if err = issue.LoadRepo(ctx); err != nil { 116 return false, nil, fmt.Errorf("loadRepo: %w", err) 117 } 118 119 opts := &CreateCommentOptions{ 120 Type: CommentTypeAssignees, 121 Doer: doer, 122 Repo: issue.Repo, 123 Issue: issue, 124 RemovedAssignee: removed, 125 AssigneeID: assigneeID, 126 } 127 // Comment 128 comment, err = CreateComment(ctx, opts) 129 if err != nil { 130 return false, nil, fmt.Errorf("createComment: %w", err) 131 } 132 133 // if pull request is in the middle of creation - don't call webhook 134 if isCreate { 135 return removed, comment, err 136 } 137 138 return removed, comment, nil 139 } 140 141 // toggles user assignee state in database 142 func toggleUserAssignee(ctx context.Context, issue *Issue, assigneeID int64) (removed bool, err error) { 143 // Check if the user exists 144 assignee, err := user_model.GetUserByID(ctx, assigneeID) 145 if err != nil { 146 return false, err 147 } 148 149 // Check if the submitted user is already assigned, if yes delete him otherwise add him 150 found := false 151 i := 0 152 for ; i < len(issue.Assignees); i++ { 153 if issue.Assignees[i].ID == assigneeID { 154 found = true 155 break 156 } 157 } 158 159 assigneeIn := IssueAssignees{AssigneeID: assigneeID, IssueID: issue.ID} 160 if found { 161 issue.Assignees = append(issue.Assignees[:i], issue.Assignees[i+1:]...) 162 _, err = db.DeleteByBean(ctx, &assigneeIn) 163 if err != nil { 164 return found, err 165 } 166 } else { 167 issue.Assignees = append(issue.Assignees, assignee) 168 if err = db.Insert(ctx, &assigneeIn); err != nil { 169 return found, err 170 } 171 } 172 173 return found, nil 174 } 175 176 // MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs 177 func MakeIDsFromAPIAssigneesToAdd(ctx context.Context, oneAssignee string, multipleAssignees []string) (assigneeIDs []int64, err error) { 178 var requestAssignees []string 179 180 // Keeping the old assigning method for compatibility reasons 181 if oneAssignee != "" && !util.SliceContainsString(multipleAssignees, oneAssignee) { 182 requestAssignees = append(requestAssignees, oneAssignee) 183 } 184 185 // Prevent empty assignees 186 if len(multipleAssignees) > 0 && multipleAssignees[0] != "" { 187 requestAssignees = append(requestAssignees, multipleAssignees...) 188 } 189 190 // Get the IDs of all assignees 191 assigneeIDs, err = user_model.GetUserIDsByNames(ctx, requestAssignees, false) 192 193 return assigneeIDs, err 194 }