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