arche / internal/archesrv/handlers_issues.go

commit a22ffc45
  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	BodyConflict *issueBodyConflictView
126}
127
128type issueBodyConflictView struct {
129	OurEdit   string
130	TheirEdit string
131}
132
133type issueCommentView struct {
134	Author string
135	Text   string
136}
137
138func (s *forgeServer) handleRepoIssue(w http.ResponseWriter, r *http.Request) {
139	repoName := r.PathValue("repo")
140	rec, err := s.db.GetRepo(repoName)
141	if err != nil || rec == nil {
142		http.NotFound(w, r)
143		return
144	}
145	user := s.db.currentUser(r)
146	if !s.db.CanRead(rec, user) {
147		http.Error(w, "Unauthorized", http.StatusUnauthorized)
148		return
149	}
150
151	id := r.URL.Query().Get("id")
152	if id == "" {
153		http.Error(w, "id required", http.StatusBadRequest)
154		return
155	}
156
157	idb, err := s.openIssueDB(repoName)
158	if err != nil {
159		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
160		return
161	}
162	defer idb.Close()
163
164	iss, err := idb.Issues.GetIssue(id)
165	if err != nil {
166		http.NotFound(w, r)
167		return
168	}
169
170	var comments []issueCommentView
171	for _, c := range iss.Comments {
172		comments = append(comments, issueCommentView{Author: c.Author, Text: c.Text})
173	}
174
175	var bodyConflict *issueBodyConflictView
176	if iss.BodyConflict != nil {
177		bodyConflict = &issueBodyConflictView{
178			OurEdit:   iss.BodyConflict.OurEdit,
179			TheirEdit: iss.BodyConflict.TheirEdit,
180		}
181	}
182
183	s.render(w, "srv_repo_issue.html", srvIssueData{
184		Repo:         repoName,
185		User:         user,
186		ID:           iss.ID,
187		Title:        iss.Title,
188		Status:       iss.Status,
189		Body:         iss.Body,
190		Labels:       iss.Labels,
191		Comments:     comments,
192		BodyConflict: bodyConflict,
193	})
194}
195
196func (s *forgeServer) handleRepoAddComment(w http.ResponseWriter, r *http.Request) {
197	repoName := r.PathValue("repo")
198	rec, err := s.db.GetRepo(repoName)
199	if err != nil || rec == nil {
200		http.NotFound(w, r)
201		return
202	}
203	user := s.db.currentUser(r)
204	if user == nil {
205		http.Error(w, "login required", http.StatusUnauthorized)
206		return
207	}
208	if !s.db.CanRead(rec, user) {
209		http.Error(w, "Unauthorized", http.StatusUnauthorized)
210		return
211	}
212
213	r.ParseForm() //nolint:errcheck
214	issueID := r.FormValue("issue_id")
215	text := strings.TrimSpace(r.FormValue("text"))
216	if issueID == "" || text == "" {
217		http.Error(w, "issue_id and text required", http.StatusBadRequest)
218		return
219	}
220
221	idb, storeCloser, err := s.openIssueDBWithStore(repoName)
222	if err != nil {
223		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
224		return
225	}
226	defer storeCloser.Close() //nolint:errcheck
227	defer idb.Close()
228
229	if err := idb.Issues.AddComment(issueID, text, user.Username); err != nil {
230		http.Error(w, "add comment: "+err.Error(), http.StatusInternalServerError)
231		return
232	}
233	http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
234}
235
236func (s *forgeServer) handleRepoResolveBody(w http.ResponseWriter, r *http.Request) {
237	repoName := r.PathValue("repo")
238	rec, err := s.db.GetRepo(repoName)
239	if err != nil || rec == nil {
240		http.NotFound(w, r)
241		return
242	}
243	user := s.db.currentUser(r)
244	if user == nil {
245		http.Error(w, "login required", http.StatusUnauthorized)
246		return
247	}
248	if !s.db.CanRead(rec, user) {
249		http.Error(w, "Unauthorized", http.StatusUnauthorized)
250		return
251	}
252
253	r.ParseForm() //nolint:errcheck
254	issueID := r.FormValue("issue_id")
255	choice := r.FormValue("choice")
256	if issueID == "" || (choice != "ours" && choice != "theirs") {
257		http.Error(w, "issue_id and valid choice required", http.StatusBadRequest)
258		return
259	}
260
261	idb, storeCloser, err := s.openIssueDBWithStore(repoName)
262	if err != nil {
263		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
264		return
265	}
266	defer storeCloser.Close() //nolint:errcheck
267	defer idb.Close()
268
269	iss, err := idb.Issues.GetIssue(issueID)
270	if err != nil || iss.BodyConflict == nil {
271		http.Error(w, "no body conflict on this issue", http.StatusBadRequest)
272		return
273	}
274
275	resolved := iss.BodyConflict.OurEdit
276	if choice == "theirs" {
277		resolved = iss.BodyConflict.TheirEdit
278	}
279	if err := idb.Issues.SetBody(issueID, resolved, user.Username); err != nil {
280		http.Error(w, "resolve: "+err.Error(), http.StatusInternalServerError)
281		return
282	}
283	http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
284}
285
286func (s *forgeServer) handleRepoSetStatus(w http.ResponseWriter, r *http.Request) {
287	repoName := r.PathValue("repo")
288	rec, err := s.db.GetRepo(repoName)
289	if err != nil || rec == nil {
290		http.NotFound(w, r)
291		return
292	}
293	user := s.db.currentUser(r)
294	if user == nil {
295		http.Error(w, "login required", http.StatusUnauthorized)
296		return
297	}
298	if !s.db.CanRead(rec, user) {
299		http.Error(w, "Unauthorized", http.StatusUnauthorized)
300		return
301	}
302
303	r.ParseForm() //nolint:errcheck
304	issueID := r.FormValue("issue_id")
305	status := r.FormValue("status")
306	if issueID == "" || status == "" {
307		http.Error(w, "issue_id and status required", http.StatusBadRequest)
308		return
309	}
310
311	idb, storeCloser, err := s.openIssueDBWithStore(repoName)
312	if err != nil {
313		http.Error(w, "open issuedb: "+err.Error(), http.StatusInternalServerError)
314		return
315	}
316	defer storeCloser.Close() //nolint:errcheck
317	defer idb.Close()
318
319	if err := idb.Issues.SetStatus(issueID, status, user.Username); err != nil {
320		http.Error(w, "set status: "+err.Error(), http.StatusInternalServerError)
321		return
322	}
323	http.Redirect(w, r, "/"+repoName+"/issue?id="+issueID, http.StatusFound)
324}