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":
114 unsafe := blackfriday.Run([]byte(content), blackfriday.WithExtensions(blackfriday.CommonExtensions))
115 html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
116 readmeContent = template.HTML(html)
117 default:
118 readmeContent = template.HTML(
119 fmt.Sprintf(`<pre>%s</pre>`, content),
120 )
121 }
122 break
123 }
124 }
125
126 if readmeContent == "" {
127 log.Printf("no readme found for %s", name)
128 }
129
130 mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
131 if err != nil {
132 d.Write500(w)
133 log.Println(err)
134 return
135 }
136
137 tpath := filepath.Join(d.c.Dirs.Templates, "*")
138 t := template.Must(template.ParseGlob(tpath))
139
140 if len(commits) >= 3 {
141 commits = commits[:3]
142 }
143
144 data := make(map[string]any)
145 data["name"] = name
146 data["ref"] = mainBranch
147 data["readme"] = readmeContent
148 data["commits"] = commits
149 data["desc"] = getDescription(path)
150 data["servername"] = d.c.Server.Name
151
152 if err := t.ExecuteTemplate(w, "repo", data); err != nil {
153 log.Println(err)
154 return
155 }
156
157 return
158}
159
160func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
161 name := flow.Param(r.Context(), "name")
162 if d.isIgnored(name) {
163 d.Write404(w)
164 return
165 }
166 treePath := flow.Param(r.Context(), "...")
167 ref := flow.Param(r.Context(), "ref")
168
169 name = filepath.Clean(name)
170 path := filepath.Join(d.c.Repo.ScanPath, name)
171 gr, err := git.Open(path, ref)
172 if err != nil {
173 d.Write404(w)
174 return
175 }
176
177 files, err := gr.FileTree(treePath)
178 if err != nil {
179 d.Write500(w)
180 log.Println(err)
181 return
182 }
183
184 data := make(map[string]any)
185 data["name"] = name
186 data["ref"] = ref
187 data["parent"] = treePath
188 data["desc"] = getDescription(path)
189
190 d.listFiles(files, data, w)
191 return
192}
193
194func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
195 name := flow.Param(r.Context(), "name")
196 if d.isIgnored(name) {
197 d.Write404(w)
198 return
199 }
200 treePath := flow.Param(r.Context(), "...")
201 ref := flow.Param(r.Context(), "ref")
202
203 name = filepath.Clean(name)
204 path := filepath.Join(d.c.Repo.ScanPath, name)
205 gr, err := git.Open(path, ref)
206 if err != nil {
207 d.Write404(w)
208 return
209 }
210
211 contents, err := gr.FileContent(treePath)
212 data := make(map[string]any)
213 data["name"] = name
214 data["ref"] = ref
215 data["desc"] = getDescription(path)
216 data["path"] = treePath
217
218 d.showFile(contents, data, w)
219 return
220}
221
222func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
223 name := flow.Param(r.Context(), "name")
224 if d.isIgnored(name) {
225 d.Write404(w)
226 return
227 }
228 ref := flow.Param(r.Context(), "ref")
229
230 path := filepath.Join(d.c.Repo.ScanPath, name)
231 gr, err := git.Open(path, ref)
232 if err != nil {
233 d.Write404(w)
234 return
235 }
236
237 commits, err := gr.Commits()
238 if err != nil {
239 d.Write500(w)
240 log.Println(err)
241 return
242 }
243
244 tpath := filepath.Join(d.c.Dirs.Templates, "*")
245 t := template.Must(template.ParseGlob(tpath))
246
247 data := make(map[string]interface{})
248 data["commits"] = commits
249 data["meta"] = d.c.Meta
250 data["name"] = name
251 data["ref"] = ref
252 data["desc"] = getDescription(path)
253
254 if err := t.ExecuteTemplate(w, "log", data); err != nil {
255 log.Println(err)
256 return
257 }
258}
259
260func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
261 name := flow.Param(r.Context(), "name")
262 if d.isIgnored(name) {
263 d.Write404(w)
264 return
265 }
266 ref := flow.Param(r.Context(), "ref")
267
268 path := filepath.Join(d.c.Repo.ScanPath, name)
269 gr, err := git.Open(path, ref)
270 if err != nil {
271 d.Write404(w)
272 return
273 }
274
275 diff, err := gr.Diff()
276 if err != nil {
277 d.Write500(w)
278 log.Println(err)
279 return
280 }
281
282 tpath := filepath.Join(d.c.Dirs.Templates, "*")
283 t := template.Must(template.ParseGlob(tpath))
284
285 data := make(map[string]interface{})
286
287 data["commit"] = diff.Commit
288 data["stat"] = diff.Stat
289 data["diff"] = diff.Diff
290 data["meta"] = d.c.Meta
291 data["name"] = name
292 data["ref"] = ref
293 data["desc"] = getDescription(path)
294
295 if err := t.ExecuteTemplate(w, "commit", data); err != nil {
296 log.Println(err)
297 return
298 }
299}
300
301func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
302 name := flow.Param(r.Context(), "name")
303 if d.isIgnored(name) {
304 d.Write404(w)
305 return
306 }
307
308 path := filepath.Join(d.c.Repo.ScanPath, name)
309 gr, err := git.Open(path, "")
310 if err != nil {
311 d.Write404(w)
312 return
313 }
314
315 tags, err := gr.Tags()
316 if err != nil {
317 // Non-fatal, we *should* have at least one branch to show.
318 log.Println(err)
319 }
320
321 branches, err := gr.Branches()
322 if err != nil {
323 log.Println(err)
324 d.Write500(w)
325 return
326 }
327
328 tpath := filepath.Join(d.c.Dirs.Templates, "*")
329 t := template.Must(template.ParseGlob(tpath))
330
331 data := make(map[string]interface{})
332
333 data["meta"] = d.c.Meta
334 data["name"] = name
335 data["branches"] = branches
336 data["tags"] = tags
337 data["desc"] = getDescription(path)
338
339 if err := t.ExecuteTemplate(w, "refs", data); err != nil {
340 log.Println(err)
341 return
342 }
343}
344
345func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
346 f := flow.Param(r.Context(), "file")
347 f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
348
349 http.ServeFile(w, r, f)
350}