summaryrefslogtreecommitdiffstats
path: root/models/repo/collaboration.go
blob: cb66cb56a6041bfb32bf334409ea16bbb589fd81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repo

import (
	"context"
	"fmt"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/models/perm"
	"code.gitea.io/gitea/models/unit"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/timeutil"

	"xorm.io/builder"
)

// Collaboration represent the relation between an individual and a repository.
type Collaboration struct {
	ID          int64              `xorm:"pk autoincr"`
	RepoID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
	UserID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
	Mode        perm.AccessMode    `xorm:"DEFAULT 2 NOT NULL"`
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}

func init() {
	db.RegisterModel(new(Collaboration))
}

// Collaborator represents a user with collaboration details.
type Collaborator struct {
	*user_model.User
	Collaboration *Collaboration
}

// GetCollaborators returns the collaborators for a repository
func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
	collaborations, err := db.Find[Collaboration](ctx, FindCollaborationOptions{
		ListOptions: listOptions,
		RepoID:      repoID,
	})
	if err != nil {
		return nil, fmt.Errorf("db.Find[Collaboration]: %w", err)
	}

	collaborators := make([]*Collaborator, 0, len(collaborations))
	userIDs := make([]int64, 0, len(collaborations))
	for _, c := range collaborations {
		userIDs = append(userIDs, c.UserID)
	}

	usersMap := make(map[int64]*user_model.User)
	if err := db.GetEngine(ctx).In("id", userIDs).Find(&usersMap); err != nil {
		return nil, fmt.Errorf("Find users map by user ids: %w", err)
	}

	for _, c := range collaborations {
		u := usersMap[c.UserID]
		if u == nil {
			u = user_model.NewGhostUser()
		}
		collaborators = append(collaborators, &Collaborator{
			User:          u,
			Collaboration: c,
		})
	}
	return collaborators, nil
}

// GetCollaboration get collaboration for a repository id with a user id
func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) {
	collaboration := &Collaboration{
		RepoID: repoID,
		UserID: uid,
	}
	has, err := db.GetEngine(ctx).Get(collaboration)
	if !has {
		collaboration = nil
	}
	return collaboration, err
}

// IsCollaborator check if a user is a collaborator of a repository
func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
	return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
}

type FindCollaborationOptions struct {
	db.ListOptions
	RepoID int64
}

func (opts FindCollaborationOptions) ToConds() builder.Cond {
	return builder.And(builder.Eq{"repo_id": opts.RepoID})
}

// ChangeCollaborationAccessMode sets new access mode for the collaboration.
func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid int64, mode perm.AccessMode) error {
	// Discard invalid input
	if mode <= perm.AccessModeNone || mode > perm.AccessModeOwner {
		return nil
	}

	return db.WithTx(ctx, func(ctx context.Context) error {
		e := db.GetEngine(ctx)

		collaboration := &Collaboration{
			RepoID: repo.ID,
			UserID: uid,
		}
		has, err := e.Get(collaboration)
		if err != nil {
			return fmt.Errorf("get collaboration: %w", err)
		} else if !has {
			return nil
		}

		if collaboration.Mode == mode {
			return nil
		}
		collaboration.Mode = mode

		if _, err = e.
			ID(collaboration.ID).
			Cols("mode").
			Update(collaboration); err != nil {
			return fmt.Errorf("update collaboration: %w", err)
		} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
			return fmt.Errorf("update access table: %w", err)
		}

		return nil
	})
}

// GetCollaboratorWithUser returns all collaborator IDs of collabUserID on
// repositories of ownerID.
func GetCollaboratorWithUser(ctx context.Context, ownerID, collabUserID int64) ([]int64, error) {
	collabsID := make([]int64, 0, 8)
	err := db.GetEngine(ctx).Table("collaboration").Select("collaboration.`id`").
		Join("INNER", "repository", "repository.id = collaboration.repo_id").
		Where("repository.`owner_id` = ?", ownerID).
		And("collaboration.`user_id` = ?", collabUserID).
		Find(&collabsID)

	return collabsID, err
}

// IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository
func IsOwnerMemberCollaborator(ctx context.Context, repo *Repository, userID int64) (bool, error) {
	if repo.OwnerID == userID {
		return true, nil
	}
	teamMember, err := db.GetEngine(ctx).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id").
		Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id").
		Where("team_repo.repo_id = ?", repo.ID).
		And("team_unit.`type` = ?", unit.TypeCode).
		And("team_user.uid = ?", userID).Table("team_user").Exist()
	if err != nil {
		return false, err
	}
	if teamMember {
		return true, nil
	}

	return db.GetEngine(ctx).Get(&Collaboration{RepoID: repo.ID, UserID: userID})
}