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.

topic.go 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2018 The Gitea 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 models
  5. import (
  6. "fmt"
  7. "strings"
  8. "code.gitea.io/gitea/modules/util"
  9. "github.com/go-xorm/builder"
  10. )
  11. func init() {
  12. tables = append(tables,
  13. new(Topic),
  14. new(RepoTopic),
  15. )
  16. }
  17. // Topic represents a topic of repositories
  18. type Topic struct {
  19. ID int64
  20. Name string `xorm:"unique"`
  21. RepoCount int
  22. CreatedUnix util.TimeStamp `xorm:"INDEX created"`
  23. UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
  24. }
  25. // RepoTopic represents associated repositories and topics
  26. type RepoTopic struct {
  27. RepoID int64 `xorm:"unique(s)"`
  28. TopicID int64 `xorm:"unique(s)"`
  29. }
  30. // ErrTopicNotExist represents an error that a topic is not exist
  31. type ErrTopicNotExist struct {
  32. Name string
  33. }
  34. // IsErrTopicNotExist checks if an error is an ErrTopicNotExist.
  35. func IsErrTopicNotExist(err error) bool {
  36. _, ok := err.(ErrTopicNotExist)
  37. return ok
  38. }
  39. // Error implements error interface
  40. func (err ErrTopicNotExist) Error() string {
  41. return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
  42. }
  43. // GetTopicByName retrieves topic by name
  44. func GetTopicByName(name string) (*Topic, error) {
  45. var topic Topic
  46. if has, err := x.Where("name = ?", name).Get(&topic); err != nil {
  47. return nil, err
  48. } else if !has {
  49. return nil, ErrTopicNotExist{name}
  50. }
  51. return &topic, nil
  52. }
  53. // FindTopicOptions represents the options when fdin topics
  54. type FindTopicOptions struct {
  55. RepoID int64
  56. Keyword string
  57. Limit int
  58. Page int
  59. }
  60. func (opts *FindTopicOptions) toConds() builder.Cond {
  61. var cond = builder.NewCond()
  62. if opts.RepoID > 0 {
  63. cond = cond.And(builder.Eq{"repo_topic.repo_id": opts.RepoID})
  64. }
  65. if opts.Keyword != "" {
  66. cond = cond.And(builder.Like{"topic.name", opts.Keyword})
  67. }
  68. return cond
  69. }
  70. // FindTopics retrieves the topics via FindTopicOptions
  71. func FindTopics(opts *FindTopicOptions) (topics []*Topic, err error) {
  72. sess := x.Select("topic.*").Where(opts.toConds())
  73. if opts.RepoID > 0 {
  74. sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id")
  75. }
  76. if opts.Limit > 0 {
  77. sess.Limit(opts.Limit, opts.Page*opts.Limit)
  78. }
  79. return topics, sess.Desc("topic.repo_count").Find(&topics)
  80. }
  81. // SaveTopics save topics to a repository
  82. func SaveTopics(repoID int64, topicNames ...string) error {
  83. topics, err := FindTopics(&FindTopicOptions{
  84. RepoID: repoID,
  85. })
  86. if err != nil {
  87. return err
  88. }
  89. sess := x.NewSession()
  90. defer sess.Close()
  91. if err := sess.Begin(); err != nil {
  92. return err
  93. }
  94. var addedTopicNames []string
  95. for _, topicName := range topicNames {
  96. if strings.TrimSpace(topicName) == "" {
  97. continue
  98. }
  99. var found bool
  100. for _, t := range topics {
  101. if strings.EqualFold(topicName, t.Name) {
  102. found = true
  103. break
  104. }
  105. }
  106. if !found {
  107. addedTopicNames = append(addedTopicNames, topicName)
  108. }
  109. }
  110. var removeTopics []*Topic
  111. for _, t := range topics {
  112. var found bool
  113. for _, topicName := range topicNames {
  114. if strings.EqualFold(topicName, t.Name) {
  115. found = true
  116. break
  117. }
  118. }
  119. if !found {
  120. removeTopics = append(removeTopics, t)
  121. }
  122. }
  123. for _, topicName := range addedTopicNames {
  124. var topic Topic
  125. if has, err := sess.Where("name = ?", topicName).Get(&topic); err != nil {
  126. return err
  127. } else if !has {
  128. topic.Name = topicName
  129. topic.RepoCount = 1
  130. if _, err := sess.Insert(&topic); err != nil {
  131. return err
  132. }
  133. } else {
  134. topic.RepoCount++
  135. if _, err := sess.ID(topic.ID).Cols("repo_count").Update(&topic); err != nil {
  136. return err
  137. }
  138. }
  139. if _, err := sess.Insert(&RepoTopic{
  140. RepoID: repoID,
  141. TopicID: topic.ID,
  142. }); err != nil {
  143. return err
  144. }
  145. }
  146. for _, topic := range removeTopics {
  147. topic.RepoCount--
  148. if _, err := sess.ID(topic.ID).Cols("repo_count").Update(topic); err != nil {
  149. return err
  150. }
  151. if _, err := sess.Delete(&RepoTopic{
  152. RepoID: repoID,
  153. TopicID: topic.ID,
  154. }); err != nil {
  155. return err
  156. }
  157. }
  158. if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
  159. Topics: topicNames,
  160. }); err != nil {
  161. return err
  162. }
  163. return sess.Commit()
  164. }