this repo has no description
1package routes
2
3import (
4 "compress/gzip"
5 "errors"
6 "fmt"
7 "html/template"
8 "log"
9 "net/http"
10 "os"
11 "path/filepath"
12 "sort"
13 "strconv"
14 "strings"
15 "time"
16
17 "github.com/dustin/go-humanize"
18 "github.com/go-chi/chi/v5"
19 "github.com/go-git/go-git/v5/plumbing"
20 "github.com/icyphox/bild/legit/config"
21 "github.com/icyphox/bild/legit/db"
22 "github.com/icyphox/bild/legit/git"
23 "github.com/russross/blackfriday/v2"
24 "golang.org/x/crypto/ssh"
25)
26
27type Handle struct {
28 c *config.Config
29 t *template.Template
30 db *db.DB
31}
32
33func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
34 user := chi.URLParam(r, "user")
35 path := filepath.Join(h.c.Repo.ScanPath, user)
36 dirs, err := os.ReadDir(path)
37 if err != nil {
38 h.Write500(w)
39 log.Printf("reading scan path: %s", err)
40 return
41 }
42
43 type info struct {
44 DisplayName, Name, Desc, Idle string
45 d time.Time
46 }
47
48 infos := []info{}
49
50 for _, dir := range dirs {
51 name := dir.Name()
52 if !dir.IsDir() || h.isIgnored(name) || h.isUnlisted(name) {
53 continue
54 }
55
56 gr, err := git.Open(path, "")
57 if err != nil {
58 log.Println(err)
59 continue
60 }
61
62 c, err := gr.LastCommit()
63 if err != nil {
64 h.Write500(w)
65 log.Println(err)
66 return
67 }
68
69 infos = append(infos, info{
70 DisplayName: getDisplayName(name),
71 Name: name,
72 Desc: getDescription(path),
73 Idle: humanize.Time(c.Author.When),
74 d: c.Author.When,
75 })
76 }
77
78 sort.Slice(infos, func(i, j int) bool {
79 return infos[j].d.Before(infos[i].d)
80 })
81
82 data := make(map[string]interface{})
83 data["meta"] = h.c.Meta
84 data["info"] = infos
85
86 if err := h.t.ExecuteTemplate(w, "index", data); err != nil {
87 log.Println(err)
88 return
89 }
90}
91
92func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
93 name := uniqueName(r)
94 if h.isIgnored(name) {
95 h.Write404(w)
96 return
97 }
98
99 name = filepath.Clean(name)
100 path := filepath.Join(h.c.Repo.ScanPath, name)
101
102 fmt.Println(path)
103 gr, err := git.Open(path, "")
104 if err != nil {
105 if errors.Is(err, plumbing.ErrReferenceNotFound) {
106 h.t.ExecuteTemplate(w, "empty", nil)
107 return
108 } else {
109 h.Write404(w)
110 return
111 }
112 }
113 commits, err := gr.Commits()
114 if err != nil {
115 h.Write500(w)
116 log.Println(err)
117 return
118 }
119
120 var readmeContent template.HTML
121 for _, readme := range h.c.Repo.Readme {
122 ext := filepath.Ext(readme)
123 content, _ := gr.FileContent(readme)
124 if len(content) > 0 {
125 switch ext {
126 case ".md", ".mkd", ".markdown":
127 unsafe := blackfriday.Run(
128 []byte(content),
129 blackfriday.WithExtensions(blackfriday.CommonExtensions),
130 )
131 html := sanitize(unsafe)
132 readmeContent = template.HTML(html)
133 default:
134 safe := sanitize([]byte(content))
135 readmeContent = template.HTML(
136 fmt.Sprintf(`<pre>%s</pre>`, safe),
137 )
138 }
139 break
140 }
141 }
142
143 if readmeContent == "" {
144 log.Printf("no readme found for %s", name)
145 }
146
147 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
148 if err != nil {
149 h.Write500(w)
150 log.Println(err)
151 return
152 }
153
154 if len(commits) >= 3 {
155 commits = commits[:3]
156 }
157
158 data := make(map[string]any)
159 data["name"] = name
160 data["displayname"] = getDisplayName(name)
161 data["ref"] = mainBranch
162 data["readme"] = readmeContent
163 data["commits"] = commits
164 data["desc"] = getDescription(path)
165 data["servername"] = h.c.Server.Name
166 data["meta"] = h.c.Meta
167 data["gomod"] = isGoModule(gr)
168
169 if err := h.t.ExecuteTemplate(w, "repo", data); err != nil {
170 log.Println(err)
171 return
172 }
173
174 return
175}
176
177func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
178 name := uniqueName(r)
179 if h.isIgnored(name) {
180 h.Write404(w)
181 return
182 }
183 treePath := chi.URLParam(r, "*")
184 ref := chi.URLParam(r, "ref")
185
186 name = filepath.Clean(name)
187 path := filepath.Join(h.c.Repo.ScanPath, name)
188 fmt.Println(path)
189 gr, err := git.Open(path, ref)
190 if err != nil {
191 h.Write404(w)
192 return
193 }
194
195 files, err := gr.FileTree(treePath)
196 if err != nil {
197 h.Write500(w)
198 log.Println(err)
199 return
200 }
201
202 data := make(map[string]any)
203 data["name"] = name
204 data["displayname"] = getDisplayName(name)
205 data["ref"] = ref
206 data["parent"] = treePath
207 data["desc"] = getDescription(path)
208 data["dotdot"] = filepath.Dir(treePath)
209
210 h.listFiles(files, data, w)
211 return
212}
213
214func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) {
215 var raw bool
216 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
217 raw = rawParam
218 }
219
220 name := uniqueName(r)
221
222 if h.isIgnored(name) {
223 h.Write404(w)
224 return
225 }
226 treePath := chi.URLParam(r, "*")
227 ref := chi.URLParam(r, "ref")
228
229 name = filepath.Clean(name)
230 path := filepath.Join(h.c.Repo.ScanPath, name)
231 gr, err := git.Open(path, ref)
232 if err != nil {
233 h.Write404(w)
234 return
235 }
236
237 contents, err := gr.FileContent(treePath)
238 if err != nil {
239 h.Write500(w)
240 return
241 }
242 data := make(map[string]any)
243 data["name"] = name
244 data["displayname"] = getDisplayName(name)
245 data["ref"] = ref
246 data["desc"] = getDescription(path)
247 data["path"] = treePath
248
249 safe := sanitize([]byte(contents))
250
251 if raw {
252 h.showRaw(string(safe), w)
253 } else {
254 if h.c.Meta.SyntaxHighlight == "" {
255 h.showFile(string(safe), data, w)
256 } else {
257 h.showFileWithHighlight(treePath, string(safe), data, w)
258 }
259 }
260}
261
262func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
263 name := uniqueName(r)
264 if h.isIgnored(name) {
265 h.Write404(w)
266 return
267 }
268
269 file := chi.URLParam(r, "file")
270
271 // TODO: extend this to add more files compression (e.g.: xz)
272 if !strings.HasSuffix(file, ".tar.gz") {
273 h.Write404(w)
274 return
275 }
276
277 ref := strings.TrimSuffix(file, ".tar.gz")
278
279 // This allows the browser to use a proper name for the file when
280 // downloading
281 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
282 setContentDisposition(w, filename)
283 setGZipMIME(w)
284
285 path := filepath.Join(h.c.Repo.ScanPath, name)
286 gr, err := git.Open(path, ref)
287 if err != nil {
288 h.Write404(w)
289 return
290 }
291
292 gw := gzip.NewWriter(w)
293 defer gw.Close()
294
295 prefix := fmt.Sprintf("%s-%s", name, ref)
296 err = gr.WriteTar(gw, prefix)
297 if err != nil {
298 // once we start writing to the body we can't report error anymore
299 // so we are only left with printing the error.
300 log.Println(err)
301 return
302 }
303
304 err = gw.Flush()
305 if err != nil {
306 // once we start writing to the body we can't report error anymore
307 // so we are only left with printing the error.
308 log.Println(err)
309 return
310 }
311}
312
313func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
314 name := uniqueName(r)
315 if h.isIgnored(name) {
316 h.Write404(w)
317 return
318 }
319 ref := chi.URLParam(r, "ref")
320
321 path := filepath.Join(h.c.Repo.ScanPath, name)
322 gr, err := git.Open(path, ref)
323 if err != nil {
324 h.Write404(w)
325 return
326 }
327
328 commits, err := gr.Commits()
329 if err != nil {
330 h.Write500(w)
331 log.Println(err)
332 return
333 }
334
335 data := make(map[string]interface{})
336 data["commits"] = commits
337 data["meta"] = h.c.Meta
338 data["name"] = name
339 data["displayname"] = getDisplayName(name)
340 data["ref"] = ref
341 data["desc"] = getDescription(path)
342 data["log"] = true
343
344 if err := h.t.ExecuteTemplate(w, "log", data); err != nil {
345 log.Println(err)
346 return
347 }
348}
349
350func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
351 name := uniqueName(r)
352 if h.isIgnored(name) {
353 h.Write404(w)
354 return
355 }
356 ref := chi.URLParam(r, "ref")
357
358 path := filepath.Join(h.c.Repo.ScanPath, name)
359 gr, err := git.Open(path, ref)
360 if err != nil {
361 h.Write404(w)
362 return
363 }
364
365 diff, err := gr.Diff()
366 if err != nil {
367 h.Write500(w)
368 log.Println(err)
369 return
370 }
371
372 data := make(map[string]interface{})
373
374 data["commit"] = diff.Commit
375 data["stat"] = diff.Stat
376 data["diff"] = diff.Diff
377 data["meta"] = h.c.Meta
378 data["name"] = name
379 data["displayname"] = getDisplayName(name)
380 data["ref"] = ref
381 data["desc"] = getDescription(path)
382
383 if err := h.t.ExecuteTemplate(w, "commit", data); err != nil {
384 log.Println(err)
385 return
386 }
387}
388
389func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
390 name := chi.URLParam(r, "name")
391 if h.isIgnored(name) {
392 h.Write404(w)
393 return
394 }
395
396 path := filepath.Join(h.c.Repo.ScanPath, name)
397 gr, err := git.Open(path, "")
398 if err != nil {
399 h.Write404(w)
400 return
401 }
402
403 tags, err := gr.Tags()
404 if err != nil {
405 // Non-fatal, we *should* have at least one branch to show.
406 log.Println(err)
407 }
408
409 branches, err := gr.Branches()
410 if err != nil {
411 log.Println(err)
412 h.Write500(w)
413 return
414 }
415
416 data := make(map[string]interface{})
417
418 data["meta"] = h.c.Meta
419 data["name"] = name
420 data["displayname"] = getDisplayName(name)
421 data["branches"] = branches
422 data["tags"] = tags
423 data["desc"] = getDescription(path)
424
425 if err := h.t.ExecuteTemplate(w, "refs", data); err != nil {
426 log.Println(err)
427 return
428 }
429}
430
431func (h *Handle) ServeStatic(w http.ResponseWriter, r *http.Request) {
432 f := chi.URLParam(r, "file")
433 f = filepath.Clean(filepath.Join(h.c.Dirs.Static, f))
434
435 http.ServeFile(w, r, f)
436}
437
438func (h *Handle) Login(w http.ResponseWriter, r *http.Request) {
439 if err := h.t.ExecuteTemplate(w, "login", nil); err != nil {
440 log.Println(err)
441 return
442 }
443}
444
445func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
446 switch r.Method {
447 case http.MethodGet:
448 keys, err := h.db.GetPublicKeys("did:ashtntnashtx")
449 if err != nil {
450 log.Println(err)
451 http.Error(w, "invalid `did`", http.StatusBadRequest)
452 return
453 }
454
455 data := make(map[string]interface{})
456 data["keys"] = keys
457 if err := h.t.ExecuteTemplate(w, "keys", data); err != nil {
458 log.Println(err)
459 return
460 }
461 case http.MethodPut:
462 key := r.FormValue("key")
463 name := r.FormValue("name")
464
465 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
466 if err != nil {
467 h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.")
468 log.Printf("parsing public key: %s", err)
469 return
470 }
471
472 // TODO: add did here
473 if err := h.db.AddPublicKey("did:ashtntnashtx", name, key); err != nil {
474 h.WriteOOBNotice(w, "keys", "Failed to add key.")
475 log.Printf("adding public key: %s", err)
476 return
477 }
478
479 h.WriteOOBNotice(w, "keys", "Key added!")
480 return
481 }
482}
483
484func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
485 switch r.Method {
486 case http.MethodGet:
487 if err := h.t.ExecuteTemplate(w, "new", nil); err != nil {
488 log.Println(err)
489 return
490 }
491 case http.MethodPut:
492 name := r.FormValue("name")
493 description := r.FormValue("description")
494
495 // TODO: Get handle from session. We need this to construct the repository path.
496 err := git.InitBare(filepath.Join(h.c.Repo.ScanPath, "example.com", name))
497 if err != nil {
498 h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.")
499 return
500 }
501
502 err = h.db.AddRepo("did:ashtntnashtx", name, description)
503 if err != nil {
504 h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.")
505 return
506 }
507
508 w.Header().Set("HX-Redirect", fmt.Sprintf("/@example.com/%s", name))
509 w.WriteHeader(http.StatusOK)
510 }
511}