this repo has no description

legit: setup db stuff and keys page

+8 -4
cmd/legit/main.go
··· 3 import ( 4 "flag" 5 "fmt" 6 - "html/template" 7 "log" 8 "net/http" 9 - "path/filepath" 10 11 "github.com/icyphox/bild/legit/config" 12 "github.com/icyphox/bild/legit/routes" ··· 16 var cfg string 17 flag.StringVar(&cfg, "config", "./config.yaml", "path to config file") 18 flag.Parse() 19 20 c, err := config.Read(cfg) 21 if err != nil { 22 log.Fatal(err) 23 } 24 25 - t := template.Must(template.ParseGlob(filepath.Join(c.Dirs.Templates, "*"))) 26 27 - mux := routes.Handlers(c, t) 28 addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) 29 log.Println("starting server on", addr) 30 log.Fatal(http.ListenAndServe(addr, mux))
··· 3 import ( 4 "flag" 5 "fmt" 6 "log" 7 + "log/slog" 8 "net/http" 9 + "os" 10 11 "github.com/icyphox/bild/legit/config" 12 "github.com/icyphox/bild/legit/routes" ··· 16 var cfg string 17 flag.StringVar(&cfg, "config", "./config.yaml", "path to config file") 18 flag.Parse() 19 + 20 + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) 21 22 c, err := config.Read(cfg) 23 if err != nil { 24 log.Fatal(err) 25 } 26 27 + mux, err := routes.Setup(c) 28 + if err != nil { 29 + log.Fatal(err) 30 + } 31 32 addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) 33 log.Println("starting server on", addr) 34 log.Fatal(http.ListenAndServe(addr, mux))
+1
config.yaml
··· 18 name: github.com/icyphox/bild 19 host: 0.0.0.0 20 port: 5555
··· 18 name: github.com/icyphox/bild 19 host: 0.0.0.0 20 port: 5555 21 + dbpath: bild.db
+1
go.mod
··· 10 github.com/dustin/go-humanize v1.0.1 11 github.com/go-chi/chi/v5 v5.2.0 12 github.com/go-git/go-git/v5 v5.12.0 13 github.com/microcosm-cc/bluemonday v1.0.27 14 github.com/russross/blackfriday/v2 v2.1.0 15 gopkg.in/yaml.v3 v3.0.1
··· 10 github.com/dustin/go-humanize v1.0.1 11 github.com/go-chi/chi/v5 v5.2.0 12 github.com/go-git/go-git/v5 v5.12.0 13 + github.com/mattn/go-sqlite3 v1.14.24 14 github.com/microcosm-cc/bluemonday v1.0.27 15 github.com/russross/blackfriday/v2 v2.1.0 16 gopkg.in/yaml.v3 v3.0.1
+2
go.sum
··· 78 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 79 github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= 80 github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 81 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 82 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 83 github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
··· 78 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 79 github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= 80 github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 81 + github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= 82 + github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 83 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 84 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 85 github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
+4 -3
legit/config/config.go
··· 26 SyntaxHighlight string `yaml:"syntaxHighlight"` 27 } `yaml:"meta"` 28 Server struct { 29 - Name string `yaml:"name,omitempty"` 30 - Host string `yaml:"host"` 31 - Port int `yaml:"port"` 32 } `yaml:"server"` 33 } 34
··· 26 SyntaxHighlight string `yaml:"syntaxHighlight"` 27 } `yaml:"meta"` 28 Server struct { 29 + Name string `yaml:"name,omitempty"` 30 + Host string `yaml:"host"` 31 + Port int `yaml:"port"` 32 + DBPath string `yaml:"dbpath"` 33 } `yaml:"server"` 34 } 35
+32
legit/db/init.go
···
··· 1 + package db 2 + 3 + import ( 4 + "database/sql" 5 + 6 + _ "github.com/mattn/go-sqlite3" 7 + ) 8 + 9 + type DB struct { 10 + db *sql.DB 11 + } 12 + 13 + func Setup(dbPath string) (*DB, error) { 14 + db, err := sql.Open("sqlite3", dbPath) 15 + if err != nil { 16 + return nil, err 17 + } 18 + 19 + _, err = db.Exec(` 20 + create table if not exists public_keys ( 21 + id integer primary key autoincrement, 22 + did text not null, 23 + name text not null, 24 + key text not null, 25 + unique(did, name, key)) 26 + `) 27 + if err != nil { 28 + return nil, err 29 + } 30 + 31 + return &DB{db: db}, nil 32 + }
+23
legit/db/pubkeys.go
···
··· 1 + package db 2 + 3 + func (d *DB) AddPublicKey(did, name, key string) error { 4 + query := `insert into public_keys (did, name, key) values (?, ?, ?)` 5 + _, err := d.db.Exec(query, did, name, key) 6 + return err 7 + } 8 + 9 + func (d *DB) RemovePublicKey(did string) error { 10 + query := `delete from public_keys where did = ?` 11 + _, err := d.db.Exec(query, did) 12 + return err 13 + } 14 + 15 + func (d *DB) GetPublicKey(did string) (string, error) { 16 + var key string 17 + query := `select key from public_keys where did = ?` 18 + err := d.db.QueryRow(query, did).Scan(&key) 19 + if err != nil { 20 + return "", err 21 + } 22 + return key, nil 23 + }
+2 -2
legit/routes/git.go
··· 10 "github.com/icyphox/bild/legit/git/service" 11 ) 12 13 - func (d *deps) InfoRefs(w http.ResponseWriter, r *http.Request) { 14 name := uniqueName(r) 15 name = filepath.Clean(name) 16 ··· 31 } 32 } 33 34 - func (d *deps) UploadPack(w http.ResponseWriter, r *http.Request) { 35 name := uniqueName(r) 36 name = filepath.Clean(name) 37
··· 10 "github.com/icyphox/bild/legit/git/service" 11 ) 12 13 + func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) { 14 name := uniqueName(r) 15 name = filepath.Clean(name) 16 ··· 31 } 32 } 33 34 + func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) { 35 name := uniqueName(r) 36 name = filepath.Clean(name) 37
+41 -20
legit/routes/handler.go
··· 1 package routes 2 3 import ( 4 "html/template" 5 "net/http" 6 7 "github.com/go-chi/chi/v5" 8 "github.com/icyphox/bild/legit/config" 9 ) 10 11 // Checks for gitprotocol-http(5) specific smells; if found, passes 12 // the request on to the git http service, else render the web frontend. 13 - func (d *deps) Multiplex(w http.ResponseWriter, r *http.Request) { 14 path := chi.URLParam(r, "*") 15 16 if r.URL.RawQuery == "service=git-receive-pack" { ··· 22 if path == "info/refs" && 23 r.URL.RawQuery == "service=git-upload-pack" && 24 r.Method == "GET" { 25 - d.InfoRefs(w, r) 26 } else if path == "git-upload-pack" && r.Method == "POST" { 27 - d.UploadPack(w, r) 28 } else if r.Method == "GET" { 29 - d.RepoIndex(w, r) 30 } 31 } 32 33 - func Handlers(c *config.Config, t *template.Template) http.Handler { 34 r := chi.NewRouter() 35 - d := deps{c, t} 36 37 - r.Get("/login", d.Login) 38 - r.Get("/static/{file}", d.ServeStatic) 39 40 r.Route("/@{user}", func(r chi.Router) { 41 - r.Get("/", d.Index) 42 r.Route("/{name}", func(r chi.Router) { 43 - r.Get("/", d.Multiplex) 44 - r.Post("/", d.Multiplex) 45 46 r.Route("/tree/{ref}", func(r chi.Router) { 47 - r.Get("/*", d.RepoTree) 48 }) 49 50 r.Route("/blob/{ref}", func(r chi.Router) { 51 - r.Get("/*", d.FileContent) 52 }) 53 54 - r.Get("/log/{ref}", d.Log) 55 - r.Get("/archive/{file}", d.Archive) 56 - r.Get("/commit/{ref}", d.Diff) 57 - r.Get("/refs/", d.Refs) 58 59 // Catch-all routes 60 - r.Get("/*", d.Multiplex) 61 - r.Post("/*", d.Multiplex) 62 }) 63 }) 64 65 - return r 66 }
··· 1 package routes 2 3 import ( 4 + "fmt" 5 "html/template" 6 "net/http" 7 + "path/filepath" 8 9 "github.com/go-chi/chi/v5" 10 "github.com/icyphox/bild/legit/config" 11 + "github.com/icyphox/bild/legit/db" 12 ) 13 14 // Checks for gitprotocol-http(5) specific smells; if found, passes 15 // the request on to the git http service, else render the web frontend. 16 + func (h *Handle) Multiplex(w http.ResponseWriter, r *http.Request) { 17 path := chi.URLParam(r, "*") 18 19 if r.URL.RawQuery == "service=git-receive-pack" { ··· 25 if path == "info/refs" && 26 r.URL.RawQuery == "service=git-upload-pack" && 27 r.Method == "GET" { 28 + h.InfoRefs(w, r) 29 } else if path == "git-upload-pack" && r.Method == "POST" { 30 + h.UploadPack(w, r) 31 } else if r.Method == "GET" { 32 + h.RepoIndex(w, r) 33 } 34 } 35 36 + func Setup(c *config.Config) (http.Handler, error) { 37 r := chi.NewRouter() 38 + t := template.Must(template.ParseGlob(filepath.Join(c.Dirs.Templates, "*"))) 39 + db, err := db.Setup(c.Server.DBPath) 40 + 41 + if err != nil { 42 + return nil, fmt.Errorf("failed to setup db: %w", err) 43 + } 44 + 45 + h := Handle{ 46 + c: c, 47 + t: t, 48 + db: db, 49 + } 50 + 51 + r.Get("/login", h.Login) 52 + r.Get("/static/{file}", h.ServeStatic) 53 54 + r.Route("/settings", func(r chi.Router) { 55 + r.Get("/keys", h.Keys) 56 + r.Put("/keys", h.Keys) 57 + }) 58 59 r.Route("/@{user}", func(r chi.Router) { 60 + r.Get("/", h.Index) 61 + 62 + // Repo routes 63 r.Route("/{name}", func(r chi.Router) { 64 + r.Get("/", h.Multiplex) 65 + r.Post("/", h.Multiplex) 66 67 r.Route("/tree/{ref}", func(r chi.Router) { 68 + r.Get("/*", h.RepoTree) 69 }) 70 71 r.Route("/blob/{ref}", func(r chi.Router) { 72 + r.Get("/*", h.FileContent) 73 }) 74 75 + r.Get("/log/{ref}", h.Log) 76 + r.Get("/archive/{file}", h.Archive) 77 + r.Get("/commit/{ref}", h.Diff) 78 + r.Get("/refs/", h.Refs) 79 80 // Catch-all routes 81 + r.Get("/*", h.Multiplex) 82 + r.Post("/*", h.Multiplex) 83 }) 84 }) 85 86 + return r, nil 87 }
+98 -73
legit/routes/routes.go
··· 16 "github.com/dustin/go-humanize" 17 "github.com/go-chi/chi/v5" 18 "github.com/icyphox/bild/legit/config" 19 "github.com/icyphox/bild/legit/git" 20 "github.com/russross/blackfriday/v2" 21 ) 22 23 - type deps struct { 24 - c *config.Config 25 - t *template.Template 26 } 27 28 - func (d *deps) Index(w http.ResponseWriter, r *http.Request) { 29 user := chi.URLParam(r, "user") 30 - path := filepath.Join(d.c.Repo.ScanPath, user) 31 dirs, err := os.ReadDir(path) 32 if err != nil { 33 - d.Write500(w) 34 log.Printf("reading scan path: %s", err) 35 return 36 } ··· 44 45 for _, dir := range dirs { 46 name := dir.Name() 47 - if !dir.IsDir() || d.isIgnored(name) || d.isUnlisted(name) { 48 continue 49 } 50 ··· 56 57 c, err := gr.LastCommit() 58 if err != nil { 59 - d.Write500(w) 60 log.Println(err) 61 return 62 } ··· 75 }) 76 77 data := make(map[string]interface{}) 78 - data["meta"] = d.c.Meta 79 data["info"] = infos 80 81 - if err := d.t.ExecuteTemplate(w, "index", data); err != nil { 82 log.Println(err) 83 return 84 } 85 } 86 87 - func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) { 88 name := uniqueName(r) 89 - if d.isIgnored(name) { 90 - d.Write404(w) 91 return 92 } 93 94 name = filepath.Clean(name) 95 - path := filepath.Join(d.c.Repo.ScanPath, name) 96 97 fmt.Println(path) 98 gr, err := git.Open(path, "") 99 if err != nil { 100 - d.Write404(w) 101 return 102 } 103 commits, err := gr.Commits() 104 if err != nil { 105 - d.Write500(w) 106 log.Println(err) 107 return 108 } 109 110 var readmeContent template.HTML 111 - for _, readme := range d.c.Repo.Readme { 112 ext := filepath.Ext(readme) 113 content, _ := gr.FileContent(readme) 114 if len(content) > 0 { ··· 134 log.Printf("no readme found for %s", name) 135 } 136 137 - mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch) 138 if err != nil { 139 - d.Write500(w) 140 log.Println(err) 141 return 142 } ··· 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 := d.t.ExecuteTemplate(w, "repo", data); err != nil { 160 log.Println(err) 161 return 162 } ··· 164 return 165 } 166 167 - func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) { 168 name := uniqueName(r) 169 - if d.isIgnored(name) { 170 - d.Write404(w) 171 return 172 } 173 treePath := chi.URLParam(r, "*") 174 ref := chi.URLParam(r, "ref") 175 176 name = filepath.Clean(name) 177 - path := filepath.Join(d.c.Repo.ScanPath, name) 178 fmt.Println(path) 179 gr, err := git.Open(path, ref) 180 if err != nil { 181 - d.Write404(w) 182 return 183 } 184 185 files, err := gr.FileTree(treePath) 186 if err != nil { 187 - d.Write500(w) 188 log.Println(err) 189 return 190 } ··· 197 data["desc"] = getDescription(path) 198 data["dotdot"] = filepath.Dir(treePath) 199 200 - d.listFiles(files, data, w) 201 return 202 } 203 204 - func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) { 205 var raw bool 206 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil { 207 raw = rawParam ··· 209 210 name := uniqueName(r) 211 212 - if d.isIgnored(name) { 213 - d.Write404(w) 214 return 215 } 216 treePath := chi.URLParam(r, "*") 217 ref := chi.URLParam(r, "ref") 218 219 name = filepath.Clean(name) 220 - path := filepath.Join(d.c.Repo.ScanPath, name) 221 gr, err := git.Open(path, ref) 222 if err != nil { 223 - d.Write404(w) 224 return 225 } 226 227 contents, err := gr.FileContent(treePath) 228 if err != nil { 229 - d.Write500(w) 230 return 231 } 232 data := make(map[string]any) ··· 239 safe := sanitize([]byte(contents)) 240 241 if raw { 242 - d.showRaw(string(safe), w) 243 } else { 244 - if d.c.Meta.SyntaxHighlight == "" { 245 - d.showFile(string(safe), data, w) 246 } else { 247 - d.showFileWithHighlight(treePath, string(safe), data, w) 248 } 249 } 250 } 251 252 - func (d *deps) Archive(w http.ResponseWriter, r *http.Request) { 253 name := uniqueName(r) 254 - if d.isIgnored(name) { 255 - d.Write404(w) 256 return 257 } 258 ··· 260 261 // TODO: extend this to add more files compression (e.g.: xz) 262 if !strings.HasSuffix(file, ".tar.gz") { 263 - d.Write404(w) 264 return 265 } 266 ··· 272 setContentDisposition(w, filename) 273 setGZipMIME(w) 274 275 - path := filepath.Join(d.c.Repo.ScanPath, name) 276 gr, err := git.Open(path, ref) 277 if err != nil { 278 - d.Write404(w) 279 return 280 } 281 ··· 300 } 301 } 302 303 - func (d *deps) Log(w http.ResponseWriter, r *http.Request) { 304 name := uniqueName(r) 305 - if d.isIgnored(name) { 306 - d.Write404(w) 307 return 308 } 309 ref := chi.URLParam(r, "ref") 310 311 - path := filepath.Join(d.c.Repo.ScanPath, name) 312 gr, err := git.Open(path, ref) 313 if err != nil { 314 - d.Write404(w) 315 return 316 } 317 318 commits, err := gr.Commits() 319 if err != nil { 320 - d.Write500(w) 321 log.Println(err) 322 return 323 } 324 325 data := make(map[string]interface{}) 326 data["commits"] = commits 327 - data["meta"] = d.c.Meta 328 data["name"] = name 329 data["displayname"] = getDisplayName(name) 330 data["ref"] = ref 331 data["desc"] = getDescription(path) 332 data["log"] = true 333 334 - if err := d.t.ExecuteTemplate(w, "log", data); err != nil { 335 log.Println(err) 336 return 337 } 338 } 339 340 - func (d *deps) Diff(w http.ResponseWriter, r *http.Request) { 341 name := uniqueName(r) 342 - if d.isIgnored(name) { 343 - d.Write404(w) 344 return 345 } 346 ref := chi.URLParam(r, "ref") 347 348 - path := filepath.Join(d.c.Repo.ScanPath, name) 349 gr, err := git.Open(path, ref) 350 if err != nil { 351 - d.Write404(w) 352 return 353 } 354 355 diff, err := gr.Diff() 356 if err != nil { 357 - d.Write500(w) 358 log.Println(err) 359 return 360 } ··· 364 data["commit"] = diff.Commit 365 data["stat"] = diff.Stat 366 data["diff"] = diff.Diff 367 - data["meta"] = d.c.Meta 368 data["name"] = name 369 data["displayname"] = getDisplayName(name) 370 data["ref"] = ref 371 data["desc"] = getDescription(path) 372 373 - if err := d.t.ExecuteTemplate(w, "commit", data); err != nil { 374 log.Println(err) 375 return 376 } 377 } 378 379 - func (d *deps) Refs(w http.ResponseWriter, r *http.Request) { 380 name := chi.URLParam(r, "name") 381 - if d.isIgnored(name) { 382 - d.Write404(w) 383 return 384 } 385 386 - path := filepath.Join(d.c.Repo.ScanPath, name) 387 gr, err := git.Open(path, "") 388 if err != nil { 389 - d.Write404(w) 390 return 391 } 392 ··· 399 branches, err := gr.Branches() 400 if err != nil { 401 log.Println(err) 402 - d.Write500(w) 403 return 404 } 405 406 data := make(map[string]interface{}) 407 408 - data["meta"] = d.c.Meta 409 data["name"] = name 410 data["displayname"] = getDisplayName(name) 411 data["branches"] = branches 412 data["tags"] = tags 413 data["desc"] = getDescription(path) 414 415 - if err := d.t.ExecuteTemplate(w, "refs", data); err != nil { 416 log.Println(err) 417 return 418 } 419 } 420 421 - func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) { 422 f := chi.URLParam(r, "file") 423 - f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f)) 424 425 http.ServeFile(w, r, f) 426 } 427 428 - func (d *deps) Login(w http.ResponseWriter, r *http.Request) { 429 - if err := d.t.ExecuteTemplate(w, "login", nil); err != nil { 430 log.Println(err) 431 return 432 } 433 }
··· 16 "github.com/dustin/go-humanize" 17 "github.com/go-chi/chi/v5" 18 "github.com/icyphox/bild/legit/config" 19 + "github.com/icyphox/bild/legit/db" 20 "github.com/icyphox/bild/legit/git" 21 "github.com/russross/blackfriday/v2" 22 ) 23 24 + type Handle struct { 25 + c *config.Config 26 + t *template.Template 27 + db *db.DB 28 } 29 30 + func (h *Handle) Index(w http.ResponseWriter, r *http.Request) { 31 user := chi.URLParam(r, "user") 32 + path := filepath.Join(h.c.Repo.ScanPath, user) 33 dirs, err := os.ReadDir(path) 34 if err != nil { 35 + h.Write500(w) 36 log.Printf("reading scan path: %s", err) 37 return 38 } ··· 46 47 for _, dir := range dirs { 48 name := dir.Name() 49 + if !dir.IsDir() || h.isIgnored(name) || h.isUnlisted(name) { 50 continue 51 } 52 ··· 58 59 c, err := gr.LastCommit() 60 if err != nil { 61 + h.Write500(w) 62 log.Println(err) 63 return 64 } ··· 77 }) 78 79 data := make(map[string]interface{}) 80 + data["meta"] = h.c.Meta 81 data["info"] = infos 82 83 + if err := h.t.ExecuteTemplate(w, "index", data); err != nil { 84 log.Println(err) 85 return 86 } 87 } 88 89 + func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 90 name := uniqueName(r) 91 + if h.isIgnored(name) { 92 + h.Write404(w) 93 return 94 } 95 96 name = filepath.Clean(name) 97 + path := filepath.Join(h.c.Repo.ScanPath, name) 98 99 fmt.Println(path) 100 gr, err := git.Open(path, "") 101 if err != nil { 102 + h.Write404(w) 103 return 104 } 105 commits, err := gr.Commits() 106 if err != nil { 107 + h.Write500(w) 108 log.Println(err) 109 return 110 } 111 112 var readmeContent template.HTML 113 + for _, readme := range h.c.Repo.Readme { 114 ext := filepath.Ext(readme) 115 content, _ := gr.FileContent(readme) 116 if len(content) > 0 { ··· 136 log.Printf("no readme found for %s", name) 137 } 138 139 + mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch) 140 if err != nil { 141 + h.Write500(w) 142 log.Println(err) 143 return 144 } ··· 154 data["readme"] = readmeContent 155 data["commits"] = commits 156 data["desc"] = getDescription(path) 157 + data["servername"] = h.c.Server.Name 158 + data["meta"] = h.c.Meta 159 data["gomod"] = isGoModule(gr) 160 161 + if err := h.t.ExecuteTemplate(w, "repo", data); err != nil { 162 log.Println(err) 163 return 164 } ··· 166 return 167 } 168 169 + func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) { 170 name := uniqueName(r) 171 + if h.isIgnored(name) { 172 + h.Write404(w) 173 return 174 } 175 treePath := chi.URLParam(r, "*") 176 ref := chi.URLParam(r, "ref") 177 178 name = filepath.Clean(name) 179 + path := filepath.Join(h.c.Repo.ScanPath, name) 180 fmt.Println(path) 181 gr, err := git.Open(path, ref) 182 if err != nil { 183 + h.Write404(w) 184 return 185 } 186 187 files, err := gr.FileTree(treePath) 188 if err != nil { 189 + h.Write500(w) 190 log.Println(err) 191 return 192 } ··· 199 data["desc"] = getDescription(path) 200 data["dotdot"] = filepath.Dir(treePath) 201 202 + h.listFiles(files, data, w) 203 return 204 } 205 206 + func (h *Handle) 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 ··· 211 212 name := uniqueName(r) 213 214 + if h.isIgnored(name) { 215 + h.Write404(w) 216 return 217 } 218 treePath := chi.URLParam(r, "*") 219 ref := chi.URLParam(r, "ref") 220 221 name = filepath.Clean(name) 222 + path := filepath.Join(h.c.Repo.ScanPath, name) 223 gr, err := git.Open(path, ref) 224 if err != nil { 225 + h.Write404(w) 226 return 227 } 228 229 contents, err := gr.FileContent(treePath) 230 if err != nil { 231 + h.Write500(w) 232 return 233 } 234 data := make(map[string]any) ··· 241 safe := sanitize([]byte(contents)) 242 243 if raw { 244 + h.showRaw(string(safe), w) 245 } else { 246 + if h.c.Meta.SyntaxHighlight == "" { 247 + h.showFile(string(safe), data, w) 248 } else { 249 + h.showFileWithHighlight(treePath, string(safe), data, w) 250 } 251 } 252 } 253 254 + func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 255 name := uniqueName(r) 256 + if h.isIgnored(name) { 257 + h.Write404(w) 258 return 259 } 260 ··· 262 263 // TODO: extend this to add more files compression (e.g.: xz) 264 if !strings.HasSuffix(file, ".tar.gz") { 265 + h.Write404(w) 266 return 267 } 268 ··· 274 setContentDisposition(w, filename) 275 setGZipMIME(w) 276 277 + path := filepath.Join(h.c.Repo.ScanPath, name) 278 gr, err := git.Open(path, ref) 279 if err != nil { 280 + h.Write404(w) 281 return 282 } 283 ··· 302 } 303 } 304 305 + func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 306 name := uniqueName(r) 307 + if h.isIgnored(name) { 308 + h.Write404(w) 309 return 310 } 311 ref := chi.URLParam(r, "ref") 312 313 + path := filepath.Join(h.c.Repo.ScanPath, name) 314 gr, err := git.Open(path, ref) 315 if err != nil { 316 + h.Write404(w) 317 return 318 } 319 320 commits, err := gr.Commits() 321 if err != nil { 322 + h.Write500(w) 323 log.Println(err) 324 return 325 } 326 327 data := make(map[string]interface{}) 328 data["commits"] = commits 329 + data["meta"] = h.c.Meta 330 data["name"] = name 331 data["displayname"] = getDisplayName(name) 332 data["ref"] = ref 333 data["desc"] = getDescription(path) 334 data["log"] = true 335 336 + if err := h.t.ExecuteTemplate(w, "log", data); err != nil { 337 log.Println(err) 338 return 339 } 340 } 341 342 + func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 343 name := uniqueName(r) 344 + if h.isIgnored(name) { 345 + h.Write404(w) 346 return 347 } 348 ref := chi.URLParam(r, "ref") 349 350 + path := filepath.Join(h.c.Repo.ScanPath, name) 351 gr, err := git.Open(path, ref) 352 if err != nil { 353 + h.Write404(w) 354 return 355 } 356 357 diff, err := gr.Diff() 358 if err != nil { 359 + h.Write500(w) 360 log.Println(err) 361 return 362 } ··· 366 data["commit"] = diff.Commit 367 data["stat"] = diff.Stat 368 data["diff"] = diff.Diff 369 + data["meta"] = h.c.Meta 370 data["name"] = name 371 data["displayname"] = getDisplayName(name) 372 data["ref"] = ref 373 data["desc"] = getDescription(path) 374 375 + if err := h.t.ExecuteTemplate(w, "commit", data); err != nil { 376 log.Println(err) 377 return 378 } 379 } 380 381 + func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) { 382 name := chi.URLParam(r, "name") 383 + if h.isIgnored(name) { 384 + h.Write404(w) 385 return 386 } 387 388 + path := filepath.Join(h.c.Repo.ScanPath, name) 389 gr, err := git.Open(path, "") 390 if err != nil { 391 + h.Write404(w) 392 return 393 } 394 ··· 401 branches, err := gr.Branches() 402 if err != nil { 403 log.Println(err) 404 + h.Write500(w) 405 return 406 } 407 408 data := make(map[string]interface{}) 409 410 + data["meta"] = h.c.Meta 411 data["name"] = name 412 data["displayname"] = getDisplayName(name) 413 data["branches"] = branches 414 data["tags"] = tags 415 data["desc"] = getDescription(path) 416 417 + if err := h.t.ExecuteTemplate(w, "refs", data); err != nil { 418 log.Println(err) 419 return 420 } 421 } 422 423 + func (h *Handle) ServeStatic(w http.ResponseWriter, r *http.Request) { 424 f := chi.URLParam(r, "file") 425 + f = filepath.Clean(filepath.Join(h.c.Dirs.Static, f)) 426 427 http.ServeFile(w, r, f) 428 } 429 430 + func (h *Handle) Login(w http.ResponseWriter, r *http.Request) { 431 + if err := h.t.ExecuteTemplate(w, "login", nil); err != nil { 432 log.Println(err) 433 return 434 } 435 } 436 + 437 + func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 438 + switch r.Method { 439 + case http.MethodGet: 440 + // TODO: fetch keys from db 441 + if err := h.t.ExecuteTemplate(w, "keys", nil); err != nil { 442 + log.Println(err) 443 + return 444 + } 445 + case http.MethodPut: 446 + key := r.FormValue("key") 447 + name := r.FormValue("name") 448 + // TODO: add did here 449 + if err := h.db.AddPublicKey("did:ashtntnashtx", name, key); err != nil { 450 + h.WriteOOBNotice(w, "keys", "Failed to add key") 451 + log.Printf("adding public key: %s", err) 452 + return 453 + } 454 + 455 + h.WriteOOBNotice(w, "keys", "Key added!") 456 + return 457 + } 458 + }
+19 -14
legit/routes/template.go
··· 2 3 import ( 4 "bytes" 5 "html/template" 6 "io" 7 "log" ··· 15 "github.com/icyphox/bild/legit/git" 16 ) 17 18 - func (d *deps) Write404(w http.ResponseWriter) { 19 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 20 - t := template.Must(template.ParseGlob(tpath)) 21 w.WriteHeader(404) 22 - if err := t.ExecuteTemplate(w, "404", nil); err != nil { 23 log.Printf("404 template: %s", err) 24 } 25 } 26 27 - func (d *deps) Write500(w http.ResponseWriter) { 28 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 29 - t := template.Must(template.ParseGlob(tpath)) 30 w.WriteHeader(500) 31 - if err := t.ExecuteTemplate(w, "500", nil); err != nil { 32 log.Printf("500 template: %s", err) 33 } 34 } 35 36 - func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) { 37 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 38 t := template.Must(template.ParseGlob(tpath)) 39 40 data["files"] = files 41 - data["meta"] = d.c.Meta 42 43 if err := t.ExecuteTemplate(w, "tree", data); err != nil { 44 log.Println(err) ··· 72 } 73 } 74 75 - func (d *deps) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) { 76 tpath := filepath.Join(d.c.Dirs.Templates, "*") 77 t := template.Must(template.ParseGlob(tpath)) 78 ··· 114 } 115 } 116 117 - func (d *deps) showFile(content string, data map[string]any, w http.ResponseWriter) { 118 tpath := filepath.Join(d.c.Dirs.Templates, "*") 119 t := template.Must(template.ParseGlob(tpath)) 120 ··· 142 } 143 } 144 145 - func (d *deps) showRaw(content string, w http.ResponseWriter) { 146 w.WriteHeader(http.StatusOK) 147 w.Header().Set("Content-Type", "text/plain") 148 w.Write([]byte(content))
··· 2 3 import ( 4 "bytes" 5 + "fmt" 6 "html/template" 7 "io" 8 "log" ··· 16 "github.com/icyphox/bild/legit/git" 17 ) 18 19 + func (h *Handle) Write404(w http.ResponseWriter) { 20 w.WriteHeader(404) 21 + if err := h.t.ExecuteTemplate(w, "404", nil); err != nil { 22 log.Printf("404 template: %s", err) 23 } 24 } 25 26 + func (h *Handle) Write500(w http.ResponseWriter) { 27 w.WriteHeader(500) 28 + if err := h.t.ExecuteTemplate(w, "500", nil); err != nil { 29 log.Printf("500 template: %s", err) 30 } 31 } 32 33 + func (h *Handle) WriteOOBNotice(w http.ResponseWriter, id, msg string) { 34 + html := fmt.Sprintf(`<span id="%s" hx-swap-oob="innerHTML">%s</span>`, id, msg) 35 + 36 + w.Header().Set("Content-Type", "text/html") 37 + w.WriteHeader(http.StatusOK) 38 + w.Write([]byte(html)) 39 + } 40 + 41 + func (h *Handle) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) { 42 + tpath := filepath.Join(h.c.Dirs.Templates, "*") 43 t := template.Must(template.ParseGlob(tpath)) 44 45 data["files"] = files 46 + data["meta"] = h.c.Meta 47 48 if err := t.ExecuteTemplate(w, "tree", data); err != nil { 49 log.Println(err) ··· 77 } 78 } 79 80 + func (d *Handle) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) { 81 tpath := filepath.Join(d.c.Dirs.Templates, "*") 82 t := template.Must(template.ParseGlob(tpath)) 83 ··· 119 } 120 } 121 122 + func (d *Handle) showFile(content string, data map[string]any, w http.ResponseWriter) { 123 tpath := filepath.Join(d.c.Dirs.Templates, "*") 124 t := template.Must(template.ParseGlob(tpath)) 125 ··· 147 } 148 } 149 150 + func (d *Handle) showRaw(content string, w http.ResponseWriter) { 151 w.WriteHeader(http.StatusOK) 152 w.Header().Set("Content-Type", "text/plain") 153 w.Write([]byte(content))
+6 -6
legit/routes/util.go
··· 43 return 44 } 45 46 - func (d *deps) isUnlisted(name string) bool { 47 - for _, i := range d.c.Repo.Unlisted { 48 if name == i { 49 return true 50 } ··· 53 return false 54 } 55 56 - func (d *deps) isIgnored(name string) bool { 57 - for _, i := range d.c.Repo.Ignore { 58 if name == i { 59 return true 60 } ··· 69 Category string 70 } 71 72 - func (d *deps) getAllRepos() ([]repoInfo, error) { 73 repos := []repoInfo{} 74 max := strings.Count(d.c.Repo.ScanPath, string(os.PathSeparator)) + 2 75 ··· 113 return repos, err 114 } 115 116 - func (d *deps) category(path string) string { 117 return strings.TrimPrefix(filepath.Dir(strings.TrimPrefix(path, d.c.Repo.ScanPath)), string(os.PathSeparator)) 118 } 119
··· 43 return 44 } 45 46 + func (h *Handle) isUnlisted(name string) bool { 47 + for _, i := range h.c.Repo.Unlisted { 48 if name == i { 49 return true 50 } ··· 53 return false 54 } 55 56 + func (h *Handle) isIgnored(name string) bool { 57 + for _, i := range h.c.Repo.Ignore { 58 if name == i { 59 return true 60 } ··· 69 Category string 70 } 71 72 + func (d *Handle) getAllRepos() ([]repoInfo, error) { 73 repos := []repoInfo{} 74 max := strings.Count(d.c.Repo.ScanPath, string(os.PathSeparator)) + 2 75 ··· 113 return repos, err 114 } 115 116 + func (d *Handle) category(path string) string { 117 return strings.TrimPrefix(filepath.Dir(strings.TrimPrefix(path, d.c.Repo.ScanPath)), string(os.PathSeparator)) 118 } 119
+177 -167
legit/static/style.css
··· 1 :root { 2 - --white: #fff; 3 - --light: #f4f4f4; 4 - --cyan: #509c93; 5 - --light-gray: #eee; 6 - --medium-gray: #ddd; 7 - --gray: #6a6a6a; 8 - --dark: #444; 9 - --darker: #222; 10 11 - --sans-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Segoe UI", sans-serif; 12 - --display-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Segoe UI", sans-serif; 13 - --mono-font: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', 'Roboto Mono', Menlo, Consolas, monospace; 14 } 15 16 @media (prefers-color-scheme: dark) { 17 - :root { 18 - color-scheme: dark light; 19 - --light: #181818; 20 - --cyan: #76c7c0; 21 - --light-gray: #333; 22 - --medium-gray: #444; 23 - --gray: #aaa; 24 - --dark: #ddd; 25 - --darker: #f4f4f4; 26 - } 27 } 28 29 html { 30 - background: var(--white); 31 - -webkit-text-size-adjust: none; 32 - font-family: var(--sans-font); 33 - font-weight: 380; 34 } 35 36 pre { 37 - font-family: var(--mono-font); 38 } 39 40 ::selection { 41 - background: var(--medium-gray); 42 - opacity: 0.3; 43 } 44 45 * { 46 - box-sizing: border-box; 47 - padding: 0; 48 - margin: 0; 49 } 50 51 body { 52 - max-width: 1000px; 53 - padding: 0 13px; 54 - margin: 40px auto; 55 } 56 57 - main, footer { 58 - font-size: 1rem; 59 - padding: 0; 60 - line-height: 160%; 61 } 62 63 - header h1, h2, h3 { 64 - font-family: var(--display-font); 65 } 66 67 h2 { 68 - font-weight: 400; 69 } 70 71 strong { 72 - font-weight: 500; 73 } 74 75 main h1 { 76 - padding: 10px 0 10px 0; 77 } 78 79 main h2 { 80 - font-size: 18px; 81 } 82 83 - main h2, h3 { 84 - padding: 20px 0 15px 0; 85 } 86 87 nav { 88 - padding: 0.4rem 0 1.5rem 0; 89 } 90 91 nav ul { 92 - padding: 0; 93 - margin: 0; 94 - list-style: none; 95 - padding-bottom: 20px; 96 } 97 98 nav ul li { 99 - padding-right: 10px; 100 - display: inline-block; 101 } 102 103 a { 104 - margin: 0; 105 - padding: 0; 106 - box-sizing: border-box; 107 - text-decoration: none; 108 - word-wrap: break-word; 109 } 110 111 a { 112 - color: var(--darker); 113 - border-bottom: 1.5px solid var(--medium-gray); 114 } 115 116 a:hover { 117 - border-bottom: 1.5px solid var(--gray); 118 } 119 120 .index { 121 - padding-top: 2em; 122 - display: grid; 123 - grid-template-columns: 6em 1fr minmax(0, 7em); 124 - grid-row-gap: 0.5em; 125 - min-width: 0; 126 } 127 128 .clone-url { 129 - padding-top: 2rem; 130 } 131 132 .clone-url pre { 133 - color: var(--dark); 134 - white-space: pre-wrap; 135 } 136 137 .desc { 138 - font-weight: normal; 139 - color: var(--gray); 140 - font-style: italic; 141 } 142 143 .tree { 144 - display: grid; 145 - grid-template-columns: 10ch auto 1fr; 146 - grid-row-gap: 0.5em; 147 - grid-column-gap: 1em; 148 - min-width: 0; 149 } 150 151 .log { 152 - display: grid; 153 - grid-template-columns: 20rem minmax(0, 1fr); 154 - grid-row-gap: 0.8em; 155 - grid-column-gap: 8rem; 156 - margin-bottom: 2em; 157 - padding-bottom: 1em; 158 - border-bottom: 1.5px solid var(--medium-gray); 159 } 160 161 .log pre { 162 - white-space: pre-wrap; 163 } 164 165 - .mode, .size { 166 - font-family: var(--mono-font); 167 } 168 .size { 169 - text-align: right; 170 } 171 172 .readme pre { 173 - white-space: pre-wrap; 174 - overflow-x: auto; 175 } 176 177 .readme { 178 - background: var(--light-gray); 179 - padding: 0.5rem; 180 } 181 182 .readme ul { 183 - padding: revert; 184 } 185 186 .readme img { 187 - max-width: 100%; 188 } 189 190 .diff { 191 - margin: 1rem 0 1rem 0; 192 - padding: 1rem 0 1rem 0; 193 - border-bottom: 1.5px solid var(--medium-gray); 194 } 195 196 .diff pre { 197 - overflow: scroll; 198 } 199 200 .diff-stat { 201 - padding: 1rem 0 1rem 0; 202 } 203 204 - .commit-hash, .commit-email { 205 - font-family: var(--mono-font); 206 } 207 208 .commit-email:before { 209 - content: '<'; 210 } 211 212 .commit-email:after { 213 - content: '>'; 214 } 215 216 .commit { 217 - margin-bottom: 1rem; 218 } 219 220 .commit pre { 221 - padding-bottom: 1rem; 222 - white-space: pre-wrap; 223 } 224 225 .diff-stat ul li { 226 - list-style: none; 227 - padding-left: 0.5em; 228 } 229 230 .diff-add { 231 - color: green; 232 } 233 234 .diff-del { 235 - color: red; 236 } 237 238 .diff-noop { 239 - color: var(--gray); 240 } 241 242 .ref { 243 - font-family: var(--sans-font); 244 - font-size: 14px; 245 - color: var(--gray); 246 - display: inline-block; 247 - padding-top: 0.7em; 248 } 249 250 .refs pre { 251 - white-space: pre-wrap; 252 - padding-bottom: 0.5rem; 253 } 254 255 .refs strong { 256 - padding-right: 1em; 257 } 258 259 .line-numbers { 260 - white-space: pre-line; 261 - -moz-user-select: -moz-none; 262 - -khtml-user-select: none; 263 - -webkit-user-select: none; 264 - -o-user-select: none; 265 - user-select: none; 266 - display: flex; 267 - float: left; 268 - flex-direction: column; 269 - margin-right: 1ch; 270 } 271 272 .file-wrapper { 273 - display: flex; 274 - flex-direction: row; 275 - grid-template-columns: 1rem minmax(0, 1fr); 276 - gap: 1rem; 277 - padding: 0.5rem; 278 - background: var(--light-gray); 279 - overflow-x: auto; 280 } 281 282 .chroma-file-wrapper { 283 - display: flex; 284 - flex-direction: row; 285 - grid-template-columns: 1rem minmax(0, 1fr); 286 - overflow-x: auto; 287 } 288 289 .file-content { 290 - background: var(--light-gray); 291 - overflow-y: hidden; 292 - overflow-x: auto; 293 } 294 295 .diff-type { 296 - color: var(--gray); 297 } 298 299 .commit-info { 300 - color: var(--gray); 301 - padding-bottom: 1.5rem; 302 - font-size: 0.85rem; 303 } 304 305 @media (max-width: 600px) { 306 - .index { 307 - grid-row-gap: 0.8em; 308 - } 309 310 - .log { 311 - grid-template-columns: 1fr; 312 - grid-row-gap: 0em; 313 - } 314 315 - .index { 316 - grid-template-columns: 1fr; 317 - grid-row-gap: 0em; 318 - } 319 320 - .index-name:not(:first-child) { 321 - padding-top: 1.5rem; 322 - } 323 324 - .commit-info:not(:last-child) { 325 - padding-bottom: 1.5rem; 326 - } 327 328 - pre { 329 - font-size: 0.8rem; 330 - } 331 }
··· 1 :root { 2 + --white: #fff; 3 + --light: #f4f4f4; 4 + --cyan: #509c93; 5 + --light-gray: #eee; 6 + --medium-gray: #ddd; 7 + --gray: #6a6a6a; 8 + --dark: #444; 9 + --darker: #222; 10 11 + --sans-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", 12 + "Segoe UI", sans-serif; 13 + --display-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", 14 + "Segoe UI", sans-serif; 15 + --mono-font: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", 16 + "Roboto Mono", Menlo, Consolas, monospace; 17 } 18 19 @media (prefers-color-scheme: dark) { 20 + :root { 21 + color-scheme: dark light; 22 + --white: #000; 23 + --light: #181818; 24 + --cyan: #76c7c0; 25 + --light-gray: #333; 26 + --medium-gray: #444; 27 + --gray: #aaa; 28 + --dark: #ddd; 29 + --darker: #f4f4f4; 30 + } 31 } 32 33 html { 34 + background: var(--white); 35 + -webkit-text-size-adjust: none; 36 + font-family: var(--sans-font); 37 + font-weight: 380; 38 } 39 40 pre { 41 + font-family: var(--mono-font); 42 } 43 44 ::selection { 45 + background: var(--medium-gray); 46 + opacity: 0.3; 47 } 48 49 * { 50 + box-sizing: border-box; 51 + padding: 0; 52 + margin: 0; 53 } 54 55 body { 56 + max-width: 1000px; 57 + padding: 0 13px; 58 + margin: 40px auto; 59 } 60 61 + main, 62 + footer { 63 + font-size: 1rem; 64 + padding: 0; 65 + line-height: 160%; 66 } 67 68 + header h1, 69 + h2, 70 + h3 { 71 + font-family: var(--display-font); 72 } 73 74 h2 { 75 + font-weight: 400; 76 } 77 78 strong { 79 + font-weight: 500; 80 } 81 82 main h1 { 83 + padding: 10px 0 10px 0; 84 } 85 86 main h2 { 87 + font-size: 18px; 88 } 89 90 + main h2, 91 + h3 { 92 + padding: 20px 0 15px 0; 93 } 94 95 nav { 96 + padding: 0.4rem 0 1.5rem 0; 97 } 98 99 nav ul { 100 + padding: 0; 101 + margin: 0; 102 + list-style: none; 103 + padding-bottom: 20px; 104 } 105 106 nav ul li { 107 + padding-right: 10px; 108 + display: inline-block; 109 } 110 111 a { 112 + margin: 0; 113 + padding: 0; 114 + box-sizing: border-box; 115 + text-decoration: none; 116 + word-wrap: break-word; 117 } 118 119 a { 120 + color: var(--darker); 121 + border-bottom: 1.5px solid var(--medium-gray); 122 } 123 124 a:hover { 125 + border-bottom: 1.5px solid var(--gray); 126 } 127 128 .index { 129 + padding-top: 2em; 130 + display: grid; 131 + grid-template-columns: 6em 1fr minmax(0, 7em); 132 + grid-row-gap: 0.5em; 133 + min-width: 0; 134 } 135 136 .clone-url { 137 + padding-top: 2rem; 138 } 139 140 .clone-url pre { 141 + color: var(--dark); 142 + white-space: pre-wrap; 143 } 144 145 .desc { 146 + font-weight: normal; 147 + color: var(--gray); 148 + font-style: italic; 149 } 150 151 .tree { 152 + display: grid; 153 + grid-template-columns: 10ch auto 1fr; 154 + grid-row-gap: 0.5em; 155 + grid-column-gap: 1em; 156 + min-width: 0; 157 } 158 159 .log { 160 + display: grid; 161 + grid-template-columns: 20rem minmax(0, 1fr); 162 + grid-row-gap: 0.8em; 163 + grid-column-gap: 8rem; 164 + margin-bottom: 2em; 165 + padding-bottom: 1em; 166 + border-bottom: 1.5px solid var(--medium-gray); 167 } 168 169 .log pre { 170 + white-space: pre-wrap; 171 } 172 173 + .mode, 174 + .size { 175 + font-family: var(--mono-font); 176 } 177 .size { 178 + text-align: right; 179 } 180 181 .readme pre { 182 + white-space: pre-wrap; 183 + overflow-x: auto; 184 } 185 186 .readme { 187 + background: var(--light-gray); 188 + padding: 0.5rem; 189 } 190 191 .readme ul { 192 + padding: revert; 193 } 194 195 .readme img { 196 + max-width: 100%; 197 } 198 199 .diff { 200 + margin: 1rem 0 1rem 0; 201 + padding: 1rem 0 1rem 0; 202 + border-bottom: 1.5px solid var(--medium-gray); 203 } 204 205 .diff pre { 206 + overflow: scroll; 207 } 208 209 .diff-stat { 210 + padding: 1rem 0 1rem 0; 211 } 212 213 + .commit-hash, 214 + .commit-email { 215 + font-family: var(--mono-font); 216 } 217 218 .commit-email:before { 219 + content: "<"; 220 } 221 222 .commit-email:after { 223 + content: ">"; 224 } 225 226 .commit { 227 + margin-bottom: 1rem; 228 } 229 230 .commit pre { 231 + padding-bottom: 1rem; 232 + white-space: pre-wrap; 233 } 234 235 .diff-stat ul li { 236 + list-style: none; 237 + padding-left: 0.5em; 238 } 239 240 .diff-add { 241 + color: green; 242 } 243 244 .diff-del { 245 + color: red; 246 } 247 248 .diff-noop { 249 + color: var(--gray); 250 } 251 252 .ref { 253 + font-family: var(--sans-font); 254 + font-size: 14px; 255 + color: var(--gray); 256 + display: inline-block; 257 + padding-top: 0.7em; 258 } 259 260 .refs pre { 261 + white-space: pre-wrap; 262 + padding-bottom: 0.5rem; 263 } 264 265 .refs strong { 266 + padding-right: 1em; 267 } 268 269 .line-numbers { 270 + white-space: pre-line; 271 + -moz-user-select: -moz-none; 272 + -khtml-user-select: none; 273 + -webkit-user-select: none; 274 + -o-user-select: none; 275 + user-select: none; 276 + display: flex; 277 + float: left; 278 + flex-direction: column; 279 + margin-right: 1ch; 280 } 281 282 .file-wrapper { 283 + display: flex; 284 + flex-direction: row; 285 + grid-template-columns: 1rem minmax(0, 1fr); 286 + gap: 1rem; 287 + padding: 0.5rem; 288 + background: var(--light-gray); 289 + overflow-x: auto; 290 } 291 292 .chroma-file-wrapper { 293 + display: flex; 294 + flex-direction: row; 295 + grid-template-columns: 1rem minmax(0, 1fr); 296 + overflow-x: auto; 297 } 298 299 .file-content { 300 + background: var(--light-gray); 301 + overflow-y: hidden; 302 + overflow-x: auto; 303 } 304 305 .diff-type { 306 + color: var(--gray); 307 } 308 309 .commit-info { 310 + color: var(--gray); 311 + padding-bottom: 1.5rem; 312 + font-size: 0.85rem; 313 } 314 315 @media (max-width: 600px) { 316 + .index { 317 + grid-row-gap: 0.8em; 318 + } 319 320 + .log { 321 + grid-template-columns: 1fr; 322 + grid-row-gap: 0em; 323 + } 324 325 + .index { 326 + grid-template-columns: 1fr; 327 + grid-row-gap: 0em; 328 + } 329 330 + .index-name:not(:first-child) { 331 + padding-top: 1.5rem; 332 + } 333 334 + .commit-info:not(:last-child) { 335 + padding-bottom: 1.5rem; 336 + } 337 338 + pre { 339 + font-size: 0.8rem; 340 + } 341 }
+22 -15
legit/templates/head.html
··· 1 {{ define "head" }} 2 - <head> 3 - <meta charset="utf-8"> 4 - <meta name="viewport" content="width=device-width, initial-scale=1"> 5 - <link rel="stylesheet" href="/static/style.css" type="text/css"> 6 - <link rel="icon" type="image/png" size="32x32" href="/static/legit.png"> 7 {{ if .parent }} 8 - <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .parent }}/</title> 9 10 {{ else if .path }} 11 - <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .path }}</title> 12 {{ else if .files }} 13 <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }})</title> 14 {{ else if .commit }} 15 <title>{{ .meta.Title }} &mdash; {{ .name }}: {{ .commit.This }}</title> 16 {{ else if .branches }} 17 <title>{{ .meta.Title }} &mdash; {{ .name }}: refs</title> 18 - {{ else if .commits }} 19 - {{ if .log }} 20 <title>{{ .meta.Title }} &mdash; {{ .name }}: log</title> 21 {{ else }} 22 <title>{{ .meta.Title }} &mdash; {{ .name }}</title> 23 - {{ end }} 24 - {{ else }} 25 <title>{{ .meta.Title }}</title> 26 - {{ end }} 27 - {{ if and .servername .gomod }} 28 - <meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"> 29 {{ end }} 30 <!-- other meta tags here --> 31 - </head> 32 {{ end }}
··· 1 {{ define "head" }} 2 + <head> 3 + <meta charset="utf-8" /> 4 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 5 + <link rel="stylesheet" href="/static/style.css" type="text/css" /> 6 + <link rel="icon" type="image/png" size="32x32" href="/static/legit.png" /> 7 + <script src="https://unpkg.com/htmx.org@2.0.4"></script> 8 + <meta name="htmx-config" content='{"selfRequestsOnly":false}' /> 9 + 10 {{ if .parent }} 11 + <title> 12 + {{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .parent }}/ 13 + </title> 14 15 {{ else if .path }} 16 + <title> 17 + {{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .path }} 18 + </title> 19 {{ else if .files }} 20 <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }})</title> 21 {{ else if .commit }} 22 <title>{{ .meta.Title }} &mdash; {{ .name }}: {{ .commit.This }}</title> 23 {{ else if .branches }} 24 <title>{{ .meta.Title }} &mdash; {{ .name }}: refs</title> 25 + {{ else if .commits }} {{ if .log }} 26 <title>{{ .meta.Title }} &mdash; {{ .name }}: log</title> 27 {{ else }} 28 <title>{{ .meta.Title }} &mdash; {{ .name }}</title> 29 + {{ end }} {{ else }} 30 <title>{{ .meta.Title }}</title> 31 + {{ end }} {{ if and .servername .gomod }} 32 + <meta 33 + name="go-import" 34 + content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}" 35 + /> 36 {{ end }} 37 <!-- other meta tags here --> 38 + </head> 39 {{ end }}
+35
legit/templates/keys.html
···
··· 1 + {{ define "keys" }} 2 + <html> 3 + {{ template "head" . }} 4 + 5 + <body> 6 + <main> 7 + <form> 8 + <p> 9 + Give your key a name and paste your 10 + <strong>public</strong> key here. This is what you'll use to 11 + push to your Git repository. 12 + </p> 13 + <div id="keys"></div> 14 + <div> 15 + <input 16 + type="text" 17 + id="name" 18 + name="name" 19 + placeholder="my laptop" 20 + /> 21 + <input 22 + type="text" 23 + id="public_key" 24 + name="key" 25 + placeholder="ssh-ed25519 AAABBBHUNTER2..." 26 + /> 27 + </div> 28 + <button hx-put="/settings/keys" hx-swap="none" type="submit"> 29 + Submit 30 + </button> 31 + </form> 32 + </main> 33 + </body> 34 + </html> 35 + {{ end }}
+2 -2
legit/templates/login.html
··· 10 action="http://localhost:3000/login" 11 > 12 <p> 13 - You will be redirected to bsky.app (or your PDS) to complete 14 - login. 15 </p> 16 <div> 17 <input
··· 10 action="http://localhost:3000/login" 11 > 12 <p> 13 + You will be redirected to bsky.social (or your PDS) to 14 + complete login. 15 </p> 16 <div> 17 <input