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  }