docs.brut.systems/main.go

217 lines
4.8 KiB
Go

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