//go:generate go run git.brut.systems/judah/rivit2html@latest docs/index.riv package main import ( "encoding/base64" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "strconv" "strings" ) var ( API_URL string API_TOKEN string REDIRECT_URL string PORT string ) type Server struct { http.ServeMux } func (sv *Server) getfile(user, repo, path string) ([]byte, error) { var ( endpoint = endpoint("/repos/%s/%s/contents/%s", user, repo, url.PathEscape(path)) ) req, err := http.NewRequest("GET", endpoint, nil) if err != nil { return nil, err } req.Header.Add("accept", "application/json") req.Header.Add("Authorization", API_TOKEN) log.Printf("GET %q", endpoint) var client http.Client res, err := client.Do(req) if err != nil { return nil, err } body, err := io.ReadAll(res.Body) if err != nil { return nil, err } if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("response had unexpected status %d - %s", res.StatusCode, string(body)) } var response struct { Encoding string `json:"encoding"` Content string `json:"content"` } err = json.Unmarshal(body, &response) if err != nil { return nil, err } if response.Encoding != "base64" { return nil, fmt.Errorf("expected base64 encoding, found %q", response.Encoding) } file, err := base64.StdEncoding.DecodeString(response.Content) if err != nil { return nil, err } return file, nil } func main() { var err error if API_URL, err = getvar("API_URL"); err != nil { log.Fatal(err) } else { if _, err := url.Parse(API_URL); err != nil { log.Fatalf("invalid API_URL %q", err) } if !strings.HasPrefix(API_URL, "http") { log.Fatalf("invalid API_URL %q - must being with http(s)://", API_URL) } } if REDIRECT_URL, err = getvar("REDIRECT_URL"); err != nil { log.Fatal(err) } else { if _, err := url.Parse(REDIRECT_URL); err != nil { log.Fatalf("invalid REDIRECT_URL %q", err) } if !strings.HasPrefix(REDIRECT_URL, "http") { log.Fatalf("invalid REDIRECT_URL %q - must begin with \"http(s)://\"", REDIRECT_URL) } } if token, err := getvar("API_TOKEN"); err != nil { log.Fatal(err) } else { API_TOKEN = "token " + token } if PORT, err = getvar("PORT"); err != nil { log.Fatal(err) } else { if _, err = strconv.ParseInt(PORT, 10, 64); err != nil { log.Fatalf("invalid port number %q", PORT) } } redirect := func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, REDIRECT_URL, http.StatusTemporaryRedirect) } sv := new(Server) sv.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { redirect(w, r) }) sv.HandleFunc("/{user}/{repo}/", func(w http.ResponseWriter, r *http.Request) { user := strings.TrimSpace(r.PathValue("user")) repo := strings.TrimSpace(r.PathValue("repo")) if len(user) == 0 || len(repo) == 0 { redirect(w, r) return } path := r.URL.Path path = strings.TrimSpace(path[strings.Index(path, repo)+len(repo):]) path = strings.TrimPrefix(path, "/") if len(path) == 0 { path = "docs/index.html" } log.Printf("fetching file content %s:%s - %q", user, repo, path) content, err := sv.getfile(user, repo, path) if err != nil { log.Println(err) redirect(w, r) return } if _, err := w.Write(content); err != nil { log.Println(err) redirect(w, r) return } }) addr := fmt.Sprintf(":%s", PORT) log.Printf("Listening at %s", addr) if err := http.ListenAndServe(addr, sv); err != nil { log.Fatal(err) } } func getvar(name string) (string, error) { envvar := strings.TrimSpace(os.Getenv(name)) if len(envvar) == 0 { return "", fmt.Errorf("required environment variable %q was not set", name) } return envvar, nil } func endpoint(endpoint string, args ...any) string { url, _ := url.JoinPath(API_URL, "api/v1", fmt.Sprintf(endpoint, args...)) return url }