this repo has no description
1package handler
2
3import (
4 "errors"
5 "fmt"
6 "net/http"
7
8 "tangled.org/core/api/tangled"
9 "tangled.org/core/appview/db"
10 "tangled.org/core/appview/models"
11 "tangled.org/core/appview/pages"
12 "tangled.org/core/appview/pagination"
13 "tangled.org/core/appview/reporesolver"
14 isvc "tangled.org/core/appview/service/issue"
15 rsvc "tangled.org/core/appview/service/repo"
16 "tangled.org/core/appview/session"
17 "tangled.org/core/appview/web/request"
18 "tangled.org/core/log"
19 "tangled.org/core/orm"
20)
21
22func RepoIssues(is isvc.Service, rs rsvc.Service, p *pages.Pages, d *db.DB) http.HandlerFunc {
23 return func(w http.ResponseWriter, r *http.Request) {
24 ctx := r.Context()
25 l := log.FromContext(ctx).With("handler", "RepoIssues")
26 repo, ok := request.RepoFromContext(ctx)
27 if !ok {
28 l.Error("malformed request")
29 p.Error503(w)
30 return
31 }
32 repoOwnerId, ok := request.OwnerFromContext(ctx)
33 if !ok {
34 l.Error("malformed request")
35 p.Error503(w)
36 return
37 }
38
39 query := r.URL.Query()
40 searchOpts := models.IssueSearchOptions{
41 RepoAt: repo.RepoAt().String(),
42 Keyword: query.Get("q"),
43 IsOpen: query.Get("state") != "closed",
44 Page: pagination.FromContext(ctx),
45 }
46
47 issues, err := is.GetIssues(ctx, repo, searchOpts)
48 if err != nil {
49 l.Error("failed to get issues")
50 p.Error503(w)
51 return
52 }
53
54 // render page
55 err = func() error {
56 user := session.UserFromContext(ctx)
57 repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, repo, "", "", user)
58 if err != nil {
59 return err
60 }
61 labelDefs, err := db.GetLabelDefinitions(
62 d,
63 orm.FilterIn("at_uri", repo.Labels),
64 orm.FilterContains("scope", tangled.RepoIssueNSID),
65 )
66 if err != nil {
67 return err
68 }
69 defs := make(map[string]*models.LabelDefinition)
70 for _, l := range labelDefs {
71 defs[l.AtUri().String()] = &l
72 }
73 return p.RepoIssues(w, pages.RepoIssuesParams{
74 LoggedInUser: user,
75 RepoInfo: *repoinfo,
76
77 Issues: issues,
78 LabelDefs: defs,
79 FilteringByOpen: searchOpts.IsOpen,
80 FilterQuery: searchOpts.Keyword,
81 Page: searchOpts.Page,
82 })
83 }()
84 if err != nil {
85 l.Error("failed to render", "err", err)
86 p.Error503(w)
87 return
88 }
89 }
90}
91
92func Issue(s isvc.Service, rs rsvc.Service, p *pages.Pages, d *db.DB) http.HandlerFunc {
93 return func(w http.ResponseWriter, r *http.Request) {
94 ctx := r.Context()
95 l := log.FromContext(ctx).With("handler", "Issue")
96 issue, ok := request.IssueFromContext(ctx)
97 if !ok {
98 l.Error("malformed request, failed to get issue")
99 p.Error503(w)
100 return
101 }
102 repoOwnerId, ok := request.OwnerFromContext(ctx)
103 if !ok {
104 l.Error("malformed request")
105 p.Error503(w)
106 return
107 }
108
109 // render
110 err := func() error {
111 user := session.UserFromContext(ctx)
112 repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, issue.Repo, "", "", user)
113 if err != nil {
114 l.Error("failed to load repo", "err", err)
115 return err
116 }
117
118 reactionMap, err := db.GetReactionMap(d, 20, issue.AtUri())
119 if err != nil {
120 l.Error("failed to get issue reactions", "err", err)
121 return err
122 }
123
124 userReactions := map[models.ReactionKind]bool{}
125 if user != nil {
126 userReactions = db.GetReactionStatusMap(d, user.Did, issue.AtUri())
127 }
128
129 backlinks, err := db.GetBacklinks(d, issue.AtUri())
130 if err != nil {
131 l.Error("failed to fetch backlinks", "err", err)
132 return err
133 }
134
135 labelDefs, err := db.GetLabelDefinitions(
136 d,
137 orm.FilterIn("at_uri", issue.Repo.Labels),
138 orm.FilterContains("scope", tangled.RepoIssueNSID),
139 )
140 if err != nil {
141 l.Error("failed to fetch label defs", "err", err)
142 return err
143 }
144
145 defs := make(map[string]*models.LabelDefinition)
146 for _, l := range labelDefs {
147 defs[l.AtUri().String()] = &l
148 }
149
150 return p.RepoSingleIssue(w, pages.RepoSingleIssueParams{
151 LoggedInUser: user,
152 RepoInfo: *repoinfo,
153 Issue: issue,
154 CommentList: issue.CommentList(),
155 Backlinks: backlinks,
156 OrderedReactionKinds: models.OrderedReactionKinds,
157 Reactions: reactionMap,
158 UserReacted: userReactions,
159 LabelDefs: defs,
160 })
161 }()
162 if err != nil {
163 l.Error("failed to render", "err", err)
164 p.Error503(w)
165 return
166 }
167 }
168}
169
170func NewIssue(rs rsvc.Service, p *pages.Pages) http.HandlerFunc {
171 return func(w http.ResponseWriter, r *http.Request) {
172 ctx := r.Context()
173 l := log.FromContext(ctx).With("handler", "NewIssue")
174
175 // render
176 err := func() error {
177 user := session.UserFromContext(ctx)
178 repo, ok := request.RepoFromContext(ctx)
179 if !ok {
180 return fmt.Errorf("malformed request")
181 }
182 repoOwnerId, ok := request.OwnerFromContext(ctx)
183 if !ok {
184 return fmt.Errorf("malformed request")
185 }
186 repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, repo, "", "", user)
187 if err != nil {
188 return err
189 }
190 return p.RepoNewIssue(w, pages.RepoNewIssueParams{
191 LoggedInUser: user,
192 RepoInfo: *repoinfo,
193 })
194 }()
195 if err != nil {
196 l.Error("failed to render", "err", err)
197 p.Error503(w)
198 return
199 }
200 }
201}
202
203func NewIssuePost(is isvc.Service, p *pages.Pages) http.HandlerFunc {
204 noticeId := "issues"
205 return func(w http.ResponseWriter, r *http.Request) {
206 ctx := r.Context()
207 l := log.FromContext(ctx).With("handler", "NewIssuePost")
208 repo, ok := request.RepoFromContext(ctx)
209 if !ok {
210 l.Error("malformed request, failed to get repo")
211 // TODO: 503 error with more detailed messages
212 p.Error503(w)
213 return
214 }
215 var (
216 title = r.FormValue("title")
217 body = r.FormValue("body")
218 )
219
220 _, err := is.NewIssue(ctx, repo, title, body)
221 if err != nil {
222 if errors.Is(err, isvc.ErrDatabaseFail) {
223 p.Notice(w, noticeId, "Failed to create issue.")
224 } else if errors.Is(err, isvc.ErrPDSFail) {
225 p.Notice(w, noticeId, "Failed to create issue.")
226 } else {
227 p.Notice(w, noticeId, "Failed to create issue.")
228 }
229 return
230 }
231 p.HxLocation(w, "/")
232 }
233}
234
235func IssueEdit(is isvc.Service, rs rsvc.Service, p *pages.Pages) http.HandlerFunc {
236 return func(w http.ResponseWriter, r *http.Request) {
237 ctx := r.Context()
238 l := log.FromContext(ctx).With("handler", "IssueEdit")
239 issue, ok := request.IssueFromContext(ctx)
240 if !ok {
241 l.Error("malformed request, failed to get issue")
242 p.Error503(w)
243 return
244 }
245 repoOwnerId, ok := request.OwnerFromContext(ctx)
246 if !ok {
247 l.Error("malformed request")
248 p.Error503(w)
249 return
250 }
251
252 // render
253 err := func() error {
254 user := session.UserFromContext(ctx)
255 repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, issue.Repo, "", "", user)
256 if err != nil {
257 return err
258 }
259 return p.EditIssueFragment(w, pages.EditIssueParams{
260 LoggedInUser: user,
261 RepoInfo: *repoinfo,
262
263 Issue: issue,
264 })
265 }()
266 if err != nil {
267 l.Error("failed to render", "err", err)
268 p.Error503(w)
269 return
270 }
271 }
272}
273
274func IssueEditPost(is isvc.Service, p *pages.Pages) http.HandlerFunc {
275 noticeId := "issues"
276 return func(w http.ResponseWriter, r *http.Request) {
277 ctx := r.Context()
278 l := log.FromContext(ctx).With("handler", "IssueEdit")
279 issue, ok := request.IssueFromContext(ctx)
280 if !ok {
281 l.Error("malformed request, failed to get issue")
282 p.Error503(w)
283 return
284 }
285
286 newIssue := *issue
287 newIssue.Title = r.FormValue("title")
288 newIssue.Body = r.FormValue("body")
289
290 err := is.EditIssue(ctx, &newIssue)
291 if err != nil {
292 if errors.Is(err, isvc.ErrDatabaseFail) {
293 p.Notice(w, noticeId, "Failed to edit issue.")
294 } else if errors.Is(err, isvc.ErrPDSFail) {
295 p.Notice(w, noticeId, "Failed to edit issue.")
296 } else {
297 p.Notice(w, noticeId, "Failed to edit issue.")
298 }
299 return
300 }
301
302 p.HxRefresh(w)
303 }
304}
305
306func CloseIssue(is isvc.Service, p *pages.Pages) http.HandlerFunc {
307 noticeId := "issue-action"
308 return func(w http.ResponseWriter, r *http.Request) {
309 ctx := r.Context()
310 l := log.FromContext(ctx).With("handler", "CloseIssue")
311 issue, ok := request.IssueFromContext(ctx)
312 if !ok {
313 l.Error("malformed request, failed to get issue")
314 p.Error503(w)
315 return
316 }
317
318 err := is.CloseIssue(ctx, issue)
319 if err != nil {
320 if errors.Is(err, isvc.ErrForbidden) {
321 http.Error(w, "forbidden", http.StatusUnauthorized)
322 } else {
323 p.Notice(w, noticeId, "Failed to close issue. Try again later.")
324 }
325 return
326 }
327
328 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, issue.Repo)
329 p.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
330 }
331}
332
333func ReopenIssue(is isvc.Service, p *pages.Pages) http.HandlerFunc {
334 noticeId := "issue-action"
335 return func(w http.ResponseWriter, r *http.Request) {
336 ctx := r.Context()
337 l := log.FromContext(ctx).With("handler", "ReopenIssue")
338 issue, ok := request.IssueFromContext(ctx)
339 if !ok {
340 l.Error("malformed request, failed to get issue")
341 p.Error503(w)
342 return
343 }
344
345 err := is.ReopenIssue(ctx, issue)
346 if err != nil {
347 if errors.Is(err, isvc.ErrForbidden) {
348 http.Error(w, "forbidden", http.StatusUnauthorized)
349 } else {
350 p.Notice(w, noticeId, "Failed to reopen issue. Try again later.")
351 }
352 return
353 }
354
355 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, issue.Repo)
356 p.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
357 }
358}
359
360func IssueDelete(s isvc.Service, p *pages.Pages) http.HandlerFunc {
361 noticeId := "issue-actions-error"
362 return func(w http.ResponseWriter, r *http.Request) {
363 ctx := r.Context()
364 l := log.FromContext(ctx).With("handler", "IssueDelete")
365 issue, ok := request.IssueFromContext(ctx)
366 if !ok {
367 l.Error("failed to get issue")
368 // TODO: 503 error with more detailed messages
369 p.Error503(w)
370 return
371 }
372 err := s.DeleteIssue(ctx, issue)
373 if err != nil {
374 p.Notice(w, noticeId, "failed to delete issue")
375 return
376 }
377 p.HxLocation(w, "/")
378 }
379}