//go:generate go run git.brut.systems/judah/rivit2html@latest docs/index.riv //go:generate go run git.brut.systems/judah/rivit2html@latest docs/main.riv docs/main.go 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 ) func main() { var err error if API_URL, err = GetEnvironmentVariable("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 = GetEnvironmentVariable("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 := GetEnvironmentVariable("API_TOKEN"); err != nil { log.Fatal(err) } else { API_TOKEN = "token " + token } if PORT, err = GetEnvironmentVariable("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(http.ServeMux) 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" goto skip_override } { body, status, err := ApiGet(FormatEndpoint("/repos/%s/%s/contents/docs", user, repo)) if err != nil { log.Printf("failed to fetch docs directory in %s:%s", user, repo) goto skip_override } if status != http.StatusOK { goto skip_override } var ents []struct { Name string `json:"name"` Path string `json:"path"` Type string `json:"type"` } if err := json.Unmarshal(body, &ents); err != nil { log.Printf("failed to fetch contents of docs directory in %s:%s - %s", user, repo, err) } for _, ent := range ents { if ent.Type != "file" { continue } if ent.Name == path { path = ent.Path break } } } skip_override: log.Printf("fetching file content %s:%s - %q", user, repo, path) content, err := FetchFile(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 FetchFile(user, repo, path string) ([]byte, error) { api_endpoint := FormatEndpoint("/repos/%s/%s/contents/%s", user, repo, url.PathEscape(path)) body, status, err := ApiGet(api_endpoint) if err != nil { return nil, err } if status != http.StatusOK { return nil, fmt.Errorf("unexpected response %d - %s", status, 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 ApiGet(endpoint string) ([]byte, int, error) { req, err := http.NewRequest("GET", endpoint, nil) if err != nil { return nil, -1, err } req.Header.Add("accept", "application/json") req.Header.Add("Authorization", API_TOKEN) var client http.Client log.Printf("GET %q", endpoint) res, err := client.Do(req) if err != nil { return nil, -1, err } body, err := io.ReadAll(res.Body) if err != nil { return nil, -1, err } return body, res.StatusCode, nil } func GetEnvironmentVariable(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 FormatEndpoint(endpoint string, args ...any) string { url, _ := url.JoinPath(API_URL, "api/v1", fmt.Sprintf(endpoint, args...)) return url }