1package web
 2
 3import (
 4	"net/http"
 5	"net/url"
 6	"path"
 7	"text/template"
 8
 9	"github.com/charmbracelet/log/v2"
10	"github.com/charmbracelet/soft-serve/pkg/backend"
11	"github.com/charmbracelet/soft-serve/pkg/config"
12	"github.com/charmbracelet/soft-serve/pkg/utils"
13	"github.com/gorilla/mux"
14	"github.com/prometheus/client_golang/prometheus"
15	"github.com/prometheus/client_golang/prometheus/promauto"
16)
17
18var goGetCounter = promauto.NewCounterVec(prometheus.CounterOpts{
19	Namespace: "soft_serve",
20	Subsystem: "http",
21	Name:      "go_get_total",
22	Help:      "The total number of go get requests",
23}, []string{"repo"})
24
25var repoIndexHTMLTpl = template.Must(template.New("index").Parse(`<!DOCTYPE html>
26<html lang="en">
27<head>
28    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
29    <meta http-equiv="refresh" content="0; url=https://godoc.org/{{ .ImportRoot }}/{{.Repo}}">
30    <meta name="go-import" content="{{ .ImportRoot }}/{{ .Repo }} git {{ .Config.HTTP.PublicURL }}/{{ .Repo }}.git">
31</head>
32<body>
33Redirecting to docs at <a href="https://godoc.org/{{ .ImportRoot }}/{{ .Repo }}">godoc.org/{{ .ImportRoot }}/{{ .Repo }}</a>...
34</body>
35</html>
36`))
37
38// GoGetHandler handles go get requests.
39func GoGetHandler(w http.ResponseWriter, r *http.Request) {
40	ctx := r.Context()
41	cfg := config.FromContext(ctx)
42	be := backend.FromContext(ctx)
43	logger := log.FromContext(ctx)
44	repo := mux.Vars(r)["repo"]
45
46	// Handle go get requests.
47	// This handler is only reached when ?go-get=1 is present due to route matcher.
48	//
49	// Always return a 200 status code, even if the repo path doesn't exist.
50	// It will try to find the repo by walking up the path until it finds one.
51	// If it can't find one, it will return a 404.
52	//
53	// https://golang.org/cmd/go/#hdr-Remote_import_paths
54	// https://go.dev/ref/mod#vcs-branch
55
56	importRoot, err := url.Parse(cfg.HTTP.PublicURL)
57	if err != nil {
58		http.Error(w, err.Error(), http.StatusInternalServerError)
59		return
60	}
61
62	// find the repo
63	for {
64		if _, err := be.Repository(ctx, repo); err == nil {
65			break
66		}
67
68		if repo == "" || repo == "." || repo == "/" {
69			renderNotFound(w, r)
70			return
71		}
72
73		repo = path.Dir(repo)
74	}
75
76	if err := repoIndexHTMLTpl.Execute(w, struct {
77		Repo       string
78		Config     *config.Config
79		ImportRoot string
80	}{
81		Repo:       utils.SanitizeRepo(repo),
82		Config:     cfg,
83		ImportRoot: importRoot.Host,
84	}); err != nil {
85		logger.Debug("failed to render go get template", "err", err)
86		renderInternalServerError(w, r)
87		return
88	}
89
90	goGetCounter.WithLabelValues(repo).Inc()
91}