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