repos.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Copyright 2020 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package db
  5. import (
  6. "context"
  7. "fmt"
  8. "strings"
  9. "time"
  10. api "github.com/gogs/go-gogs-client"
  11. "gorm.io/gorm"
  12. "gogs.io/gogs/internal/errutil"
  13. "gogs.io/gogs/internal/repoutil"
  14. )
  15. // ReposStore is the persistent interface for repositories.
  16. //
  17. // NOTE: All methods are sorted in alphabetical order.
  18. type ReposStore interface {
  19. // Create creates a new repository record in the database. It returns
  20. // ErrNameNotAllowed when the repository name is not allowed, or
  21. // ErrRepoAlreadyExist when a repository with same name already exists for the
  22. // owner.
  23. Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error)
  24. // GetByCollaboratorID returns a list of repositories that the given
  25. // collaborator has access to. Results are limited to the given limit and sorted
  26. // by the given order (e.g. "updated_unix DESC"). Repositories that are owned
  27. // directly by the given collaborator are not included.
  28. GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error)
  29. // GetByCollaboratorIDWithAccessMode returns a list of repositories and
  30. // corresponding access mode that the given collaborator has access to.
  31. // Repositories that are owned directly by the given collaborator are not
  32. // included.
  33. GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error)
  34. // GetByName returns the repository with given owner and name. It returns
  35. // ErrRepoNotExist when not found.
  36. GetByName(ctx context.Context, ownerID int64, name string) (*Repository, error)
  37. // Touch updates the updated time to the current time and removes the bare state
  38. // of the given repository.
  39. Touch(ctx context.Context, id int64) error
  40. }
  41. var Repos ReposStore
  42. // BeforeCreate implements the GORM create hook.
  43. func (r *Repository) BeforeCreate(tx *gorm.DB) error {
  44. if r.CreatedUnix == 0 {
  45. r.CreatedUnix = tx.NowFunc().Unix()
  46. }
  47. return nil
  48. }
  49. // BeforeUpdate implements the GORM update hook.
  50. func (r *Repository) BeforeUpdate(tx *gorm.DB) error {
  51. r.UpdatedUnix = tx.NowFunc().Unix()
  52. return nil
  53. }
  54. // AfterFind implements the GORM query hook.
  55. func (r *Repository) AfterFind(_ *gorm.DB) error {
  56. r.Created = time.Unix(r.CreatedUnix, 0).Local()
  57. r.Updated = time.Unix(r.UpdatedUnix, 0).Local()
  58. return nil
  59. }
  60. type RepositoryAPIFormatOptions struct {
  61. Permission *api.Permission
  62. Parent *api.Repository
  63. }
  64. // APIFormat returns the API format of a repository.
  65. func (r *Repository) APIFormat(owner *User, opts ...RepositoryAPIFormatOptions) *api.Repository {
  66. var opt RepositoryAPIFormatOptions
  67. if len(opts) > 0 {
  68. opt = opts[0]
  69. }
  70. cloneLink := repoutil.NewCloneLink(owner.Name, r.Name, false)
  71. return &api.Repository{
  72. ID: r.ID,
  73. Owner: owner.APIFormat(),
  74. Name: r.Name,
  75. FullName: owner.Name + "/" + r.Name,
  76. Description: r.Description,
  77. Private: r.IsPrivate,
  78. Fork: r.IsFork,
  79. Parent: opt.Parent,
  80. Empty: r.IsBare,
  81. Mirror: r.IsMirror,
  82. Size: r.Size,
  83. HTMLURL: repoutil.HTMLURL(owner.Name, r.Name),
  84. SSHURL: cloneLink.SSH,
  85. CloneURL: cloneLink.HTTPS,
  86. Website: r.Website,
  87. Stars: r.NumStars,
  88. Forks: r.NumForks,
  89. Watchers: r.NumWatches,
  90. OpenIssues: r.NumOpenIssues,
  91. DefaultBranch: r.DefaultBranch,
  92. Created: r.Created,
  93. Updated: r.Updated,
  94. Permissions: opt.Permission,
  95. }
  96. }
  97. var _ ReposStore = (*repos)(nil)
  98. type repos struct {
  99. *gorm.DB
  100. }
  101. // NewReposStore returns a persistent interface for repositories with given
  102. // database connection.
  103. func NewReposStore(db *gorm.DB) ReposStore {
  104. return &repos{DB: db}
  105. }
  106. type ErrRepoAlreadyExist struct {
  107. args errutil.Args
  108. }
  109. func IsErrRepoAlreadyExist(err error) bool {
  110. _, ok := err.(ErrRepoAlreadyExist)
  111. return ok
  112. }
  113. func (err ErrRepoAlreadyExist) Error() string {
  114. return fmt.Sprintf("repository already exists: %v", err.args)
  115. }
  116. type CreateRepoOptions struct {
  117. Name string
  118. Description string
  119. DefaultBranch string
  120. Private bool
  121. Mirror bool
  122. EnableWiki bool
  123. EnableIssues bool
  124. EnablePulls bool
  125. Fork bool
  126. ForkID int64
  127. }
  128. func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error) {
  129. err := isRepoNameAllowed(opts.Name)
  130. if err != nil {
  131. return nil, err
  132. }
  133. _, err = db.GetByName(ctx, ownerID, opts.Name)
  134. if err == nil {
  135. return nil, ErrRepoAlreadyExist{
  136. args: errutil.Args{
  137. "ownerID": ownerID,
  138. "name": opts.Name,
  139. },
  140. }
  141. } else if !IsErrRepoNotExist(err) {
  142. return nil, err
  143. }
  144. repo := &Repository{
  145. OwnerID: ownerID,
  146. LowerName: strings.ToLower(opts.Name),
  147. Name: opts.Name,
  148. Description: opts.Description,
  149. DefaultBranch: opts.DefaultBranch,
  150. IsPrivate: opts.Private,
  151. IsMirror: opts.Mirror,
  152. EnableWiki: opts.EnableWiki,
  153. EnableIssues: opts.EnableIssues,
  154. EnablePulls: opts.EnablePulls,
  155. IsFork: opts.Fork,
  156. ForkID: opts.ForkID,
  157. }
  158. return repo, db.WithContext(ctx).Create(repo).Error
  159. }
  160. func (db *repos) GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error) {
  161. /*
  162. Equivalent SQL for PostgreSQL:
  163. SELECT * FROM repository
  164. JOIN access ON access.repo_id = repository.id AND access.user_id = @collaboratorID
  165. WHERE access.mode >= @accessModeRead
  166. ORDER BY @orderBy
  167. LIMIT @limit
  168. */
  169. var repos []*Repository
  170. return repos, db.WithContext(ctx).
  171. Joins("JOIN access ON access.repo_id = repository.id AND access.user_id = ?", collaboratorID).
  172. Where("access.mode >= ?", AccessModeRead).
  173. Order(orderBy).
  174. Limit(limit).
  175. Find(&repos).
  176. Error
  177. }
  178. func (db *repos) GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error) {
  179. /*
  180. Equivalent SQL for PostgreSQL:
  181. SELECT
  182. repository.*,
  183. access.mode
  184. FROM repository
  185. JOIN access ON access.repo_id = repository.id AND access.user_id = @collaboratorID
  186. WHERE access.mode >= @accessModeRead
  187. */
  188. var reposWithAccessMode []*struct {
  189. *Repository
  190. Mode AccessMode
  191. }
  192. err := db.WithContext(ctx).
  193. Select("repository.*", "access.mode").
  194. Table("repository").
  195. Joins("JOIN access ON access.repo_id = repository.id AND access.user_id = ?", collaboratorID).
  196. Where("access.mode >= ?", AccessModeRead).
  197. Find(&reposWithAccessMode).
  198. Error
  199. if err != nil {
  200. return nil, err
  201. }
  202. repos := make(map[*Repository]AccessMode, len(reposWithAccessMode))
  203. for _, repoWithAccessMode := range reposWithAccessMode {
  204. repos[repoWithAccessMode.Repository] = repoWithAccessMode.Mode
  205. }
  206. return repos, nil
  207. }
  208. var _ errutil.NotFound = (*ErrRepoNotExist)(nil)
  209. type ErrRepoNotExist struct {
  210. args errutil.Args
  211. }
  212. func IsErrRepoNotExist(err error) bool {
  213. _, ok := err.(ErrRepoNotExist)
  214. return ok
  215. }
  216. func (err ErrRepoNotExist) Error() string {
  217. return fmt.Sprintf("repository does not exist: %v", err.args)
  218. }
  219. func (ErrRepoNotExist) NotFound() bool {
  220. return true
  221. }
  222. func (db *repos) GetByName(ctx context.Context, ownerID int64, name string) (*Repository, error) {
  223. repo := new(Repository)
  224. err := db.WithContext(ctx).
  225. Where("owner_id = ? AND lower_name = ?", ownerID, strings.ToLower(name)).
  226. First(repo).
  227. Error
  228. if err != nil {
  229. if err == gorm.ErrRecordNotFound {
  230. return nil, ErrRepoNotExist{
  231. args: errutil.Args{
  232. "ownerID": ownerID,
  233. "name": name,
  234. },
  235. }
  236. }
  237. return nil, err
  238. }
  239. return repo, nil
  240. }
  241. func (db *repos) Touch(ctx context.Context, id int64) error {
  242. return db.WithContext(ctx).
  243. Model(new(Repository)).
  244. Where("id = ?", id).
  245. Updates(map[string]any{
  246. "is_bare": false,
  247. "updated_unix": db.NowFunc().Unix(),
  248. }).
  249. Error
  250. }