1
0

repo_collaboration.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package database
  2. import (
  3. log "unknwon.dev/clog/v2"
  4. "github.com/cockroachdb/errors"
  5. api "github.com/gogs/go-gogs-client"
  6. "gorm.io/gorm"
  7. )
  8. // Collaboration represent the relation between an individual and a repository.
  9. type Collaboration struct {
  10. ID int64 `gorm:"primary_key"`
  11. UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL" gorm:"uniqueIndex:collaboration_user_repo_unique;index;not null"`
  12. RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL" gorm:"uniqueIndex:collaboration_user_repo_unique;index;not null"`
  13. Mode AccessMode `xorm:"DEFAULT 2 NOT NULL" gorm:"not null;default:2"`
  14. }
  15. func (c *Collaboration) ModeI18nKey() string {
  16. switch c.Mode {
  17. case AccessModeRead:
  18. return "repo.settings.collaboration.read"
  19. case AccessModeWrite:
  20. return "repo.settings.collaboration.write"
  21. case AccessModeAdmin:
  22. return "repo.settings.collaboration.admin"
  23. default:
  24. return "repo.settings.collaboration.undefined"
  25. }
  26. }
  27. // IsCollaborator returns true if the user is a collaborator of the repository.
  28. func IsCollaborator(repoID, userID int64) bool {
  29. collaboration := &Collaboration{
  30. RepoID: repoID,
  31. UserID: userID,
  32. }
  33. err := db.Where("repo_id = ? AND user_id = ?", repoID, userID).First(collaboration).Error
  34. if err != nil {
  35. log.Error("get collaboration [repo_id: %d, user_id: %d]: %v", repoID, userID, err)
  36. return false
  37. }
  38. return true
  39. }
  40. func (r *Repository) IsCollaborator(userID int64) bool {
  41. return IsCollaborator(r.ID, userID)
  42. }
  43. // AddCollaborator adds new collaboration to a repository with default access mode.
  44. func (r *Repository) AddCollaborator(u *User) error {
  45. collaboration := &Collaboration{
  46. RepoID: r.ID,
  47. UserID: u.ID,
  48. }
  49. var existing Collaboration
  50. err := db.Where("repo_id = ? AND user_id = ?", r.ID, u.ID).First(&existing).Error
  51. if err == nil {
  52. return nil
  53. } else if !errors.Is(err, gorm.ErrRecordNotFound) {
  54. return err
  55. }
  56. collaboration.Mode = AccessModeWrite
  57. return db.Transaction(func(tx *gorm.DB) error {
  58. if err := tx.Create(collaboration).Error; err != nil {
  59. return err
  60. }
  61. if err := r.recalculateAccesses(tx); err != nil {
  62. return errors.Newf("recalculateAccesses [repo_id: %v]: %v", r.ID, err)
  63. }
  64. return nil
  65. })
  66. }
  67. func (r *Repository) getCollaborations(e *gorm.DB) ([]*Collaboration, error) {
  68. collaborations := make([]*Collaboration, 0)
  69. return collaborations, e.Where("repo_id = ?", r.ID).Find(&collaborations).Error
  70. }
  71. // Collaborator represents a user with collaboration details.
  72. type Collaborator struct {
  73. *User
  74. Collaboration *Collaboration
  75. }
  76. func (c *Collaborator) APIFormat() *api.Collaborator {
  77. return &api.Collaborator{
  78. User: c.User.APIFormat(),
  79. Permissions: api.Permission{
  80. Admin: c.Collaboration.Mode >= AccessModeAdmin,
  81. Push: c.Collaboration.Mode >= AccessModeWrite,
  82. Pull: c.Collaboration.Mode >= AccessModeRead,
  83. },
  84. }
  85. }
  86. func (r *Repository) getCollaborators(e *gorm.DB) ([]*Collaborator, error) {
  87. collaborations, err := r.getCollaborations(e)
  88. if err != nil {
  89. return nil, errors.Newf("getCollaborations: %v", err)
  90. }
  91. collaborators := make([]*Collaborator, len(collaborations))
  92. for i, c := range collaborations {
  93. user, err := getUserByID(e, c.UserID)
  94. if err != nil {
  95. return nil, err
  96. }
  97. collaborators[i] = &Collaborator{
  98. User: user,
  99. Collaboration: c,
  100. }
  101. }
  102. return collaborators, nil
  103. }
  104. // GetCollaborators returns the collaborators for a repository
  105. func (r *Repository) GetCollaborators() ([]*Collaborator, error) {
  106. return r.getCollaborators(db)
  107. }
  108. // ChangeCollaborationAccessMode sets new access mode for the collaboration.
  109. func (r *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode) error {
  110. // Discard invalid input
  111. if mode <= AccessModeNone || mode > AccessModeOwner {
  112. return nil
  113. }
  114. collaboration := &Collaboration{
  115. RepoID: r.ID,
  116. UserID: userID,
  117. }
  118. err := db.Where("repo_id = ? AND user_id = ?", r.ID, userID).First(collaboration).Error
  119. if errors.Is(err, gorm.ErrRecordNotFound) {
  120. return nil
  121. } else if err != nil {
  122. return errors.Newf("get collaboration: %v", err)
  123. }
  124. if collaboration.Mode == mode {
  125. return nil
  126. }
  127. collaboration.Mode = mode
  128. // If it's an organizational repository, merge with team access level for highest permission
  129. if r.Owner.IsOrganization() {
  130. teams, err := GetUserTeams(r.OwnerID, userID)
  131. if err != nil {
  132. return errors.Newf("GetUserTeams: [org_id: %d, user_id: %d]: %v", r.OwnerID, userID, err)
  133. }
  134. for i := range teams {
  135. if mode < teams[i].Authorize {
  136. mode = teams[i].Authorize
  137. }
  138. }
  139. }
  140. return db.Transaction(func(tx *gorm.DB) error {
  141. if err := tx.Model(&Collaboration{}).Where("id = ?", collaboration.ID).Updates(collaboration).Error; err != nil {
  142. return errors.Newf("update collaboration: %v", err)
  143. }
  144. access := &Access{
  145. UserID: userID,
  146. RepoID: r.ID,
  147. }
  148. err := tx.Where("user_id = ? AND repo_id = ?", userID, r.ID).First(access).Error
  149. if err == nil {
  150. if err := tx.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, userID, r.ID).Error; err != nil {
  151. return errors.Newf("update access table: %v", err)
  152. }
  153. } else if errors.Is(err, gorm.ErrRecordNotFound) {
  154. access.Mode = mode
  155. if err := tx.Create(access).Error; err != nil {
  156. return errors.Newf("insert access table: %v", err)
  157. }
  158. } else {
  159. return errors.Newf("get access record: %v", err)
  160. }
  161. return nil
  162. })
  163. }
  164. // DeleteCollaboration removes collaboration relation between the user and repository.
  165. func DeleteCollaboration(repo *Repository, userID int64) (err error) {
  166. if !IsCollaborator(repo.ID, userID) {
  167. return nil
  168. }
  169. collaboration := &Collaboration{
  170. RepoID: repo.ID,
  171. UserID: userID,
  172. }
  173. return db.Transaction(func(tx *gorm.DB) error {
  174. result := tx.Delete(collaboration, "repo_id = ? AND user_id = ?", repo.ID, userID)
  175. if result.Error != nil {
  176. return result.Error
  177. } else if result.RowsAffected == 0 {
  178. return nil
  179. }
  180. if err := repo.recalculateAccesses(tx); err != nil {
  181. return err
  182. }
  183. return nil
  184. })
  185. }
  186. func (r *Repository) DeleteCollaboration(userID int64) error {
  187. return DeleteCollaboration(r, userID)
  188. }