coverage~bigbes/sr-ht-dolt3523280cweb/handlers_ready.go

Coverage
79.2% 19/24 statements
Δ
+0.0
Blob
a8eda0e
Uncovered L37-L41L51L64-L65
1 package web
2
3 import (
4 "context"
5 "fmt"
6 "log/slog"
7 "net/http"
8
9 "go.bigb.es/auxilia/scribe"
10
11 "sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
12
13 "sourcecraft.dev/bigbes/sr-ht-dolt/beads"
14 "sourcecraft.dev/bigbes/sr-ht-dolt/core"
15 )
16
17 // handleReady renders the cross-database ready page: for every database this
18 // caller may browse, the issues that are open, unblocked and not scaffolding,
19 // grouped by database.
20 //
21 // It is the one page here that reads more than one database, and the three
22 // things that keeps affordable live in beads: the head-hash gate over the app's
23 // projection cache, the 60-second TTL, and the ceiling on how many databases one
24 // request opens. This handler's own job is the other half — deciding which
25 // databases the caller may see at all, and doing it before a single store is
26 // opened.
27 //
28 // Visibility is enumerated with ListReposForViewer (the listing rule) and then
29 // re-asked per database with core.Allowed/OpBrowse (the access rule). A database
30 // the caller may not browse is simply absent: not a 403, not a count, not a
31 // group with a hidden name — nothing on the page may hint that it exists.
32 18 func (a *app) handleReady(w http.ResponseWriter, r *http.Request) {
33 18 _, caller := callerOf(r.Context())
34 18
35 18 repos, err := a.cfg.Repos.ListReposForViewer(r.Context(), caller)
36 18 if err != nil {
37 0 slog.Error("listing databases for the ready page failed",
38 0 "component", "web", scribe.Err(err))
39 0 a.fail(w, r, http.StatusInternalServerError, "")
40 0 return
41 0 }
42
43 // The disk path never reaches beads: it is this service's arrangement of its
44 // own storage, and the aggregation addresses a database by the identity the
45 // cache is keyed on. The opener closes over the map, so a database that was
46 // filtered out of the candidate list has no path to be opened by.
47 18 paths := make(map[int]string, len(repos))
48 18 dbs := make([]beads.ReadyDatabase, 0, len(repos))
49 101 for _, repo := range repos {
50 101 if !core.Allowed(caller, repo, a.effectiveACL(r, caller, repo), core.OpBrowse) {
51 0 continue
52 }
53 101 paths[repo.ID] = repo.Path
54 101 dbs = append(dbs, beads.ReadyDatabase{
55 101 ID: repo.ID,
56 101 OwnerName: repo.OwnerName,
57 101 Name: repo.Name,
58 101 })
59 }
60
61 98 open := func(ctx context.Context, d beads.ReadyDatabase) (beads.ReadySession, error) {
62 98 path, ok := paths[d.ID]
63 98 if !ok {
64 0 return nil, fmt.Errorf("web: no store path for database %s", d.Slug())
65 0 }
66 98 return a.cfg.Browse.Open(ctx, path)
67 }
68
69 18 data := beads.ReadyAcross(r.Context(), dbs, open, a.ready,
70 18 beads.ParseReadyFilter(r.URL.Query()), timeNow())
71 18 data.Query = r.URL.Query()
72 18
73 18 // A store that cannot be read is a fact about this deployment, and it belongs
74 18 // in the log with its error. The page says only how many databases it could
75 18 // not read: echoing a browse error into the response is how a store path and
76 18 // a dolt internal end up in somebody's browser (sr-ht-dolt-7ta).
77 18 for _, f := range data.Failed {
78 1 slog.Warn("reading a database for the ready page failed",
79 1 "component", "web", "database", f.Database.Slug(), scribe.Err(f.Err))
80 1 }
81
82 18 view := struct {
83 18 chrome.Page
84 18 Data *beads.ReadyView
85 18 }{
86 18 Page: a.page(r, "Ready — "+serviceName),
87 18 Data: data,
88 18 }
89 18 a.render(w, http.StatusOK, "ready", view)
90 }