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