// Command dev-stub is a throwaway fake git.sr.ht GraphQL API for local
// development of diff.sr.ht. It answers the two queries diff.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 diff.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/<name> you visit resolves; put a matching bare repo at
// {repos}/~owner/<name> 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 diff.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})
}