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/dustin/go-humanize"
17 "github.com/microcosm-cc/bluemonday"
18 "github.com/russross/blackfriday/v2"
19)
20
21type deps struct {
22 c *config.Config
23}
24
25func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
26 dirs, err := os.ReadDir(d.c.Repo.ScanPath)
27 if err != nil {
28 d.Write500(w)
29 log.Printf("reading scan path: %s", err)
30 return
31 }
32
33 type info struct {
34 Name, Desc, Idle string
35 d time.Time
36 }
37
38 infos := []info{}
39
40 for _, dir := range dirs {
41 if d.isIgnored(dir.Name()) {
42 continue
43 }
44
45 path := filepath.Join(d.c.Repo.ScanPath, dir.Name())
46 gr, err := git.Open(path, "")
47 if err != nil {
48 log.Println(err)
49 continue
50 }
51
52 c, err := gr.LastCommit()
53 if err != nil {
54 d.Write500(w)
55 log.Println(err)
56 return
57 }
58
59 name := dir.Name()
60 if name[len(name)-4:] == ".git" {
61 name = name[:len(name)-4]
62 }
63
64 desc := getDescription(path)
65
66 infos = append(infos, info{
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["ref"] = mainBranch
156 data["readme"] = readmeContent
157 data["commits"] = commits
158 data["desc"] = getDescription(path)
159 data["servername"] = d.c.Server.Name
160 data["meta"] = d.c.Meta
161 data["gomod"] = isGoModule(gr)
162
163 if err := t.ExecuteTemplate(w, "repo", data); err != nil {
164 log.Println(err)
165 return
166 }
167
168 return
169}
170
171func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
172 name := r.PathValue("name")
173 if d.isIgnored(name) {
174 d.Write404(w)
175 return
176 }
177 treePath := r.PathValue("rest")
178 ref := r.PathValue("ref")
179
180 name = filepath.Clean(name)
181 path := filepath.Join(d.c.Repo.ScanPath, name)
182 gr, err := git.Open(path, ref)
183 if err != nil {
184 d.Write404(w)
185 return
186 }
187
188 files, err := gr.FileTree(treePath)
189 if err != nil {
190 d.Write500(w)
191 log.Println(err)
192 return
193 }
194
195 data := make(map[string]any)
196 data["name"] = name
197 data["ref"] = ref
198 data["parent"] = treePath
199 data["desc"] = getDescription(path)
200 data["dotdot"] = filepath.Dir(treePath)
201
202 d.listFiles(files, data, w)
203 return
204}
205
206func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
207 var raw bool
208 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
209 raw = rawParam
210 }
211
212 name := r.PathValue("name")
213 if d.isIgnored(name) {
214 d.Write404(w)
215 return
216 }
217 treePath := r.PathValue("rest")
218 ref := r.PathValue("ref")
219
220 name = filepath.Clean(name)
221 path := filepath.Join(d.c.Repo.ScanPath, name)
222 gr, err := git.Open(path, ref)
223 if err != nil {
224 d.Write404(w)
225 return
226 }
227
228 contents, err := gr.FileContent(treePath)
229 data := make(map[string]any)
230 data["name"] = name
231 data["ref"] = ref
232 data["desc"] = getDescription(path)
233 data["path"] = treePath
234
235 if raw {
236 d.showRaw(contents, w)
237 } else {
238 d.showFile(contents, data, w)
239 }
240 return
241}
242
243func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
244 name := r.PathValue("name")
245 if d.isIgnored(name) {
246 d.Write404(w)
247 return
248 }
249 ref := r.PathValue("ref")
250
251 path := filepath.Join(d.c.Repo.ScanPath, name)
252 gr, err := git.Open(path, ref)
253 if err != nil {
254 d.Write404(w)
255 return
256 }
257
258 commits, err := gr.Commits()
259 if err != nil {
260 d.Write500(w)
261 log.Println(err)
262 return
263 }
264
265 tpath := filepath.Join(d.c.Dirs.Templates, "*")
266 t := template.Must(template.ParseGlob(tpath))
267
268 data := make(map[string]interface{})
269 data["commits"] = commits
270 data["meta"] = d.c.Meta
271 data["name"] = name
272 data["ref"] = ref
273 data["desc"] = getDescription(path)
274 data["log"] = true
275
276 if err := t.ExecuteTemplate(w, "log", data); err != nil {
277 log.Println(err)
278 return
279 }
280}
281
282func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
283 name := r.PathValue("name")
284 if d.isIgnored(name) {
285 d.Write404(w)
286 return
287 }
288 ref := r.PathValue("ref")
289
290 path := filepath.Join(d.c.Repo.ScanPath, name)
291 gr, err := git.Open(path, ref)
292 if err != nil {
293 d.Write404(w)
294 return
295 }
296
297 diff, err := gr.Diff()
298 if err != nil {
299 d.Write500(w)
300 log.Println(err)
301 return
302 }
303
304 tpath := filepath.Join(d.c.Dirs.Templates, "*")
305 t := template.Must(template.ParseGlob(tpath))
306
307 data := make(map[string]interface{})
308
309 data["commit"] = diff.Commit
310 data["stat"] = diff.Stat
311 data["diff"] = diff.Diff
312 data["meta"] = d.c.Meta
313 data["name"] = name
314 data["ref"] = ref
315 data["desc"] = getDescription(path)
316
317 if err := t.ExecuteTemplate(w, "commit", data); err != nil {
318 log.Println(err)
319 return
320 }
321}
322
323func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
324 name := r.PathValue("name")
325 if d.isIgnored(name) {
326 d.Write404(w)
327 return
328 }
329
330 path := filepath.Join(d.c.Repo.ScanPath, name)
331 gr, err := git.Open(path, "")
332 if err != nil {
333 d.Write404(w)
334 return
335 }
336
337 tags, err := gr.Tags()
338 if err != nil {
339 // Non-fatal, we *should* have at least one branch to show.
340 log.Println(err)
341 }
342
343 branches, err := gr.Branches()
344 if err != nil {
345 log.Println(err)
346 d.Write500(w)
347 return
348 }
349
350 tpath := filepath.Join(d.c.Dirs.Templates, "*")
351 t := template.Must(template.ParseGlob(tpath))
352
353 data := make(map[string]interface{})
354
355 data["meta"] = d.c.Meta
356 data["name"] = name
357 data["branches"] = branches
358 data["tags"] = tags
359 data["desc"] = getDescription(path)
360
361 if err := t.ExecuteTemplate(w, "refs", data); err != nil {
362 log.Println(err)
363 return
364 }
365}
366
367func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
368 f := r.PathValue("file")
369 f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
370
371 http.ServeFile(w, r, f)
372}