You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

watch.go 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "context"
  6. "code.gitea.io/gitea/models/db"
  7. user_model "code.gitea.io/gitea/models/user"
  8. "code.gitea.io/gitea/modules/setting"
  9. "code.gitea.io/gitea/modules/timeutil"
  10. )
  11. // WatchMode specifies what kind of watch the user has on a repository
  12. type WatchMode int8
  13. const (
  14. // WatchModeNone don't watch
  15. WatchModeNone WatchMode = iota // 0
  16. // WatchModeNormal watch repository (from other sources)
  17. WatchModeNormal // 1
  18. // WatchModeDont explicit don't auto-watch
  19. WatchModeDont // 2
  20. // WatchModeAuto watch repository (from AutoWatchOnChanges)
  21. WatchModeAuto // 3
  22. )
  23. // Watch is connection request for receiving repository notification.
  24. type Watch struct {
  25. ID int64 `xorm:"pk autoincr"`
  26. UserID int64 `xorm:"UNIQUE(watch)"`
  27. RepoID int64 `xorm:"UNIQUE(watch)"`
  28. Mode WatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
  29. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  30. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  31. }
  32. func init() {
  33. db.RegisterModel(new(Watch))
  34. }
  35. // GetWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
  36. func GetWatch(ctx context.Context, userID, repoID int64) (Watch, error) {
  37. watch := Watch{UserID: userID, RepoID: repoID}
  38. has, err := db.GetEngine(ctx).Get(&watch)
  39. if err != nil {
  40. return watch, err
  41. }
  42. if !has {
  43. watch.Mode = WatchModeNone
  44. }
  45. return watch, nil
  46. }
  47. // IsWatchMode Decodes watchability of WatchMode
  48. func IsWatchMode(mode WatchMode) bool {
  49. return mode != WatchModeNone && mode != WatchModeDont
  50. }
  51. // IsWatching checks if user has watched given repository.
  52. func IsWatching(ctx context.Context, userID, repoID int64) bool {
  53. watch, err := GetWatch(ctx, userID, repoID)
  54. return err == nil && IsWatchMode(watch.Mode)
  55. }
  56. func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) {
  57. if watch.Mode == mode {
  58. return nil
  59. }
  60. if mode == WatchModeAuto && (watch.Mode == WatchModeDont || IsWatchMode(watch.Mode)) {
  61. // Don't auto watch if already watching or deliberately not watching
  62. return nil
  63. }
  64. hadrec := watch.Mode != WatchModeNone
  65. needsrec := mode != WatchModeNone
  66. repodiff := 0
  67. if IsWatchMode(mode) && !IsWatchMode(watch.Mode) {
  68. repodiff = 1
  69. } else if !IsWatchMode(mode) && IsWatchMode(watch.Mode) {
  70. repodiff = -1
  71. }
  72. watch.Mode = mode
  73. if !hadrec && needsrec {
  74. watch.Mode = mode
  75. if err = db.Insert(ctx, watch); err != nil {
  76. return err
  77. }
  78. } else if needsrec {
  79. watch.Mode = mode
  80. if _, err := db.GetEngine(ctx).ID(watch.ID).AllCols().Update(watch); err != nil {
  81. return err
  82. }
  83. } else if _, err = db.DeleteByID[Watch](ctx, watch.ID); err != nil {
  84. return err
  85. }
  86. if repodiff != 0 {
  87. _, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
  88. }
  89. return err
  90. }
  91. // WatchRepo watch or unwatch repository.
  92. func WatchRepo(ctx context.Context, doer *user_model.User, repo *Repository, doWatch bool) error {
  93. watch, err := GetWatch(ctx, doer.ID, repo.ID)
  94. if err != nil {
  95. return err
  96. }
  97. if !doWatch && watch.Mode == WatchModeAuto {
  98. return watchRepoMode(ctx, watch, WatchModeDont)
  99. } else if !doWatch {
  100. return watchRepoMode(ctx, watch, WatchModeNone)
  101. }
  102. if user_model.IsUserBlockedBy(ctx, doer, repo.OwnerID) {
  103. return user_model.ErrBlockedUser
  104. }
  105. return watchRepoMode(ctx, watch, WatchModeNormal)
  106. }
  107. // GetWatchers returns all watchers of given repository.
  108. func GetWatchers(ctx context.Context, repoID int64) ([]*Watch, error) {
  109. watches := make([]*Watch, 0, 10)
  110. return watches, db.GetEngine(ctx).Where("`watch`.repo_id=?", repoID).
  111. And("`watch`.mode<>?", WatchModeDont).
  112. And("`user`.is_active=?", true).
  113. And("`user`.prohibit_login=?", false).
  114. Join("INNER", "`user`", "`user`.id = `watch`.user_id").
  115. Find(&watches)
  116. }
  117. // GetRepoWatchersIDs returns IDs of watchers for a given repo ID
  118. // but avoids joining with `user` for performance reasons
  119. // User permissions must be verified elsewhere if required
  120. func GetRepoWatchersIDs(ctx context.Context, repoID int64) ([]int64, error) {
  121. ids := make([]int64, 0, 64)
  122. return ids, db.GetEngine(ctx).Table("watch").
  123. Where("watch.repo_id=?", repoID).
  124. And("watch.mode<>?", WatchModeDont).
  125. Select("user_id").
  126. Find(&ids)
  127. }
  128. // GetRepoWatchers returns range of users watching given repository.
  129. func GetRepoWatchers(ctx context.Context, repoID int64, opts db.ListOptions) ([]*user_model.User, error) {
  130. sess := db.GetEngine(ctx).Where("watch.repo_id=?", repoID).
  131. Join("LEFT", "watch", "`user`.id=`watch`.user_id").
  132. And("`watch`.mode<>?", WatchModeDont)
  133. if opts.Page > 0 {
  134. sess = db.SetSessionPagination(sess, &opts)
  135. users := make([]*user_model.User, 0, opts.PageSize)
  136. return users, sess.Find(&users)
  137. }
  138. users := make([]*user_model.User, 0, 8)
  139. return users, sess.Find(&users)
  140. }
  141. // WatchIfAuto subscribes to repo if AutoWatchOnChanges is set
  142. func WatchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error {
  143. if !isWrite || !setting.Service.AutoWatchOnChanges {
  144. return nil
  145. }
  146. watch, err := GetWatch(ctx, userID, repoID)
  147. if err != nil {
  148. return err
  149. }
  150. if watch.Mode != WatchModeNone {
  151. return nil
  152. }
  153. return watchRepoMode(ctx, watch, WatchModeAuto)
  154. }