docs.brut.systems/main.go
2026-01-28 16:04:56 -07:00

181 lines
3.8 KiB
Go

//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
}