選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

user_repo.go 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "context"
  6. "code.gitea.io/gitea/models/db"
  7. "code.gitea.io/gitea/models/perm"
  8. "code.gitea.io/gitea/models/unit"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/modules/container"
  11. api "code.gitea.io/gitea/modules/structs"
  12. "xorm.io/builder"
  13. )
  14. type StarredReposOptions struct {
  15. db.ListOptions
  16. StarrerID int64
  17. RepoOwnerID int64
  18. IncludePrivate bool
  19. }
  20. func (opts *StarredReposOptions) ToConds() builder.Cond {
  21. var cond builder.Cond = builder.Eq{
  22. "star.uid": opts.StarrerID,
  23. }
  24. if opts.RepoOwnerID != 0 {
  25. cond = cond.And(builder.Eq{
  26. "repository.owner_id": opts.RepoOwnerID,
  27. })
  28. }
  29. if !opts.IncludePrivate {
  30. cond = cond.And(builder.Eq{
  31. "repository.is_private": false,
  32. })
  33. }
  34. return cond
  35. }
  36. func (opts *StarredReposOptions) ToJoins() []db.JoinFunc {
  37. return []db.JoinFunc{
  38. func(e db.Engine) error {
  39. e.Join("INNER", "star", "`repository`.id=`star`.repo_id")
  40. return nil
  41. },
  42. }
  43. }
  44. // GetStarredRepos returns the repos starred by a particular user
  45. func GetStarredRepos(ctx context.Context, opts *StarredReposOptions) ([]*Repository, error) {
  46. return db.Find[Repository](ctx, opts)
  47. }
  48. type WatchedReposOptions struct {
  49. db.ListOptions
  50. WatcherID int64
  51. RepoOwnerID int64
  52. IncludePrivate bool
  53. }
  54. func (opts *WatchedReposOptions) ToConds() builder.Cond {
  55. var cond builder.Cond = builder.Eq{
  56. "watch.user_id": opts.WatcherID,
  57. }
  58. if opts.RepoOwnerID != 0 {
  59. cond = cond.And(builder.Eq{
  60. "repository.owner_id": opts.RepoOwnerID,
  61. })
  62. }
  63. if !opts.IncludePrivate {
  64. cond = cond.And(builder.Eq{
  65. "repository.is_private": false,
  66. })
  67. }
  68. return cond.And(builder.Neq{
  69. "watch.mode": WatchModeDont,
  70. })
  71. }
  72. func (opts *WatchedReposOptions) ToJoins() []db.JoinFunc {
  73. return []db.JoinFunc{
  74. func(e db.Engine) error {
  75. e.Join("INNER", "watch", "`repository`.id=`watch`.repo_id")
  76. return nil
  77. },
  78. }
  79. }
  80. // GetWatchedRepos returns the repos watched by a particular user
  81. func GetWatchedRepos(ctx context.Context, opts *WatchedReposOptions) ([]*Repository, int64, error) {
  82. return db.FindAndCount[Repository](ctx, opts)
  83. }
  84. // GetRepoAssignees returns all users that have write access and can be assigned to issues
  85. // of the repository,
  86. func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.User, err error) {
  87. if err = repo.LoadOwner(ctx); err != nil {
  88. return nil, err
  89. }
  90. e := db.GetEngine(ctx)
  91. userIDs := make([]int64, 0, 10)
  92. if err = e.Table("access").
  93. Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
  94. Select("user_id").
  95. Find(&userIDs); err != nil {
  96. return nil, err
  97. }
  98. additionalUserIDs := make([]int64, 0, 10)
  99. if err = e.Table("team_user").
  100. Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
  101. Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
  102. Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
  103. repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
  104. Distinct("`team_user`.uid").
  105. Select("`team_user`.uid").
  106. Find(&additionalUserIDs); err != nil {
  107. return nil, err
  108. }
  109. uniqueUserIDs := make(container.Set[int64])
  110. uniqueUserIDs.AddMultiple(userIDs...)
  111. uniqueUserIDs.AddMultiple(additionalUserIDs...)
  112. // Leave a seat for owner itself to append later, but if owner is an organization
  113. // and just waste 1 unit is cheaper than re-allocate memory once.
  114. users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
  115. if len(userIDs) > 0 {
  116. if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
  117. return nil, err
  118. }
  119. }
  120. if !repo.Owner.IsOrganization() && !uniqueUserIDs.Contains(repo.OwnerID) {
  121. users = append(users, repo.Owner)
  122. }
  123. return users, nil
  124. }
  125. // GetReviewers get all users can be requested to review:
  126. // * for private repositories this returns all users that have read access or higher to the repository.
  127. // * for public repositories this returns all users that have read access or higher to the repository,
  128. // all repo watchers and all organization members.
  129. // TODO: may be we should have a busy choice for users to block review request to them.
  130. func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) ([]*user_model.User, error) {
  131. // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries
  132. if err := repo.LoadOwner(ctx); err != nil {
  133. return nil, err
  134. }
  135. cond := builder.And(builder.Neq{"`user`.id": posterID})
  136. if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
  137. // This a private repository:
  138. // Anyone who can read the repository is a requestable reviewer
  139. cond = cond.And(builder.In("`user`.id",
  140. builder.Select("user_id").From("access").Where(
  141. builder.Eq{"repo_id": repo.ID}.
  142. And(builder.Gte{"mode": perm.AccessModeRead}),
  143. ),
  144. ))
  145. if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID {
  146. // as private *user* repos don't generate an entry in the `access` table,
  147. // the owner of a private repo needs to be explicitly added.
  148. cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID})
  149. }
  150. } else {
  151. // This is a "public" repository:
  152. // Any user that has read access, is a watcher or organization member can be requested to review
  153. cond = cond.And(builder.And(builder.In("`user`.id",
  154. builder.Select("user_id").From("access").
  155. Where(builder.Eq{"repo_id": repo.ID}.
  156. And(builder.Gte{"mode": perm.AccessModeRead})),
  157. ).Or(builder.In("`user`.id",
  158. builder.Select("user_id").From("watch").
  159. Where(builder.Eq{"repo_id": repo.ID}.
  160. And(builder.In("mode", WatchModeNormal, WatchModeAuto))),
  161. ).Or(builder.In("`user`.id",
  162. builder.Select("uid").From("org_user").
  163. Where(builder.Eq{"org_id": repo.OwnerID}),
  164. )))))
  165. }
  166. users := make([]*user_model.User, 0, 8)
  167. return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
  168. }
  169. // GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
  170. // If isShowFullName is set to true, also include full name prefix search
  171. func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
  172. users := make([]*user_model.User, 0, 30)
  173. var prefixCond builder.Cond = builder.Like{"name", search + "%"}
  174. if isShowFullName {
  175. prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"})
  176. }
  177. cond := builder.In("`user`.id",
  178. builder.Select("poster_id").From("issue").Where(
  179. builder.Eq{"repo_id": repo.ID}.
  180. And(builder.Eq{"is_pull": isPull}),
  181. ).GroupBy("poster_id")).And(prefixCond)
  182. return users, db.GetEngine(ctx).
  183. Where(cond).
  184. Cols("id", "name", "full_name", "avatar", "avatar_email", "use_custom_avatar").
  185. OrderBy("name").
  186. Limit(30).
  187. Find(&users)
  188. }