package handler import ( "errors" "fmt" "net/http" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/pages" "tangled.org/core/appview/pagination" "tangled.org/core/appview/reporesolver" isvc "tangled.org/core/appview/service/issue" rsvc "tangled.org/core/appview/service/repo" "tangled.org/core/appview/session" "tangled.org/core/appview/web/request" "tangled.org/core/log" "tangled.org/core/orm" ) func RepoIssues(is isvc.Service, rs rsvc.Service, p *pages.Pages, d *db.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "RepoIssues") repo, ok := request.RepoFromContext(ctx) if !ok { l.Error("malformed request") p.Error503(w) return } repoOwnerId, ok := request.OwnerFromContext(ctx) if !ok { l.Error("malformed request") p.Error503(w) return } query := r.URL.Query() searchOpts := models.IssueSearchOptions{ RepoAt: repo.RepoAt().String(), Keyword: query.Get("q"), IsOpen: query.Get("state") != "closed", Page: pagination.FromContext(ctx), } issues, err := is.GetIssues(ctx, repo, searchOpts) if err != nil { l.Error("failed to get issues") p.Error503(w) return } // render page err = func() error { labelDefs, err := db.GetLabelDefinitions( d, orm.FilterIn("at_uri", repo.Labels), orm.FilterContains("scope", tangled.RepoIssueNSID), ) if err != nil { return err } defs := make(map[string]*models.LabelDefinition) for _, l := range labelDefs { defs[l.AtUri().String()] = &l } return p.RepoIssues(w, pages.RepoIssuesParams{ LoggedInUser: session.UserFromContext(ctx), RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, repo, "", ""), Issues: issues, LabelDefs: defs, FilteringByOpen: searchOpts.IsOpen, FilterQuery: searchOpts.Keyword, Page: searchOpts.Page, }) }() if err != nil { l.Error("failed to render", "err", err) p.Error503(w) return } } } func Issue(s isvc.Service, rs rsvc.Service, p *pages.Pages, d *db.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "Issue") issue, ok := request.IssueFromContext(ctx) if !ok { l.Error("malformed request, failed to get issue") p.Error503(w) return } repoOwnerId, ok := request.OwnerFromContext(ctx) if !ok { l.Error("malformed request") p.Error503(w) return } // render err := func() error { reactionMap, err := db.GetReactionMap(d, 20, issue.AtUri()) if err != nil { l.Error("failed to get issue reactions", "err", err) return err } userReactions := map[models.ReactionKind]bool{} if sess, ok := session.FromContext(ctx); ok { userReactions = db.GetReactionStatusMap(d, sess.User.Did, issue.AtUri()) } backlinks, err := db.GetBacklinks(d, issue.AtUri()) if err != nil { l.Error("failed to fetch backlinks", "err", err) return err } labelDefs, err := db.GetLabelDefinitions( d, orm.FilterIn("at_uri", issue.Repo.Labels), orm.FilterContains("scope", tangled.RepoIssueNSID), ) if err != nil { l.Error("failed to fetch label defs", "err", err) return err } defs := make(map[string]*models.LabelDefinition) for _, l := range labelDefs { defs[l.AtUri().String()] = &l } return p.RepoSingleIssue(w, pages.RepoSingleIssueParams{ LoggedInUser: session.UserFromContext(ctx), RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, issue.Repo, "", ""), Issue: issue, CommentList: issue.CommentList(), Backlinks: backlinks, Reactions: reactionMap, UserReacted: userReactions, LabelDefs: defs, }) }() if err != nil { l.Error("failed to render", "err", err) p.Error503(w) return } } } func NewIssue(rs rsvc.Service, p *pages.Pages) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "NewIssue") // render err := func() error { repo, ok := request.RepoFromContext(ctx) if !ok { return fmt.Errorf("malformed request") } repoOwnerId, ok := request.OwnerFromContext(ctx) if !ok { return fmt.Errorf("malformed request") } return p.RepoNewIssue(w, pages.RepoNewIssueParams{ LoggedInUser: session.UserFromContext(ctx), RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, repo, "", ""), }) }() if err != nil { l.Error("failed to render", "err", err) p.Error503(w) return } } } func NewIssuePost(is isvc.Service, p *pages.Pages) http.HandlerFunc { noticeId := "issues" return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "NewIssuePost") repo, ok := request.RepoFromContext(ctx) if !ok { l.Error("malformed request, failed to get repo") // TODO: 503 error with more detailed messages p.Error503(w) return } var ( title = r.FormValue("title") body = r.FormValue("body") ) issue, err := is.NewIssue(ctx, repo, title, body) if err != nil { if errors.Is(err, isvc.ErrDatabaseFail) { p.Notice(w, noticeId, "Failed to create issue.") } else if errors.Is(err, isvc.ErrPDSFail) { p.Notice(w, noticeId, "Failed to create issue.") } else { p.Notice(w, noticeId, "Failed to create issue.") } return } ownerSlashRepo := reporesolver.GetBaseRepoPath(r, issue.Repo) p.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId)) } } func IssueEdit(is isvc.Service, rs rsvc.Service, p *pages.Pages) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "IssueEdit") issue, ok := request.IssueFromContext(ctx) if !ok { l.Error("malformed request, failed to get issue") p.Error503(w) return } repoOwnerId, ok := request.OwnerFromContext(ctx) if !ok { l.Error("malformed request") p.Error503(w) return } // render err := func() error { return p.EditIssueFragment(w, pages.EditIssueParams{ LoggedInUser: session.UserFromContext(ctx), RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, issue.Repo, "", ""), Issue: issue, }) }() if err != nil { l.Error("failed to render", "err", err) p.Error503(w) return } } } func IssueEditPost(is isvc.Service, p *pages.Pages) http.HandlerFunc { noticeId := "issues" return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "IssueEdit") issue, ok := request.IssueFromContext(ctx) if !ok { l.Error("malformed request, failed to get issue") p.Error503(w) return } newIssue := *issue newIssue.Title = r.FormValue("title") newIssue.Body = r.FormValue("body") err := is.EditIssue(ctx, &newIssue) if err != nil { if errors.Is(err, isvc.ErrDatabaseFail) { p.Notice(w, noticeId, "Failed to edit issue.") } else if errors.Is(err, isvc.ErrPDSFail) { p.Notice(w, noticeId, "Failed to edit issue.") } else { p.Notice(w, noticeId, "Failed to edit issue.") } return } p.HxRefresh(w) } } func CloseIssue(is isvc.Service, p *pages.Pages) http.HandlerFunc { noticeId := "issue-action" return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "CloseIssue") issue, ok := request.IssueFromContext(ctx) if !ok { l.Error("malformed request, failed to get issue") p.Error503(w) return } err := is.CloseIssue(ctx, issue) if err != nil { if errors.Is(err, isvc.ErrForbidden) { http.Error(w, "forbidden", http.StatusUnauthorized) } else { p.Notice(w, noticeId, "Failed to close issue. Try again later.") } return } ownerSlashRepo := reporesolver.GetBaseRepoPath(r, issue.Repo) p.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId)) } } func ReopenIssue(is isvc.Service, p *pages.Pages) http.HandlerFunc { noticeId := "issue-action" return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "ReopenIssue") issue, ok := request.IssueFromContext(ctx) if !ok { l.Error("malformed request, failed to get issue") p.Error503(w) return } err := is.ReopenIssue(ctx, issue) if err != nil { if errors.Is(err, isvc.ErrForbidden) { http.Error(w, "forbidden", http.StatusUnauthorized) } else { p.Notice(w, noticeId, "Failed to reopen issue. Try again later.") } return } ownerSlashRepo := reporesolver.GetBaseRepoPath(r, issue.Repo) p.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId)) } } func IssueDelete(s isvc.Service, p *pages.Pages) http.HandlerFunc { noticeId := "issue-actions-error" return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() l := log.FromContext(ctx).With("handler", "IssueDelete") issue, ok := request.IssueFromContext(ctx) if !ok { l.Error("failed to get issue") // TODO: 503 error with more detailed messages p.Error503(w) return } err := s.DeleteIssue(ctx, issue) if err != nil { p.Notice(w, noticeId, "failed to delete issue") return } p.HxLocation(w, "/") } }