routes/routes.go (view raw)
1package routes
2
3import (
4 "compress/gzip"
5 "fmt"
6 "html/template"
7 "log"
8 "net/http"
9 "os"
10 "path/filepath"
11 "sort"
12 "strconv"
13 "strings"
14 "time"
15
16 "git.icyphox.sh/legit/config"
17 "git.icyphox.sh/legit/git"
18 "github.com/dustin/go-humanize"
19 "github.com/microcosm-cc/bluemonday"
20 "github.com/russross/blackfriday/v2"
21)
22
23type deps struct {
24 c *config.Config
25}
26
27func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
28 dirs, err := os.ReadDir(d.c.Repo.ScanPath)
29 if err != nil {
30 d.Write500(w)
31 log.Printf("reading scan path: %s", err)
32 return
33 }
34
35 type info struct {
36 Name, Desc, Idle string
37 d time.Time
38 }
39
40 infos := []info{}
41
42 for _, dir := range dirs {
43 if d.isIgnored(dir.Name()) {
44 continue
45 }
46
47 path := filepath.Join(d.c.Repo.ScanPath, dir.Name())
48 gr, err := git.Open(path, "")
49 if err != nil {
50 log.Println(err)
51 continue
52 }
53
54 c, err := gr.LastCommit()
55 if err != nil {
56 d.Write500(w)
57 log.Println(err)
58 return
59 }
60
61 desc := getDescription(path)
62
63 infos = append(infos, info{
64 Name: dir.Name(),
65 Desc: desc,
66 Idle: humanize.Time(c.Author.When),
67 d: c.Author.When,
68 })
69 }
70
71 sort.Slice(infos, func(i, j int) bool {
72 return infos[j].d.Before(infos[i].d)
73 })
74
75 tpath := filepath.Join(d.c.Dirs.Templates, "*")
76 t := template.Must(template.ParseGlob(tpath))
77
78 data := make(map[string]interface{})
79 data["meta"] = d.c.Meta
80 data["info"] = infos
81
82 if err := t.ExecuteTemplate(w, "index", data); err != nil {
83 log.Println(err)
84 return
85 }
86}
87
88func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
89 name := r.PathValue("name")
90 if d.isIgnored(name) {
91 d.Write404(w)
92 return
93 }
94 name = filepath.Clean(name)
95 path := filepath.Join(d.c.Repo.ScanPath, name)
96
97 gr, err := git.Open(path, "")
98 if err != nil {
99 d.Write404(w)
100 return
101 }
102
103 commits, err := gr.Commits()
104 if err != nil {
105 d.Write500(w)
106 log.Println(err)
107 return
108 }
109
110 var readmeContent template.HTML
111 for _, readme := range d.c.Repo.Readme {
112 ext := filepath.Ext(readme)
113 content, _ := gr.FileContent(readme)
114 if len(content) > 0 {
115 switch ext {
116 case ".md", ".mkd", ".markdown":
117 unsafe := blackfriday.Run(
118 []byte(content),
119 blackfriday.WithExtensions(blackfriday.CommonExtensions),
120 )
121 html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
122 readmeContent = template.HTML(html)
123 default:
124 readmeContent = template.HTML(
125 fmt.Sprintf(`<pre>%s</pre>`, content),
126 )
127 }
128 break
129 }
130 }
131
132 if readmeContent == "" {
133 log.Printf("no readme found for %s", name)
134 }
135
136 mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
137 if err != nil {
138 d.Write500(w)
139 log.Println(err)
140 return
141 }
142
143 tpath := filepath.Join(d.c.Dirs.Templates, "*")
144 t := template.Must(template.ParseGlob(tpath))
145
146 if len(commits) >= 3 {
147 commits = commits[:3]
148 }
149
150 data := make(map[string]any)
151 data["name"] = name
152 data["ref"] = mainBranch
153 data["readme"] = readmeContent
154 data["commits"] = commits
155 data["desc"] = getDescription(path)
156 data["servername"] = d.c.Server.Name
157 data["meta"] = d.c.Meta
158 data["gomod"] = isGoModule(gr)
159
160 if err := t.ExecuteTemplate(w, "repo", data); err != nil {
161 log.Println(err)
162 return
163 }
164
165 return
166}
167
168func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
169 name := r.PathValue("name")
170 if d.isIgnored(name) {
171 d.Write404(w)
172 return
173 }
174 treePath := r.PathValue("rest")
175 ref := r.PathValue("ref")
176
177 name = filepath.Clean(name)
178 path := filepath.Join(d.c.Repo.ScanPath, name)
179 gr, err := git.Open(path, ref)
180 if err != nil {
181 d.Write404(w)
182 return
183 }
184
185 files, err := gr.FileTree(treePath)
186 if err != nil {
187 d.Write500(w)
188 log.Println(err)
189 return
190 }
191
192 data := make(map[string]any)
193 data["name"] = name
194 data["ref"] = ref
195 data["parent"] = treePath
196 data["desc"] = getDescription(path)
197 data["dotdot"] = filepath.Dir(treePath)
198
199 d.listFiles(files, data, w)
200 return
201}
202
203func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
204 var raw bool
205 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
206 raw = rawParam
207 }
208
209 name := r.PathValue("name")
210 if d.isIgnored(name) {
211 d.Write404(w)
212 return
213 }
214 treePath := r.PathValue("rest")
215 ref := r.PathValue("ref")
216
217 name = filepath.Clean(name)
218 path := filepath.Join(d.c.Repo.ScanPath, name)
219 gr, err := git.Open(path, ref)
220 if err != nil {
221 d.Write404(w)
222 return
223 }
224
225 contents, err := gr.FileContent(treePath)
226 data := make(map[string]any)
227 data["name"] = name
228 data["ref"] = ref
229 data["desc"] = getDescription(path)
230 data["path"] = treePath
231
232 if raw {
233 d.showRaw(contents, w)
234 } else {
235 d.showFile(contents, data, w)
236 }
237 return
238}
239
240func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
241 name := r.PathValue("name")
242 if d.isIgnored(name) {
243 d.Write404(w)
244 return
245 }
246
247 file := r.PathValue("file")
248
249 // TODO: extend this to add more files compression (e.g.: xz)
250 if !strings.HasSuffix(file, ".tar.gz") {
251 d.Write404(w)
252 return
253 }
254
255 ref := strings.TrimSuffix(file, ".tar.gz")
256
257 // This allows the browser to use a proper name for the file when
258 // downloading
259 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
260 setContentDisposition(w, filename)
261 setGZipMIME(w)
262
263 path := filepath.Join(d.c.Repo.ScanPath, name)
264 gr, err := git.Open(path, ref)
265 if err != nil {
266 d.Write404(w)
267 return
268 }
269
270 gw := gzip.NewWriter(w)
271 defer gw.Close()
272
273 prefix := fmt.Sprintf("%s-%s", name, ref)
274 err = gr.WriteTar(gw, prefix)
275 if err != nil {
276 // once we start writing to the body we can't report error anymore
277 // so we are only left with printing the error.
278 log.Println(err)
279 return
280 }
281
282 err = gw.Flush()
283 if err != nil {
284 // once we start writing to the body we can't report error anymore
285 // so we are only left with printing the error.
286 log.Println(err)
287 return
288 }
289}
290
291func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
292 name := r.PathValue("name")
293 if d.isIgnored(name) {
294 d.Write404(w)
295 return
296 }
297 ref := r.PathValue("ref")
298
299 path := filepath.Join(d.c.Repo.ScanPath, name)
300 gr, err := git.Open(path, ref)
301 if err != nil {
302 d.Write404(w)
303 return
304 }
305
306 commits, err := gr.Commits()
307 if err != nil {
308 d.Write500(w)
309 log.Println(err)
310 return
311 }
312
313 tpath := filepath.Join(d.c.Dirs.Templates, "*")
314 t := template.Must(template.ParseGlob(tpath))
315
316 data := make(map[string]interface{})
317 data["commits"] = commits
318 data["meta"] = d.c.Meta
319 data["name"] = name
320 data["ref"] = ref
321 data["desc"] = getDescription(path)
322 data["log"] = true
323
324 if err := t.ExecuteTemplate(w, "log", data); err != nil {
325 log.Println(err)
326 return
327 }
328}
329
330func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
331 name := r.PathValue("name")
332 if d.isIgnored(name) {
333 d.Write404(w)
334 return
335 }
336 ref := r.PathValue("ref")
337
338 path := filepath.Join(d.c.Repo.ScanPath, name)
339 gr, err := git.Open(path, ref)
340 if err != nil {
341 d.Write404(w)
342 return
343 }
344
345 diff, err := gr.Diff()
346 if err != nil {
347 d.Write500(w)
348 log.Println(err)
349 return
350 }
351
352 tpath := filepath.Join(d.c.Dirs.Templates, "*")
353 t := template.Must(template.ParseGlob(tpath))
354
355 data := make(map[string]interface{})
356
357 data["commit"] = diff.Commit
358 data["stat"] = diff.Stat
359 data["diff"] = diff.Diff
360 data["meta"] = d.c.Meta
361 data["name"] = name
362 data["ref"] = ref
363 data["desc"] = getDescription(path)
364
365 if err := t.ExecuteTemplate(w, "commit", data); err != nil {
366 log.Println(err)
367 return
368 }
369}
370
371func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
372 name := r.PathValue("name")
373 if d.isIgnored(name) {
374 d.Write404(w)
375 return
376 }
377
378 path := filepath.Join(d.c.Repo.ScanPath, name)
379 gr, err := git.Open(path, "")
380 if err != nil {
381 d.Write404(w)
382 return
383 }
384
385 tags, err := gr.Tags()
386 if err != nil {
387 // Non-fatal, we *should* have at least one branch to show.
388 log.Println(err)
389 }
390
391 branches, err := gr.Branches()
392 if err != nil {
393 log.Println(err)
394 d.Write500(w)
395 return
396 }
397
398 tpath := filepath.Join(d.c.Dirs.Templates, "*")
399 t := template.Must(template.ParseGlob(tpath))
400
401 data := make(map[string]interface{})
402
403 data["meta"] = d.c.Meta
404 data["name"] = name
405 data["branches"] = branches
406 data["tags"] = tags
407 data["desc"] = getDescription(path)
408
409 if err := t.ExecuteTemplate(w, "refs", data); err != nil {
410 log.Println(err)
411 return
412 }
413}
414
415func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
416 f := r.PathValue("file")
417 f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
418
419 http.ServeFile(w, r, f)
420}