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.

issue.go 8.0KB

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