arche / internal/archesrv/handlers_issues.go

commit 154431fd
  1package archesrv
  2
  3import (
  4	"io"
  5	"net/http"
  6	"path/filepath"
  7	"strings"
  8
  9	"arche/internal/issuedb"
 10)
 11
 12func (s *forgeServer) openIssueDB(repoName string) (*issuedb.DB, error) {
 13	dir := filepath.Join(repoPath(s.dataDir(), repoName), ".arche")
 14	return issuedb.Open(dir)
 15}
 16
 17func (s *forgeServer) openIssueDBWithStore(repoName string) (*issuedb.DB, io.Closer, error) {
 18	r, err := openRepo(s.dataDir(), repoName)
 19	if err != nil {
 20		return nil, nil, err
 21	}
 22	dir := filepath.Join(repoPath(s.dataDir(), repoName), ".arche")
 23	idb, err := issuedb.NewWithStore(dir, r.Store)
 24	if err != nil {
 25		r.Store.Close() //nolint:errcheck
 26		return nil, nil, err
 27	}
 28	return idb, r.Store, nil
 29}
 30
 31type srvIssuesData struct {
 32	Repo   string
 33	User   *User
 34	Issues []issueStubView
 35}
 36
 37type issueStubView struct {
 38	ID     string
 39	Title  string
 40	Status string
 41}
 42
 43func (s *forgeServer) handleRepoIssues(w http.ResponseWriter, r *http.Request) {
 44	repoName := r.PathValue("repo")
 45	rec, err := s.db.GetRepo(repoName)
 46	if err != nil || rec == nil {
 47		http.NotFound(w, r)
 48		return
 49	}
 50	user := s.db.currentUser(r)
 51	if !s.db.CanRead(rec, user) {
 52		http.Error(w, "Unauthorized", http.StatusUnauthorized)
 53		return
 54	}
 55
 56	idb, err := s.openIssueDB(repoName)
 57	if err != nil {
 58		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
 59		return
 60	}
 61	defer idb.Close()
 62
 63	stubs, err := idb.Issues.ListIssues()
 64	if err != nil {
 65		http.Error(w, "list issues: "+err.Error(), http.StatusInternalServerError)
 66		return
 67	}
 68	var items []issueStubView
 69	for _, st := range stubs {
 70		items = append(items, issueStubView{ID: st.ID, Title: st.Title, Status: st.Status})
 71	}
 72	s.render(w, "srv_repo_issues.html", srvIssuesData{Repo: repoName, User: user, Issues: items})
 73}
 74
 75func (s *forgeServer) handleRepoCreateIssue(w http.ResponseWriter, r *http.Request) {
 76	repoName := r.PathValue("repo")
 77	rec, err := s.db.GetRepo(repoName)
 78	if err != nil || rec == nil {
 79		http.NotFound(w, r)
 80		return
 81	}
 82	user := s.db.currentUser(r)
 83	if user == nil {
 84		http.Error(w, "login required", http.StatusUnauthorized)
 85		return
 86	}
 87	if !s.db.CanRead(rec, user) {
 88		http.Error(w, "Unauthorized", http.StatusUnauthorized)
 89		return
 90	}
 91
 92	r.ParseForm() //nolint:errcheck
 93	title := strings.TrimSpace(r.FormValue("title"))
 94	body := r.FormValue("body")
 95	if title == "" {
 96		http.Error(w, "title required", http.StatusBadRequest)
 97		return
 98	}
 99
100	idb, storeCloser, err := s.openIssueDBWithStore(repoName)
101	if err != nil {
102		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
103		return
104	}
105	defer storeCloser.Close() //nolint:errcheck
106	defer idb.Close()
107
108	id, err := idb.Issues.CreateIssue(title, body, user.Username)
109	if err != nil {
110		http.Error(w, "create issue: "+err.Error(), http.StatusInternalServerError)
111		return
112	}
113	http.Redirect(w, r, "/"+repoName+"/issue?id="+id, http.StatusFound)
114}
115
116type srvIssueData struct {
117	Repo     string
118	User     *User
119	ID       string
120	Title    string
121	Status   string
122	Body     string
123	Labels   []string
124	Comments []issueCommentView
125}
126
127type issueCommentView struct {
128	Author string
129	Text   string
130}
131
132func (s *forgeServer) handleRepoIssue(w http.ResponseWriter, r *http.Request) {
133	repoName := r.PathValue("repo")
134	rec, err := s.db.GetRepo(repoName)
135	if err != nil || rec == nil {
136		http.NotFound(w, r)
137		return
138	}
139	user := s.db.currentUser(r)
140	if !s.db.CanRead(rec, user) {
141		http.Error(w, "Unauthorized", http.StatusUnauthorized)
142		return
143	}
144
145	id := r.URL.Query().Get("id")
146	if id == "" {
147		http.Error(w, "id required", http.StatusBadRequest)
148		return
149	}
150
151	idb, err := s.openIssueDB(repoName)
152	if err != nil {
153		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
154		return
155	}
156	defer idb.Close()
157
158	iss, err := idb.Issues.GetIssue(id)
159	if err != nil {
160		http.NotFound(w, r)
161		return
162	}
163
164	var comments []issueCommentView
165	for _, c := range iss.Comments {
166		comments = append(comments, issueCommentView{Author: c.Author, Text: c.Text})
167	}
168
169	s.render(w, "srv_repo_issue.html", srvIssueData{
170		Repo:     repoName,
171		User:     user,
172		ID:       iss.ID,
173		Title:    iss.Title,
174		Status:   iss.Status,
175		Body:     iss.Body,
176		Labels:   iss.Labels,
177		Comments: comments,
178	})
179}
180
181func (s *forgeServer) handleRepoAddComment(w http.ResponseWriter, r *http.Request) {
182	repoName := r.PathValue("repo")
183	rec, err := s.db.GetRepo(repoName)
184	if err != nil || rec == nil {
185		http.NotFound(w, r)
186		return
187	}
188	user := s.db.currentUser(r)
189	if user == nil {
190		http.Error(w, "login required", http.StatusUnauthorized)
191		return
192	}
193	if !s.db.CanRead(rec, user) {
194		http.Error(w, "Unauthorized", http.StatusUnauthorized)
195		return
196	}
197
198	r.ParseForm() //nolint:errcheck
199	issueID := r.FormValue("issue_id")
200	text := strings.TrimSpace(r.FormValue("text"))
201	if issueID == "" || text == "" {
202		http.Error(w, "issue_id and text required", http.StatusBadRequest)
203		return
204	}
205
206	idb, storeCloser, err := s.openIssueDBWithStore(repoName)
207	if err != nil {
208		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
209		return
210	}
211	defer storeCloser.Close() //nolint:errcheck
212	defer idb.Close()
213
214	if err := idb.Issues.AddComment(issueID, text, user.Username); err != nil {
215		http.Error(w, "add comment: "+err.Error(), http.StatusInternalServerError)
216		return
217	}
218	http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
219}
220
221func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
222	repoName := r.PathValue("repo")
223	rec, err := s.db.GetRepo(repoName)
224	if err != nil || rec == nil {
225		http.NotFound(w, r)
226		return
227	}
228	user := s.db.currentUser(r)
229	if user == nil {
230		http.Error(w, "login required", http.StatusUnauthorized)
231		return
232	}
233	if !s.db.CanRead(rec, user) {
234		http.Error(w, "Unauthorized", http.StatusUnauthorized)
235		return
236	}
237
238	r.ParseForm() //nolint:errcheck
239	issueID := r.FormValue("issue_id")
240	status := r.FormValue("status")
241	if issueID == "" || status == "" {
242		http.Error(w, "issue_id and status required", http.StatusBadRequest)
243		return
244	}
245
246	idb, storeCloser, err := s.openIssueDBWithStore(repoName)
247	if err != nil {
248		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
249		return
250	}
251	defer storeCloser.Close() //nolint:errcheck
252	defer idb.Close()
253
254	if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
255		http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
256		return
257	}
258	http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
259}