--- a/internal/archesrv/handlers_issues.go
+++ b/internal/archesrv/handlers_issues.go
@@ -1,259 +1,324 @@
package archesrv
import (
"io"
"net/http"
"path/filepath"
"strings"
"arche/internal/issuedb"
)
func (s *forgeServer) openIssueDB(repoName string) (*issuedb.DB, error) {
dir := filepath.Join(repoPath(s.dataDir(), repoName), ".arche")
return issuedb.Open(dir)
}
func (s *forgeServer) openIssueDBWithStore(repoName string) (*issuedb.DB, io.Closer, error) {
r, err := openRepo(s.dataDir(), repoName)
if err != nil {
return nil, nil, err
}
dir := filepath.Join(repoPath(s.dataDir(), repoName), ".arche")
idb, err := issuedb.NewWithStore(dir, r.Store)
if err != nil {
r.Store.Close() //nolint:errcheck
return nil, nil, err
}
return idb, r.Store, nil
}
type srvIssuesData struct {
Repo string
User *User
Issues []issueStubView
}
type issueStubView struct {
ID string
Title string
Status string
}
func (s *forgeServer) handleRepoIssues(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
idb, err := s.openIssueDB(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer idb.Close()
stubs, err := idb.Issues.ListIssues()
if err != nil {
http.Error(w, "list issues: "+err.Error(), http.StatusInternalServerError)
return
}
var items []issueStubView
for _, st := range stubs {
items = append(items, issueStubView{ID: st.ID, Title: st.Title, Status: st.Status})
}
s.render(w, "srv_repo_issues.html", srvIssuesData{Repo: repoName, User: user, Issues: items})
}
func (s *forgeServer) handleRepoCreateIssue(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
title := strings.TrimSpace(r.FormValue("title"))
body := r.FormValue("body")
if title == "" {
http.Error(w, "title required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
id, err := idb.Issues.CreateIssue(title, body, user.Username)
if err != nil {
http.Error(w, "create issue: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+id, http.StatusFound)
}
type srvIssueData struct {
Repo
+
string
User
+
+
*User
ID
+
+
string
Title
+
+
string
Status
+
+
string
Body
+
+
string
Labels
+
[]string
Comments
+
[]issueCommentView
+ BodyConflict *issueBodyConflictView
+}
+
+type issueBodyConflictView struct {
+ OurEdit string
+ TheirEdit string
}
type issueCommentView struct {
Author string
Text string
}
func (s *forgeServer) handleRepoIssue(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "id required", http.StatusBadRequest)
return
}
idb, err := s.openIssueDB(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer idb.Close()
iss, err := idb.Issues.GetIssue(id)
if err != nil {
http.NotFound(w, r)
return
}
var comments []issueCommentView
for _, c := range iss.Comments {
comments = append(comments, issueCommentView{Author: c.Author, Text: c.Text})
}
+ var bodyConflict *issueBodyConflictView
+ if iss.BodyConflict != nil {
+ bodyConflict = &issueBodyConflictView{
+ OurEdit: iss.BodyConflict.OurEdit,
+ TheirEdit: iss.BodyConflict.TheirEdit,
+ }
+ }
+
s.render(w, "srv_repo_issue.html", srvIssueData{
Repo:
+
repoName,
User:
+
+
user,
ID:
+
iss.ID,
Title:
+
+
iss.Title,
Status:
+
iss.Status,
Body:
+
+
iss.Body,
Labels:
+
iss.Labels,
Comments:
+
comments,
+ BodyConflict: bodyConflict,
})
}
func (s *forgeServer) handleRepoAddComment(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
text := strings.TrimSpace(r.FormValue("text"))
if issueID == "" || text == "" {
http.Error(w, "issue_id and text required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.AddComment(issueID, text, user.Username); err != nil {
http.Error(w, "add comment
+: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
+}
+
+func (s *forgeServer) handleRepoResolveBody(w http.ResponseWriter, r *http.Request) {
+ repoName := r.PathValue("repo")
+ rec, err := s.db.GetRepo(repoName)
+ if err != nil || rec == nil {
+ http.NotFound(w, r)
+ return
+ }
+ user := s.db.currentUser(r)
+ if user == nil {
+ http.Error(w, "login required", http.StatusUnauthorized)
+ return
+ }
+ if !s.db.CanRead(rec, user) {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ r.ParseForm() //nolint:errcheck
+ issueID := r.FormValue("issue_id")
+ choice := r.FormValue("choice")
+ if issueID == "" || (choice != "ours" && choice != "theirs") {
+ http.Error(w, "issue_id and valid choice required", http.StatusBadRequest)
+ return
+ }
+
+ idb, storeCloser, err := s.openIssueDBWithStore(repoName)
+ if err != nil {
+ http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer storeCloser.Close() //nolint:errcheck
+ defer idb.Close()
+
+ iss, err := idb.Issues.GetIssue(issueID)
+ if err != nil || iss.BodyConflict == nil {
+ http.Error(w, "no body conflict on this issue", http.StatusBadRequest)
+ return
+ }
+
+ resolved := iss.BodyConflict.OurEdit
+ if choice == "theirs" {
+ resolved = iss.BodyConflict.TheirEdit
+ }
+ if err := idb.Issues.SetBody(issueID, resolved, user.Username); err != nil {
+ http.Error(w, "resolve
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}
func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
repoName := r.PathValue("repo")
rec, err := s.db.GetRepo(repoName)
if err != nil || rec == nil {
http.NotFound(w, r)
return
}
user := s.db.currentUser(r)
if user == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
if !s.db.CanRead(rec, user) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm() //nolint:errcheck
issueID := r.FormValue("issue_id")
status := r.FormValue("status")
if issueID == "" || status == "" {
http.Error(w, "issue_id and status required", http.StatusBadRequest)
return
}
idb, storeCloser, err := s.openIssueDBWithStore(repoName)
if err != nil {
http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
return
}
defer storeCloser.Close() //nolint:errcheck
defer idb.Close()
if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
}