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  }