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}