| 1 |
|
// Command dev-stub is a throwaway fake git.sr.ht GraphQL API for local |
| 2 |
|
// development of diff.sr.ht. It answers the two queries diff.sr.ht's |
| 3 |
|
// authorizer issues (a single `user{repository}` lookup and the `me` |
| 4 |
|
// repository list) with fixed PUBLIC data, so you can drive the UI without a |
| 5 |
|
// real SourceHut instance or its internal-auth machinery. |
| 6 |
|
// |
| 7 |
|
// It ignores authentication entirely: every request — anonymous or "logged in" |
| 8 |
|
// — sees the same public repository, which is exactly what you want when |
| 9 |
|
// exercising the compare/commit pages against a directory of local bare repos. |
| 10 |
|
// |
| 11 |
|
// Usage: |
| 12 |
|
// |
| 13 |
|
// go run ./contrib/dev-stub -addr 127.0.0.1:5101 |
| 14 |
|
// |
| 15 |
|
// Then point diff.sr.ht's config at it and start the daemon: |
| 16 |
|
// |
| 17 |
|
// [git.sr.ht] |
| 18 |
|
// api-origin=http://127.0.0.1:5101 # dev-stub serves POST /query here |
| 19 |
|
// repos=/path/to/local/bare/repos # {repos}/~{owner}/{name} |
| 20 |
|
// |
| 21 |
|
// make run-dev |
| 22 |
|
// |
| 23 |
|
// The repository NAME echoed back is taken from the query variables, so any |
| 24 |
|
// /~owner/<name> you visit resolves; put a matching bare repo at |
| 25 |
|
// {repos}/~owner/<name> for gitx to read. See README.md ("Development") for the |
| 26 |
|
// full recipe including forging a dev login cookie. |
| 27 |
|
// |
| 28 |
|
// stdlib only, no build-time dependencies. Not meant for production. |
| 29 |
|
package main |
| 30 |
|
|
| 31 |
|
import ( |
| 32 |
|
"encoding/json" |
| 33 |
|
"flag" |
| 34 |
|
"log" |
| 35 |
|
"net/http" |
| 36 |
|
"strings" |
| 37 |
|
) |
| 38 |
|
|
| 39 |
|
// repoNode is the repository shape diff.sr.ht's authorizer decodes for both |
| 40 |
|
// the single-repo lookup and the me.repositories list. |
| 41 |
|
type repoNode struct { |
| 42 |
|
ID int `json:"id"` |
| 43 |
|
Name string `json:"name"` |
| 44 |
|
Description string `json:"description"` |
| 45 |
|
Visibility string `json:"visibility"` |
| 46 |
|
} |
| 47 |
|
|
| 48 |
|
type gqlRequest struct { |
| 49 |
|
Query string `json:"query"` |
| 50 |
|
Variables map[string]any `json:"variables"` |
| 51 |
|
} |
| 52 |
|
|
| 53 |
0 |
func main() { |
| 54 |
0 |
addr := flag.String("addr", "127.0.0.1:5101", "address to listen on") |
| 55 |
0 |
flag.Parse() |
| 56 |
0 |
|
| 57 |
0 |
http.HandleFunc("/query", handleQuery) |
| 58 |
0 |
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { |
| 59 |
0 |
_, _ = w.Write([]byte("ok\n")) |
| 60 |
0 |
}) |
| 61 |
|
|
| 62 |
0 |
log.Printf("dev-stub git.sr.ht GraphQL API listening on http://%s/query", *addr) |
| 63 |
0 |
log.Printf("every query resolves to a fixed PUBLIC repository (auth ignored)") |
| 64 |
0 |
if err := http.ListenAndServe(*addr, nil); err != nil { |
| 65 |
0 |
log.Fatal(err) |
| 66 |
0 |
} |
| 67 |
|
} |
| 68 |
|
|
| 69 |
0 |
func handleQuery(w http.ResponseWriter, r *http.Request) { |
| 70 |
0 |
if r.Method != http.MethodPost { |
| 71 |
0 |
http.Error(w, "POST only", http.StatusMethodNotAllowed) |
| 72 |
0 |
return |
| 73 |
0 |
} |
| 74 |
0 |
var req gqlRequest |
| 75 |
0 |
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| 76 |
0 |
writeData(w, map[string]any{}) // let the client surface a decode-shaped miss |
| 77 |
0 |
return |
| 78 |
0 |
} |
| 79 |
|
|
| 80 |
0 |
switch { |
| 81 |
0 |
case strings.Contains(req.Query, "repositories("): |
| 82 |
0 |
// me { repositories { results { ... } cursor } } |
| 83 |
0 |
writeData(w, map[string]any{ |
| 84 |
0 |
"me": map[string]any{ |
| 85 |
0 |
"repositories": map[string]any{ |
| 86 |
0 |
"results": []repoNode{ |
| 87 |
0 |
{ID: 1, Name: "demo", Description: "a public demo repo", Visibility: "PUBLIC"}, |
| 88 |
0 |
{ID: 2, Name: "playground", Description: "scratch space", Visibility: "UNLISTED"}, |
| 89 |
0 |
}, |
| 90 |
0 |
"cursor": nil, |
| 91 |
0 |
}, |
| 92 |
0 |
}, |
| 93 |
0 |
}) |
| 94 |
0 |
case strings.Contains(req.Query, "repository("): |
| 95 |
0 |
// user(username:$u) { repository(name:$r) { ... } } |
| 96 |
0 |
name, _ := req.Variables["r"].(string) |
| 97 |
0 |
if name == "" { |
| 98 |
0 |
name = "demo" |
| 99 |
0 |
} |
| 100 |
0 |
writeData(w, map[string]any{ |
| 101 |
0 |
"user": map[string]any{ |
| 102 |
0 |
"repository": repoNode{ |
| 103 |
0 |
ID: 1, |
| 104 |
0 |
Name: name, |
| 105 |
0 |
Description: "a public demo repo (dev-stub)", |
| 106 |
0 |
Visibility: "PUBLIC", |
| 107 |
0 |
}, |
| 108 |
0 |
}, |
| 109 |
0 |
}) |
| 110 |
0 |
default: |
| 111 |
0 |
writeData(w, map[string]any{}) |
| 112 |
|
} |
| 113 |
|
} |
| 114 |
|
|
| 115 |
0 |
func writeData(w http.ResponseWriter, data map[string]any) { |
| 116 |
0 |
w.Header().Set("Content-Type", "application/json") |
| 117 |
0 |
_ = json.NewEncoder(w).Encode(map[string]any{"data": data}) |
| 118 |
0 |
} |