code.gitea.io/gitea@v1.22.3/models/repo/collaboration.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 "fmt" 9 10 "code.gitea.io/gitea/models/db" 11 "code.gitea.io/gitea/models/perm" 12 "code.gitea.io/gitea/models/unit" 13 user_model "code.gitea.io/gitea/models/user" 14 "code.gitea.io/gitea/modules/timeutil" 15 16 "xorm.io/builder" 17 ) 18 19 // Collaboration represent the relation between an individual and a repository. 20 type Collaboration struct { 21 ID int64 `xorm:"pk autoincr"` 22 RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 23 UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 24 Mode perm.AccessMode `xorm:"DEFAULT 2 NOT NULL"` 25 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` 26 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` 27 } 28 29 func init() { 30 db.RegisterModel(new(Collaboration)) 31 } 32 33 // Collaborator represents a user with collaboration details. 34 type Collaborator struct { 35 *user_model.User 36 Collaboration *Collaboration 37 } 38 39 type FindCollaborationOptions struct { 40 db.ListOptions 41 RepoID int64 42 RepoOwnerID int64 43 CollaboratorID int64 44 } 45 46 func (opts *FindCollaborationOptions) ToConds() builder.Cond { 47 cond := builder.NewCond() 48 if opts.RepoID != 0 { 49 cond = cond.And(builder.Eq{"collaboration.repo_id": opts.RepoID}) 50 } 51 if opts.RepoOwnerID != 0 { 52 cond = cond.And(builder.Eq{"repository.owner_id": opts.RepoOwnerID}) 53 } 54 if opts.CollaboratorID != 0 { 55 cond = cond.And(builder.Eq{"collaboration.user_id": opts.CollaboratorID}) 56 } 57 return cond 58 } 59 60 func (opts *FindCollaborationOptions) ToJoins() []db.JoinFunc { 61 if opts.RepoOwnerID != 0 { 62 return []db.JoinFunc{ 63 func(e db.Engine) error { 64 e.Join("INNER", "repository", "repository.id = collaboration.repo_id") 65 return nil 66 }, 67 } 68 } 69 return nil 70 } 71 72 // GetCollaborators returns the collaborators for a repository 73 func GetCollaborators(ctx context.Context, opts *FindCollaborationOptions) ([]*Collaborator, int64, error) { 74 collaborations, total, err := db.FindAndCount[Collaboration](ctx, opts) 75 if err != nil { 76 return nil, 0, fmt.Errorf("db.FindAndCount[Collaboration]: %w", err) 77 } 78 79 collaborators := make([]*Collaborator, 0, len(collaborations)) 80 userIDs := make([]int64, 0, len(collaborations)) 81 for _, c := range collaborations { 82 userIDs = append(userIDs, c.UserID) 83 } 84 85 usersMap := make(map[int64]*user_model.User) 86 if err := db.GetEngine(ctx).In("id", userIDs).Find(&usersMap); err != nil { 87 return nil, 0, fmt.Errorf("Find users map by user ids: %w", err) 88 } 89 90 for _, c := range collaborations { 91 u := usersMap[c.UserID] 92 if u == nil { 93 u = user_model.NewGhostUser() 94 } 95 collaborators = append(collaborators, &Collaborator{ 96 User: u, 97 Collaboration: c, 98 }) 99 } 100 return collaborators, total, nil 101 } 102 103 // GetCollaboration get collaboration for a repository id with a user id 104 func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) { 105 collaboration := &Collaboration{ 106 RepoID: repoID, 107 UserID: uid, 108 } 109 has, err := db.GetEngine(ctx).Get(collaboration) 110 if !has { 111 collaboration = nil 112 } 113 return collaboration, err 114 } 115 116 // IsCollaborator check if a user is a collaborator of a repository 117 func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) { 118 return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID}) 119 } 120 121 // ChangeCollaborationAccessMode sets new access mode for the collaboration. 122 func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid int64, mode perm.AccessMode) error { 123 // Discard invalid input 124 if mode <= perm.AccessModeNone || mode > perm.AccessModeOwner { 125 return nil 126 } 127 128 return db.WithTx(ctx, func(ctx context.Context) error { 129 e := db.GetEngine(ctx) 130 131 collaboration := &Collaboration{ 132 RepoID: repo.ID, 133 UserID: uid, 134 } 135 has, err := e.Get(collaboration) 136 if err != nil { 137 return fmt.Errorf("get collaboration: %w", err) 138 } else if !has { 139 return nil 140 } 141 142 if collaboration.Mode == mode { 143 return nil 144 } 145 collaboration.Mode = mode 146 147 if _, err = e. 148 ID(collaboration.ID). 149 Cols("mode"). 150 Update(collaboration); err != nil { 151 return fmt.Errorf("update collaboration: %w", err) 152 } else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil { 153 return fmt.Errorf("update access table: %w", err) 154 } 155 156 return nil 157 }) 158 } 159 160 // IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository 161 func IsOwnerMemberCollaborator(ctx context.Context, repo *Repository, userID int64) (bool, error) { 162 if repo.OwnerID == userID { 163 return true, nil 164 } 165 teamMember, err := db.GetEngine(ctx).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). 166 Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id"). 167 Where("team_repo.repo_id = ?", repo.ID). 168 And("team_unit.`type` = ?", unit.TypeCode). 169 And("team_user.uid = ?", userID).Table("team_user").Exist() 170 if err != nil { 171 return false, err 172 } 173 if teamMember { 174 return true, nil 175 } 176 177 return db.GetEngine(ctx).Get(&Collaboration{RepoID: repo.ID, UserID: userID}) 178 }