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}