this repo has no description
1package knotserver
2
3import (
4 "compress/gzip"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "html/template"
9 "log"
10 "net/http"
11 "path/filepath"
12 "strconv"
13 "strings"
14
15 "github.com/go-chi/chi/v5"
16 "github.com/go-git/go-git/v5/plumbing"
17 "github.com/go-git/go-git/v5/plumbing/object"
18 "github.com/icyphox/bild/knotserver/git"
19 "github.com/russross/blackfriday/v2"
20)
21
22func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
23 w.Write([]byte("This is a knot, part of the wider Tangle network: https://knots.sh"))
24}
25
26func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
27 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
28
29 gr, err := git.Open(path, "")
30 if err != nil {
31 if errors.Is(err, plumbing.ErrReferenceNotFound) {
32 writeMsg(w, "repo empty")
33 return
34 } else {
35 log.Println(err)
36 notFound(w)
37 return
38 }
39 }
40 commits, err := gr.Commits()
41 if err != nil {
42 writeError(w, err.Error(), http.StatusInternalServerError)
43 log.Println(err)
44 return
45 }
46
47 var readmeContent template.HTML
48 for _, readme := range h.c.Repo.Readme {
49 ext := filepath.Ext(readme)
50 content, _ := gr.FileContent(readme)
51 if len(content) > 0 {
52 switch ext {
53 case ".md", ".mkd", ".markdown":
54 unsafe := blackfriday.Run(
55 []byte(content),
56 blackfriday.WithExtensions(blackfriday.CommonExtensions),
57 )
58 html := sanitize(unsafe)
59 readmeContent = template.HTML(html)
60 default:
61 safe := sanitize([]byte(content))
62 readmeContent = template.HTML(
63 fmt.Sprintf(`<pre>%s</pre>`, safe),
64 )
65 }
66 break
67 }
68 }
69
70 if readmeContent == "" {
71 log.Printf("no readme found for %s", path)
72 }
73
74 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
75 if err != nil {
76 writeError(w, err.Error(), http.StatusInternalServerError)
77 log.Println(err)
78 return
79 }
80
81 if len(commits) >= 3 {
82 commits = commits[:3]
83 }
84 data := make(map[string]any)
85 data["ref"] = mainBranch
86 data["readme"] = readmeContent
87 data["commits"] = commits
88 data["desc"] = getDescription(path)
89
90 writeJSON(w, data)
91 return
92}
93
94func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
95 treePath := chi.URLParam(r, "*")
96 ref := chi.URLParam(r, "ref")
97
98 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
99 gr, err := git.Open(path, ref)
100 if err != nil {
101 notFound(w)
102 return
103 }
104
105 files, err := gr.FileTree(treePath)
106 if err != nil {
107 writeError(w, err.Error(), http.StatusInternalServerError)
108 log.Println(err)
109 return
110 }
111
112 data := make(map[string]any)
113 data["ref"] = ref
114 data["parent"] = treePath
115 data["desc"] = getDescription(path)
116 data["dotdot"] = filepath.Dir(treePath)
117
118 h.listFiles(files, data, w)
119 return
120}
121
122func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) {
123 var raw bool
124 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
125 raw = rawParam
126 }
127
128 treePath := chi.URLParam(r, "*")
129 ref := chi.URLParam(r, "ref")
130
131 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
132 gr, err := git.Open(path, ref)
133 if err != nil {
134 notFound(w)
135 return
136 }
137
138 contents, err := gr.FileContent(treePath)
139 if err != nil {
140 writeError(w, err.Error(), http.StatusInternalServerError)
141 return
142 }
143 data := make(map[string]any)
144 data["ref"] = ref
145 data["desc"] = getDescription(path)
146 data["path"] = treePath
147
148 safe := sanitize([]byte(contents))
149
150 if raw {
151 h.showRaw(string(safe), w)
152 } else {
153 h.showFile(string(safe), data, w)
154 }
155}
156
157func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
158 name := chi.URLParam(r, "name")
159 file := chi.URLParam(r, "file")
160
161 // TODO: extend this to add more files compression (e.g.: xz)
162 if !strings.HasSuffix(file, ".tar.gz") {
163 notFound(w)
164 return
165 }
166
167 ref := strings.TrimSuffix(file, ".tar.gz")
168
169 // This allows the browser to use a proper name for the file when
170 // downloading
171 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
172 setContentDisposition(w, filename)
173 setGZipMIME(w)
174
175 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
176 gr, err := git.Open(path, ref)
177 if err != nil {
178 notFound(w)
179 return
180 }
181
182 gw := gzip.NewWriter(w)
183 defer gw.Close()
184
185 prefix := fmt.Sprintf("%s-%s", name, ref)
186 err = gr.WriteTar(gw, prefix)
187 if err != nil {
188 // once we start writing to the body we can't report error anymore
189 // so we are only left with printing the error.
190 log.Println(err)
191 return
192 }
193
194 err = gw.Flush()
195 if err != nil {
196 // once we start writing to the body we can't report error anymore
197 // so we are only left with printing the error.
198 log.Println(err)
199 return
200 }
201}
202
203func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
204 fmt.Println(r.URL.Path)
205 ref := chi.URLParam(r, "ref")
206
207 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
208 gr, err := git.Open(path, ref)
209 if err != nil {
210 notFound(w)
211 return
212 }
213
214 commits, err := gr.Commits()
215 if err != nil {
216 writeError(w, err.Error(), http.StatusInternalServerError)
217 log.Println(err)
218 return
219 }
220
221 // Get page parameters
222 page := 1
223 pageSize := 30
224
225 if pageParam := r.URL.Query().Get("page"); pageParam != "" {
226 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
227 page = p
228 }
229 }
230
231 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" {
232 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 {
233 pageSize = ps
234 }
235 }
236
237 // Calculate pagination
238 start := (page - 1) * pageSize
239 end := start + pageSize
240 total := len(commits)
241
242 if start >= total {
243 commits = []*object.Commit{}
244 } else {
245 if end > total {
246 end = total
247 }
248 commits = commits[start:end]
249 }
250
251 data := make(map[string]interface{})
252 data["commits"] = commits
253 data["ref"] = ref
254 data["desc"] = getDescription(path)
255 data["log"] = true
256 data["total"] = total
257 data["page"] = page
258 data["per_page"] = pageSize
259
260 writeJSON(w, data)
261 return
262}
263
264func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
265 ref := chi.URLParam(r, "ref")
266
267 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
268 gr, err := git.Open(path, ref)
269 if err != nil {
270 notFound(w)
271 return
272 }
273
274 diff, err := gr.Diff()
275 if err != nil {
276 writeError(w, err.Error(), http.StatusInternalServerError)
277 log.Println(err)
278 return
279 }
280
281 data := make(map[string]interface{})
282
283 data["commit"] = diff.Commit
284 data["stat"] = diff.Stat
285 data["diff"] = diff.Diff
286 data["ref"] = ref
287 data["desc"] = getDescription(path)
288
289 writeJSON(w, data)
290 return
291}
292
293func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
294 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
295 gr, err := git.Open(path, "")
296 if err != nil {
297 notFound(w)
298 return
299 }
300
301 tags, err := gr.Tags()
302 if err != nil {
303 // Non-fatal, we *should* have at least one branch to show.
304 log.Println(err)
305 }
306
307 branches, err := gr.Branches()
308 if err != nil {
309 log.Println(err)
310 writeError(w, err.Error(), http.StatusInternalServerError)
311 return
312 }
313
314 data := make(map[string]interface{})
315
316 data["branches"] = branches
317 data["tags"] = tags
318 data["desc"] = getDescription(path)
319
320 writeJSON(w, data)
321 return
322}
323
324// func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
325// session, _ := h.s.Get(r, "bild-session")
326// did := session.Values["did"].(string)
327
328// switch r.Method {
329// case http.MethodGet:
330// keys, err := h.db.GetPublicKeys(did)
331// if err != nil {
332// h.WriteOOBNotice(w, "keys", "Failed to list keys. Try again later.")
333// log.Println(err)
334// return
335// }
336
337// data := make(map[string]interface{})
338// data["keys"] = keys
339// if err := h.t.ExecuteTemplate(w, "settings/keys", data); err != nil {
340// log.Println(err)
341// return
342// }
343// case http.MethodPut:
344// key := r.FormValue("key")
345// name := r.FormValue("name")
346// client, _ := h.auth.AuthorizedClient(r)
347
348// _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
349// if err != nil {
350// h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.")
351// log.Printf("parsing public key: %s", err)
352// return
353// }
354
355// if err := h.db.AddPublicKey(did, name, key); err != nil {
356// h.WriteOOBNotice(w, "keys", "Failed to add key.")
357// log.Printf("adding public key: %s", err)
358// return
359// }
360
361// h.WriteOOBNotice(w, "keys", "Key added!")
362// return
363// }
364// }
365
366func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
367 data := struct {
368 DID string `json:"did"`
369 Name string `json:"name"`
370 }{}
371
372 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
373 writeError(w, "invalid request body", http.StatusBadRequest)
374 return
375 }
376
377 did := data.DID
378 name := data.Name
379
380 repoPath := filepath.Join(h.c.Repo.ScanPath, did, name)
381 err := git.InitBare(repoPath)
382 if err != nil {
383 writeError(w, err.Error(), http.StatusInternalServerError)
384 return
385 }
386
387 w.WriteHeader(http.StatusNoContent)
388}
389
390// func (h *Handle) Timeline(w http.ResponseWriter, r *http.Request) {
391// session, err := h.s.Get(r, "bild-session")
392// user := make(map[string]string)
393// if err != nil || session.IsNew {
394// // user is not logged in
395// } else {
396// user["handle"] = session.Values["handle"].(string)
397// user["did"] = session.Values["did"].(string)
398// }
399
400// if err := h.t.ExecuteTemplate(w, "timeline", user); err != nil {
401// log.Println(err)
402// return
403// }
404// }
405
406func (h *Handle) Health(w http.ResponseWriter, r *http.Request) {
407 w.Write([]byte("ok"))
408}