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