gitea源码

issue.go 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package convert
  4. import (
  5. "context"
  6. "fmt"
  7. "net/url"
  8. "strings"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. user_model "code.gitea.io/gitea/models/user"
  12. "code.gitea.io/gitea/modules/label"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. api "code.gitea.io/gitea/modules/structs"
  16. "code.gitea.io/gitea/modules/util"
  17. )
  18. func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
  19. return toIssue(ctx, doer, issue, WebAssetDownloadURL)
  20. }
  21. // ToAPIIssue converts an Issue to API format
  22. // it assumes some fields assigned with values:
  23. // Required - Poster, Labels,
  24. // Optional - Milestone, Assignee, PullRequest
  25. func ToAPIIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
  26. return toIssue(ctx, doer, issue, APIAssetDownloadURL)
  27. }
  28. func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
  29. if err := issue.LoadPoster(ctx); err != nil {
  30. return &api.Issue{}
  31. }
  32. if err := issue.LoadRepo(ctx); err != nil {
  33. return &api.Issue{}
  34. }
  35. if err := issue.LoadAttachments(ctx); err != nil {
  36. return &api.Issue{}
  37. }
  38. if err := issue.LoadPinOrder(ctx); err != nil {
  39. return &api.Issue{}
  40. }
  41. apiIssue := &api.Issue{
  42. ID: issue.ID,
  43. Index: issue.Index,
  44. Poster: ToUser(ctx, issue.Poster, doer),
  45. Title: issue.Title,
  46. Body: issue.Content,
  47. Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL),
  48. Ref: issue.Ref,
  49. State: issue.State(),
  50. IsLocked: issue.IsLocked,
  51. Comments: issue.NumComments,
  52. Created: issue.CreatedUnix.AsTime(),
  53. Updated: issue.UpdatedUnix.AsTime(),
  54. PinOrder: util.Iif(issue.PinOrder == -1, 0, issue.PinOrder), // -1 means loaded with no pin order
  55. TimeEstimate: issue.TimeEstimate,
  56. }
  57. if issue.Repo != nil {
  58. if err := issue.Repo.LoadOwner(ctx); err != nil {
  59. return &api.Issue{}
  60. }
  61. apiIssue.URL = issue.APIURL(ctx)
  62. apiIssue.HTMLURL = issue.HTMLURL(ctx)
  63. if err := issue.LoadLabels(ctx); err != nil {
  64. return &api.Issue{}
  65. }
  66. apiIssue.Labels = util.SliceNilAsEmpty(ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner))
  67. apiIssue.Repo = &api.RepositoryMeta{
  68. ID: issue.Repo.ID,
  69. Name: issue.Repo.Name,
  70. Owner: issue.Repo.OwnerName,
  71. FullName: issue.Repo.FullName(),
  72. }
  73. }
  74. if issue.ClosedUnix != 0 {
  75. apiIssue.Closed = issue.ClosedUnix.AsTimePtr()
  76. }
  77. if err := issue.LoadMilestone(ctx); err != nil {
  78. return &api.Issue{}
  79. }
  80. if issue.Milestone != nil {
  81. apiIssue.Milestone = ToAPIMilestone(issue.Milestone)
  82. }
  83. if err := issue.LoadAssignees(ctx); err != nil {
  84. return &api.Issue{}
  85. }
  86. if len(issue.Assignees) > 0 {
  87. for _, assignee := range issue.Assignees {
  88. apiIssue.Assignees = append(apiIssue.Assignees, ToUser(ctx, assignee, nil))
  89. }
  90. apiIssue.Assignee = ToUser(ctx, issue.Assignees[0], nil) // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
  91. }
  92. if issue.IsPull {
  93. if err := issue.LoadPullRequest(ctx); err != nil {
  94. return &api.Issue{}
  95. }
  96. if issue.PullRequest != nil {
  97. apiIssue.PullRequest = &api.PullRequestMeta{
  98. HasMerged: issue.PullRequest.HasMerged,
  99. IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx),
  100. }
  101. if issue.PullRequest.HasMerged {
  102. apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
  103. }
  104. // Add pr's html url
  105. apiIssue.PullRequest.HTMLURL = issue.HTMLURL(ctx)
  106. }
  107. }
  108. if issue.DeadlineUnix != 0 {
  109. apiIssue.Deadline = issue.DeadlineUnix.AsTimePtr()
  110. }
  111. return apiIssue
  112. }
  113. // ToIssueList converts an IssueList to API format
  114. func ToIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
  115. result := make([]*api.Issue, len(il))
  116. _ = il.LoadPinOrder(ctx)
  117. for i := range il {
  118. result[i] = ToIssue(ctx, doer, il[i])
  119. }
  120. return result
  121. }
  122. // ToAPIIssueList converts an IssueList to API format
  123. func ToAPIIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
  124. result := make([]*api.Issue, len(il))
  125. _ = il.LoadPinOrder(ctx)
  126. for i := range il {
  127. result[i] = ToAPIIssue(ctx, doer, il[i])
  128. }
  129. return result
  130. }
  131. // ToTrackedTime converts TrackedTime to API format
  132. func ToTrackedTime(ctx context.Context, doer *user_model.User, t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
  133. apiT = &api.TrackedTime{
  134. ID: t.ID,
  135. IssueID: t.IssueID,
  136. UserID: t.UserID,
  137. Time: t.Time,
  138. Created: t.Created,
  139. }
  140. if t.Issue != nil {
  141. apiT.Issue = ToAPIIssue(ctx, doer, t.Issue)
  142. }
  143. if t.User != nil {
  144. apiT.UserName = t.User.Name
  145. }
  146. return apiT
  147. }
  148. // ToStopWatches convert Stopwatch list to api.StopWatches
  149. func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.StopWatches, error) {
  150. result := api.StopWatches(make([]api.StopWatch, 0, len(sws)))
  151. issueCache := make(map[int64]*issues_model.Issue)
  152. repoCache := make(map[int64]*repo_model.Repository)
  153. var (
  154. issue *issues_model.Issue
  155. repo *repo_model.Repository
  156. ok bool
  157. err error
  158. )
  159. for _, sw := range sws {
  160. issue, ok = issueCache[sw.IssueID]
  161. if !ok {
  162. issue, err = issues_model.GetIssueByID(ctx, sw.IssueID)
  163. if err != nil {
  164. return nil, err
  165. }
  166. }
  167. repo, ok = repoCache[issue.RepoID]
  168. if !ok {
  169. repo, err = repo_model.GetRepositoryByID(ctx, issue.RepoID)
  170. if err != nil {
  171. return nil, err
  172. }
  173. }
  174. result = append(result, api.StopWatch{
  175. Created: sw.CreatedUnix.AsTime(),
  176. Seconds: sw.Seconds(),
  177. Duration: util.SecToHours(sw.Seconds()),
  178. IssueIndex: issue.Index,
  179. IssueTitle: issue.Title,
  180. RepoOwnerName: repo.OwnerName,
  181. RepoName: repo.Name,
  182. })
  183. }
  184. return result, nil
  185. }
  186. // ToTrackedTimeList converts TrackedTimeList to API format
  187. func ToTrackedTimeList(ctx context.Context, doer *user_model.User, tl issues_model.TrackedTimeList) api.TrackedTimeList {
  188. result := make([]*api.TrackedTime, 0, len(tl))
  189. for _, t := range tl {
  190. result = append(result, ToTrackedTime(ctx, doer, t))
  191. }
  192. return result
  193. }
  194. // ToLabel converts Label to API format
  195. func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_model.User) *api.Label {
  196. result := &api.Label{
  197. ID: label.ID,
  198. Name: label.Name,
  199. Exclusive: label.Exclusive,
  200. Color: strings.TrimLeft(label.Color, "#"),
  201. Description: label.Description,
  202. IsArchived: label.IsArchived(),
  203. }
  204. labelBelongsToRepo := label.BelongsToRepo()
  205. // calculate URL
  206. if labelBelongsToRepo && repo != nil {
  207. result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
  208. } else { // BelongsToOrg
  209. if org != nil {
  210. result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID)
  211. } else {
  212. log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID)
  213. }
  214. }
  215. if labelBelongsToRepo && repo == nil {
  216. log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
  217. }
  218. return result
  219. }
  220. // ToLabelList converts list of Label to API format
  221. func ToLabelList(labels []*issues_model.Label, repo *repo_model.Repository, org *user_model.User) []*api.Label {
  222. result := make([]*api.Label, len(labels))
  223. for i := range labels {
  224. result[i] = ToLabel(labels[i], repo, org)
  225. }
  226. return result
  227. }
  228. // ToAPIMilestone converts Milestone into API Format
  229. func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone {
  230. apiMilestone := &api.Milestone{
  231. ID: m.ID,
  232. State: m.State(),
  233. Title: m.Name,
  234. Description: m.Content,
  235. OpenIssues: m.NumOpenIssues,
  236. ClosedIssues: m.NumClosedIssues,
  237. Created: m.CreatedUnix.AsTime(),
  238. Updated: m.UpdatedUnix.AsTimePtr(),
  239. }
  240. if m.IsClosed {
  241. apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr()
  242. }
  243. if m.DeadlineUnix > 0 {
  244. apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr()
  245. }
  246. return apiMilestone
  247. }
  248. // ToLabelTemplate converts Label to API format
  249. func ToLabelTemplate(label *label.Label) *api.LabelTemplate {
  250. result := &api.LabelTemplate{
  251. Name: label.Name,
  252. Exclusive: label.Exclusive,
  253. Color: strings.TrimLeft(label.Color, "#"),
  254. Description: label.Description,
  255. }
  256. return result
  257. }
  258. // ToLabelTemplateList converts list of Label to API format
  259. func ToLabelTemplateList(labels []*label.Label) []*api.LabelTemplate {
  260. result := make([]*api.LabelTemplate, len(labels))
  261. for i := range labels {
  262. result[i] = ToLabelTemplate(labels[i])
  263. }
  264. return result
  265. }