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