// Command dev-stub is a throwaway fake git.sr.ht GraphQL API for local // development of compare.sr.ht. It answers the two queries compare.sr.ht's // authorizer issues (a single `user{repository}` lookup and the `me` // repository list) with fixed PUBLIC data, so you can drive the UI without a // real SourceHut instance or its internal-auth machinery. // // It ignores authentication entirely: every request — anonymous or "logged in" // — sees the same public repository, which is exactly what you want when // exercising the compare/commit pages against a directory of local bare repos. // // Usage: // // go run ./contrib/dev-stub -addr 127.0.0.1:5101 // // Then point compare.sr.ht's config at it and start the daemon: // // [git.sr.ht] // api-origin=http://127.0.0.1:5101 # dev-stub serves POST /query here // repos=/path/to/local/bare/repos # {repos}/~{owner}/{name} // // make run-dev // // The repository NAME echoed back is taken from the query variables, so any // /~owner/ you visit resolves; put a matching bare repo at // {repos}/~owner/ for gitx to read. See README.md ("Development") for the // full recipe including forging a dev login cookie. // // stdlib only, no build-time dependencies. Not meant for production. package main import ( "encoding/json" "flag" "log" "net/http" "strings" ) // repoNode is the repository shape compare.sr.ht's authorizer decodes for both // the single-repo lookup and the me.repositories list. type repoNode struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` Visibility string `json:"visibility"` } type gqlRequest struct { Query string `json:"query"` Variables map[string]any `json:"variables"` } func main() { addr := flag.String("addr", "127.0.0.1:5101", "address to listen on") flag.Parse() http.HandleFunc("/query", handleQuery) http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok\n")) }) log.Printf("dev-stub git.sr.ht GraphQL API listening on http://%s/query", *addr) log.Printf("every query resolves to a fixed PUBLIC repository (auth ignored)") if err := http.ListenAndServe(*addr, nil); err != nil { log.Fatal(err) } } func handleQuery(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "POST only", http.StatusMethodNotAllowed) return } var req gqlRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeData(w, map[string]any{}) // let the client surface a decode-shaped miss return } switch { case strings.Contains(req.Query, "repositories("): // me { repositories { results { ... } cursor } } writeData(w, map[string]any{ "me": map[string]any{ "repositories": map[string]any{ "results": []repoNode{ {ID: 1, Name: "demo", Description: "a public demo repo", Visibility: "PUBLIC"}, {ID: 2, Name: "playground", Description: "scratch space", Visibility: "UNLISTED"}, }, "cursor": nil, }, }, }) case strings.Contains(req.Query, "repository("): // user(username:$u) { repository(name:$r) { ... } } name, _ := req.Variables["r"].(string) if name == "" { name = "demo" } writeData(w, map[string]any{ "user": map[string]any{ "repository": repoNode{ ID: 1, Name: name, Description: "a public demo repo (dev-stub)", Visibility: "PUBLIC", }, }, }) default: writeData(w, map[string]any{}) } } func writeData(w http.ResponseWriter, data map[string]any) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"data": data}) }