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