this repo has no description
1package routes 2 3import ( 4 "compress/gzip" 5 "errors" 6 "fmt" 7 "html/template" 8 "log" 9 "net/http" 10 "os" 11 "path/filepath" 12 "sort" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/dustin/go-humanize" 18 "github.com/go-chi/chi/v5" 19 "github.com/go-git/go-git/v5/plumbing" 20 "github.com/icyphox/bild/legit/config" 21 "github.com/icyphox/bild/legit/db" 22 "github.com/icyphox/bild/legit/git" 23 "github.com/russross/blackfriday/v2" 24 "golang.org/x/crypto/ssh" 25) 26 27type Handle struct { 28 c *config.Config 29 t *template.Template 30 db *db.DB 31} 32 33func (h *Handle) Index(w http.ResponseWriter, r *http.Request) { 34 user := chi.URLParam(r, "user") 35 path := filepath.Join(h.c.Repo.ScanPath, user) 36 dirs, err := os.ReadDir(path) 37 if err != nil { 38 h.Write500(w) 39 log.Printf("reading scan path: %s", err) 40 return 41 } 42 43 type info struct { 44 DisplayName, Name, Desc, Idle string 45 d time.Time 46 } 47 48 infos := []info{} 49 50 for _, dir := range dirs { 51 name := dir.Name() 52 if !dir.IsDir() || h.isIgnored(name) || h.isUnlisted(name) { 53 continue 54 } 55 56 gr, err := git.Open(path, "") 57 if err != nil { 58 log.Println(err) 59 continue 60 } 61 62 c, err := gr.LastCommit() 63 if err != nil { 64 h.Write500(w) 65 log.Println(err) 66 return 67 } 68 69 infos = append(infos, info{ 70 DisplayName: getDisplayName(name), 71 Name: name, 72 Desc: getDescription(path), 73 Idle: humanize.Time(c.Author.When), 74 d: c.Author.When, 75 }) 76 } 77 78 sort.Slice(infos, func(i, j int) bool { 79 return infos[j].d.Before(infos[i].d) 80 }) 81 82 data := make(map[string]interface{}) 83 data["meta"] = h.c.Meta 84 data["info"] = infos 85 86 if err := h.t.ExecuteTemplate(w, "index", data); err != nil { 87 log.Println(err) 88 return 89 } 90} 91 92func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 93 name := uniqueName(r) 94 if h.isIgnored(name) { 95 h.Write404(w) 96 return 97 } 98 99 name = filepath.Clean(name) 100 path := filepath.Join(h.c.Repo.ScanPath, name) 101 102 fmt.Println(path) 103 gr, err := git.Open(path, "") 104 if err != nil { 105 if errors.Is(err, plumbing.ErrReferenceNotFound) { 106 h.t.ExecuteTemplate(w, "empty", nil) 107 return 108 } else { 109 h.Write404(w) 110 return 111 } 112 } 113 commits, err := gr.Commits() 114 if err != nil { 115 h.Write500(w) 116 log.Println(err) 117 return 118 } 119 120 var readmeContent template.HTML 121 for _, readme := range h.c.Repo.Readme { 122 ext := filepath.Ext(readme) 123 content, _ := gr.FileContent(readme) 124 if len(content) > 0 { 125 switch ext { 126 case ".md", ".mkd", ".markdown": 127 unsafe := blackfriday.Run( 128 []byte(content), 129 blackfriday.WithExtensions(blackfriday.CommonExtensions), 130 ) 131 html := sanitize(unsafe) 132 readmeContent = template.HTML(html) 133 default: 134 safe := sanitize([]byte(content)) 135 readmeContent = template.HTML( 136 fmt.Sprintf(`<pre>%s</pre>`, safe), 137 ) 138 } 139 break 140 } 141 } 142 143 if readmeContent == "" { 144 log.Printf("no readme found for %s", name) 145 } 146 147 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch) 148 if err != nil { 149 h.Write500(w) 150 log.Println(err) 151 return 152 } 153 154 if len(commits) >= 3 { 155 commits = commits[:3] 156 } 157 158 data := make(map[string]any) 159 data["name"] = name 160 data["displayname"] = getDisplayName(name) 161 data["ref"] = mainBranch 162 data["readme"] = readmeContent 163 data["commits"] = commits 164 data["desc"] = getDescription(path) 165 data["servername"] = h.c.Server.Name 166 data["meta"] = h.c.Meta 167 data["gomod"] = isGoModule(gr) 168 169 if err := h.t.ExecuteTemplate(w, "repo", data); err != nil { 170 log.Println(err) 171 return 172 } 173 174 return 175} 176 177func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) { 178 name := uniqueName(r) 179 if h.isIgnored(name) { 180 h.Write404(w) 181 return 182 } 183 treePath := chi.URLParam(r, "*") 184 ref := chi.URLParam(r, "ref") 185 186 name = filepath.Clean(name) 187 path := filepath.Join(h.c.Repo.ScanPath, name) 188 fmt.Println(path) 189 gr, err := git.Open(path, ref) 190 if err != nil { 191 h.Write404(w) 192 return 193 } 194 195 files, err := gr.FileTree(treePath) 196 if err != nil { 197 h.Write500(w) 198 log.Println(err) 199 return 200 } 201 202 data := make(map[string]any) 203 data["name"] = name 204 data["displayname"] = getDisplayName(name) 205 data["ref"] = ref 206 data["parent"] = treePath 207 data["desc"] = getDescription(path) 208 data["dotdot"] = filepath.Dir(treePath) 209 210 h.listFiles(files, data, w) 211 return 212} 213 214func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) { 215 var raw bool 216 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil { 217 raw = rawParam 218 } 219 220 name := uniqueName(r) 221 222 if h.isIgnored(name) { 223 h.Write404(w) 224 return 225 } 226 treePath := chi.URLParam(r, "*") 227 ref := chi.URLParam(r, "ref") 228 229 name = filepath.Clean(name) 230 path := filepath.Join(h.c.Repo.ScanPath, name) 231 gr, err := git.Open(path, ref) 232 if err != nil { 233 h.Write404(w) 234 return 235 } 236 237 contents, err := gr.FileContent(treePath) 238 if err != nil { 239 h.Write500(w) 240 return 241 } 242 data := make(map[string]any) 243 data["name"] = name 244 data["displayname"] = getDisplayName(name) 245 data["ref"] = ref 246 data["desc"] = getDescription(path) 247 data["path"] = treePath 248 249 safe := sanitize([]byte(contents)) 250 251 if raw { 252 h.showRaw(string(safe), w) 253 } else { 254 if h.c.Meta.SyntaxHighlight == "" { 255 h.showFile(string(safe), data, w) 256 } else { 257 h.showFileWithHighlight(treePath, string(safe), data, w) 258 } 259 } 260} 261 262func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 263 name := uniqueName(r) 264 if h.isIgnored(name) { 265 h.Write404(w) 266 return 267 } 268 269 file := chi.URLParam(r, "file") 270 271 // TODO: extend this to add more files compression (e.g.: xz) 272 if !strings.HasSuffix(file, ".tar.gz") { 273 h.Write404(w) 274 return 275 } 276 277 ref := strings.TrimSuffix(file, ".tar.gz") 278 279 // This allows the browser to use a proper name for the file when 280 // downloading 281 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) 282 setContentDisposition(w, filename) 283 setGZipMIME(w) 284 285 path := filepath.Join(h.c.Repo.ScanPath, name) 286 gr, err := git.Open(path, ref) 287 if err != nil { 288 h.Write404(w) 289 return 290 } 291 292 gw := gzip.NewWriter(w) 293 defer gw.Close() 294 295 prefix := fmt.Sprintf("%s-%s", name, ref) 296 err = gr.WriteTar(gw, prefix) 297 if err != nil { 298 // once we start writing to the body we can't report error anymore 299 // so we are only left with printing the error. 300 log.Println(err) 301 return 302 } 303 304 err = gw.Flush() 305 if err != nil { 306 // once we start writing to the body we can't report error anymore 307 // so we are only left with printing the error. 308 log.Println(err) 309 return 310 } 311} 312 313func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 314 name := uniqueName(r) 315 if h.isIgnored(name) { 316 h.Write404(w) 317 return 318 } 319 ref := chi.URLParam(r, "ref") 320 321 path := filepath.Join(h.c.Repo.ScanPath, name) 322 gr, err := git.Open(path, ref) 323 if err != nil { 324 h.Write404(w) 325 return 326 } 327 328 commits, err := gr.Commits() 329 if err != nil { 330 h.Write500(w) 331 log.Println(err) 332 return 333 } 334 335 data := make(map[string]interface{}) 336 data["commits"] = commits 337 data["meta"] = h.c.Meta 338 data["name"] = name 339 data["displayname"] = getDisplayName(name) 340 data["ref"] = ref 341 data["desc"] = getDescription(path) 342 data["log"] = true 343 344 if err := h.t.ExecuteTemplate(w, "log", data); err != nil { 345 log.Println(err) 346 return 347 } 348} 349 350func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 351 name := uniqueName(r) 352 if h.isIgnored(name) { 353 h.Write404(w) 354 return 355 } 356 ref := chi.URLParam(r, "ref") 357 358 path := filepath.Join(h.c.Repo.ScanPath, name) 359 gr, err := git.Open(path, ref) 360 if err != nil { 361 h.Write404(w) 362 return 363 } 364 365 diff, err := gr.Diff() 366 if err != nil { 367 h.Write500(w) 368 log.Println(err) 369 return 370 } 371 372 data := make(map[string]interface{}) 373 374 data["commit"] = diff.Commit 375 data["stat"] = diff.Stat 376 data["diff"] = diff.Diff 377 data["meta"] = h.c.Meta 378 data["name"] = name 379 data["displayname"] = getDisplayName(name) 380 data["ref"] = ref 381 data["desc"] = getDescription(path) 382 383 if err := h.t.ExecuteTemplate(w, "commit", data); err != nil { 384 log.Println(err) 385 return 386 } 387} 388 389func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) { 390 name := chi.URLParam(r, "name") 391 if h.isIgnored(name) { 392 h.Write404(w) 393 return 394 } 395 396 path := filepath.Join(h.c.Repo.ScanPath, name) 397 gr, err := git.Open(path, "") 398 if err != nil { 399 h.Write404(w) 400 return 401 } 402 403 tags, err := gr.Tags() 404 if err != nil { 405 // Non-fatal, we *should* have at least one branch to show. 406 log.Println(err) 407 } 408 409 branches, err := gr.Branches() 410 if err != nil { 411 log.Println(err) 412 h.Write500(w) 413 return 414 } 415 416 data := make(map[string]interface{}) 417 418 data["meta"] = h.c.Meta 419 data["name"] = name 420 data["displayname"] = getDisplayName(name) 421 data["branches"] = branches 422 data["tags"] = tags 423 data["desc"] = getDescription(path) 424 425 if err := h.t.ExecuteTemplate(w, "refs", data); err != nil { 426 log.Println(err) 427 return 428 } 429} 430 431func (h *Handle) ServeStatic(w http.ResponseWriter, r *http.Request) { 432 f := chi.URLParam(r, "file") 433 f = filepath.Clean(filepath.Join(h.c.Dirs.Static, f)) 434 435 http.ServeFile(w, r, f) 436} 437 438func (h *Handle) Login(w http.ResponseWriter, r *http.Request) { 439 if err := h.t.ExecuteTemplate(w, "login", nil); err != nil { 440 log.Println(err) 441 return 442 } 443} 444 445func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 446 switch r.Method { 447 case http.MethodGet: 448 keys, err := h.db.GetPublicKeys("did:ashtntnashtx") 449 if err != nil { 450 log.Println(err) 451 http.Error(w, "invalid `did`", http.StatusBadRequest) 452 return 453 } 454 455 data := make(map[string]interface{}) 456 data["keys"] = keys 457 if err := h.t.ExecuteTemplate(w, "keys", data); err != nil { 458 log.Println(err) 459 return 460 } 461 case http.MethodPut: 462 key := r.FormValue("key") 463 name := r.FormValue("name") 464 465 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 466 if err != nil { 467 h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.") 468 log.Printf("parsing public key: %s", err) 469 return 470 } 471 472 // TODO: add did here 473 if err := h.db.AddPublicKey("did:ashtntnashtx", name, key); err != nil { 474 h.WriteOOBNotice(w, "keys", "Failed to add key.") 475 log.Printf("adding public key: %s", err) 476 return 477 } 478 479 h.WriteOOBNotice(w, "keys", "Key added!") 480 return 481 } 482} 483 484func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) { 485 switch r.Method { 486 case http.MethodGet: 487 if err := h.t.ExecuteTemplate(w, "new", nil); err != nil { 488 log.Println(err) 489 return 490 } 491 case http.MethodPut: 492 name := r.FormValue("name") 493 description := r.FormValue("description") 494 495 // TODO: Get handle from session. We need this to construct the repository path. 496 err := git.InitBare(filepath.Join(h.c.Repo.ScanPath, "example.com", name)) 497 if err != nil { 498 h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.") 499 return 500 } 501 502 err = h.db.AddRepo("did:ashtntnashtx", name, description) 503 if err != nil { 504 h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.") 505 return 506 } 507 508 w.Header().Set("HX-Redirect", fmt.Sprintf("/@example.com/%s", name)) 509 w.WriteHeader(http.StatusOK) 510 } 511}