2024-01-15 22:26:51 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2024-01-20 21:49:38 +00:00
|
|
|
"strings"
|
2024-01-15 22:26:51 +00:00
|
|
|
|
|
|
|
"go.jolheiser.com/ugit/assets"
|
|
|
|
"go.jolheiser.com/ugit/internal/git"
|
|
|
|
"go.jolheiser.com/ugit/internal/html"
|
|
|
|
"go.jolheiser.com/ugit/internal/http/httperr"
|
|
|
|
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Server is the container struct for the HTTP server
|
|
|
|
type Server struct {
|
|
|
|
port int
|
|
|
|
mux *chi.Mux
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListenAndServe simply wraps http.ListenAndServe to contain the functionality here
|
|
|
|
func (s Server) ListenAndServe() error {
|
|
|
|
return http.ListenAndServe(fmt.Sprintf("localhost:%d", s.port), s.mux)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Settings is the configuration for the HTTP server
|
|
|
|
type Settings struct {
|
|
|
|
Title string
|
|
|
|
Description string
|
|
|
|
CloneURL string
|
|
|
|
Port int
|
|
|
|
RepoDir string
|
|
|
|
Profile Profile
|
|
|
|
}
|
|
|
|
|
|
|
|
// Profile is the index profile
|
|
|
|
type Profile struct {
|
|
|
|
Username string
|
|
|
|
Email string
|
|
|
|
Links []Link
|
|
|
|
}
|
|
|
|
|
|
|
|
// Link is a profile link
|
|
|
|
type Link struct {
|
|
|
|
Name string
|
|
|
|
URL string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Settings) goGet(repo string) string {
|
|
|
|
u, _ := url.Parse(s.CloneURL)
|
|
|
|
return fmt.Sprintf(`<!DOCTYPE html><title>%[1]s</title><meta name="go-import" content="%[2]s/%[1]s git %[3]s/%[1]s.git"><meta name="go-source" content="%[2]s/%[1]s _ %[3]s/%[1]s/tree/main{/dir}/{file}#L{line}">`, repo, u.Hostname(), s.CloneURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new HTTP server
|
|
|
|
func New(settings Settings) Server {
|
|
|
|
mux := chi.NewMux()
|
|
|
|
|
|
|
|
mux.Use(middleware.Logger)
|
|
|
|
mux.Use(middleware.Recoverer)
|
|
|
|
|
|
|
|
rh := repoHandler{s: settings}
|
|
|
|
mux.Route("/", func(r chi.Router) {
|
|
|
|
r.Get("/", httperr.Handler(rh.index))
|
|
|
|
r.Route("/{repo}", func(r chi.Router) {
|
2024-01-19 05:00:13 +00:00
|
|
|
r.Use(rh.repoMiddleware)
|
2024-01-15 22:26:51 +00:00
|
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
2024-01-20 21:49:38 +00:00
|
|
|
repo := r.Context().Value(repoCtxKey).(*git.Repo)
|
2024-01-15 22:26:51 +00:00
|
|
|
if r.URL.Query().Has("go-get") {
|
2024-01-20 21:49:38 +00:00
|
|
|
w.Write([]byte(settings.goGet(repo.Name())))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if strings.HasSuffix(chi.URLParam(r, "repo"), ".git") {
|
|
|
|
http.Redirect(w, r, "/"+repo.Name(), http.StatusFound)
|
2024-01-15 22:26:51 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
rh.repoTree("", "").ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
r.Get("/tree/{ref}/*", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
rh.repoTree(chi.URLParam(r, "ref"), chi.URLParam(r, "*")).ServeHTTP(w, r)
|
|
|
|
})
|
2024-01-16 03:11:52 +00:00
|
|
|
r.Get("/refs", httperr.Handler(rh.repoRefs))
|
2024-01-16 04:54:43 +00:00
|
|
|
r.Get("/log/{ref}", httperr.Handler(rh.repoLog))
|
2024-01-17 03:37:25 +00:00
|
|
|
r.Get("/commit/{commit}", httperr.Handler(rh.repoCommit))
|
|
|
|
r.Get("/commit/{commit}.patch", httperr.Handler(rh.repoPatch))
|
2024-03-01 17:58:05 +00:00
|
|
|
r.Get("/search", httperr.Handler(rh.repoSearch))
|
2024-01-20 21:49:38 +00:00
|
|
|
|
|
|
|
// Protocol
|
|
|
|
r.Get("/info/refs", httperr.Handler(rh.infoRefs))
|
|
|
|
r.Post("/git-upload-pack", httperr.Handler(rh.uploadPack))
|
2024-01-15 22:26:51 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
mux.Route("/_", func(r chi.Router) {
|
|
|
|
r.Get("/favicon.svg", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
|
|
w.Write(assets.LogoIcon)
|
|
|
|
})
|
|
|
|
r.Get("/tailwind.css", html.TailwindHandler)
|
|
|
|
})
|
|
|
|
|
|
|
|
return Server{mux: mux, port: settings.Port}
|
|
|
|
}
|
|
|
|
|
|
|
|
type repoHandler struct {
|
|
|
|
s Settings
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rh repoHandler) baseContext() html.BaseContext {
|
|
|
|
return html.BaseContext{
|
|
|
|
Title: rh.s.Title,
|
|
|
|
Description: rh.s.Description,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rh repoHandler) repoHeaderContext(repo *git.Repo, r *http.Request) html.RepoHeaderComponentContext {
|
2024-01-16 03:11:52 +00:00
|
|
|
ref := chi.URLParam(r, "ref")
|
|
|
|
if ref == "" {
|
|
|
|
ref, _ = repo.DefaultBranch()
|
|
|
|
}
|
2024-01-15 22:26:51 +00:00
|
|
|
return html.RepoHeaderComponentContext{
|
|
|
|
Description: repo.Meta.Description,
|
|
|
|
Name: chi.URLParam(r, "repo"),
|
2024-01-16 03:11:52 +00:00
|
|
|
Ref: ref,
|
2024-01-20 02:34:27 +00:00
|
|
|
CloneURL: rh.s.CloneURL,
|
2024-01-15 22:26:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NoopLogger is a no-op logging middleware
|
|
|
|
func NoopLogger(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|