~bigbes/sr-ht-dolt

sr-ht-dolt/web/web_test.go -rw-r--r-- 59.7 KiB
cd0b0c0f — Eugene Blikh 3 days ago
web: rename a database from its settings page

A database's name lives in two places — the metadata row and the on-disk
store directory its path names — so a rename has to move both. The order
is creation's: the row first, where a name already taken is refused by the
unique index before anything on disk changes, then the store, then the
handle the remotesapi memoized under the old path. A store that will not
move rolls the row back, so the two halves never disagree about where a
database lives; only a failed rollback is escalated to a human, because
it is the one outcome no later request can repair.

storage.MoveStore is one os.Rename behind the containment guard DeleteStore
already used, extracted here as containedPath. It refuses an occupied
destination outright: os.Rename over an empty directory would succeed and
swallow it.

No redirect is left behind — the old address simply stops resolving, as on
git.sr.ht — so an existing clone needs its remote replaced, and a companion
of a git repository is re-provisioned under its old name by the next push
to that repository. The README's quickstart now says both.
93e69910 — Eugene Blikh 3 days ago
storage: create databases empty so the first push needs no --force

Every automatic creation path wrote an "Initialize data repository"
commit through WriteEmptyRepo, and that commit is history. dolt decides
fast-forward on the client (actions.CanFastForward over the remotesapi),
so the server cannot forgive the collision: pushing a database that has a
root commit of its own — a beads tracker, anything grown locally — was
rejected as a non-fast-forward and could only land with --force. That is
the whole reason the companion-database recipe starts with a forced push.

push-to-create already provisioned an empty store for this exact reason.
Give the other two paths the same default: /internal/repos, whose caller
is git.sr.ht's post-update hook and therefore fires before its user has
ever pushed, now always provisions empty, and the web form does unless
its new "initialize with an empty commit" checkbox is ticked. The
checkbox buys what an empty store cannot offer — a database that can be
cloned before anything is pushed to it, since dolt refuses a store with
no commits as "contains no Dolt data".

Which is also why the overview of a database with no branches now teaches
push rather than clone: the clone box there quoted a command that could
not work. A store that fails to open is deliberately not treated as
empty — an unreadable database must not be advertised as a fresh one.
35875ff7 — Eugene Blikh 5 days ago
web: stop printing store paths to the owner
11c622f3 — Eugene Blikh 5 days ago
web: stop printing browse errors to the reader

The overview rendered the browse layer's own error text into the page, under
"Could not read history: " — dolt internals and the store's path on our disk,
which nothing else on this surface discloses and which a reader can do nothing
with.

The page now carries a fixed sentence and the reason goes to the log with the
database id (slog + scribe.Err). The view field is a bool rather than a message,
so no error string can reach the template by being assigned to it later. The
empty-database state the page already reported ("No commits.") is untouched:
that is a fact about the database, not a failure.
bee20506 — Eugene Blikh 5 days ago
web: tell a database outage from a missing database

loadRepoForBrowse turned any error from GetRepoByOwnerAndName into a 404, so a
metadata store that could not answer told every reader on the instance that
their database does not exist — for as long as Postgres was down, in the voice
reserved for "there is nothing here". loadRepoForAdmin did the same on the
settings page.

Both classify through repoLookupFailed now: db.ErrNotFound keeps the 404, and
it stays the same 404 the visibility rule renders for a PRIVATE database the
caller may not see, so a masked database and a missing one remain
indistinguishable. Anything else is a 500 through the shared error page, with
the cause logged (slog + scribe.Err) and never rendered.
dc918296 — Eugene Blikh 5 days ago
web: link issue ids to the tracker that owns them
043b0fd7 — Eugene Blikh 5 days ago
web: answer what is ready across every tracker
231b7764 — Eugene Blikh 5 days ago
web: give bd memories their own view
64592988 — Eugene Blikh 5 days ago
web: show how fresh a beads view is

handleView now reads the head commit of the rendered ref onto the envelope
(Head *browse.CommitInfo), and the beads and milestones headers carry a
shared beadsHead partial: <branch> · last commit <relative> · <short hash>,
the hash linking to the commit page and the exact stamp in the title.

The read is decoration on top of an answer: a database with no commits, or
a Log that fails, renders the page without the line rather than 500ing.

The relative time is a new ago func rather than chrome's reltime — it is
past-facing (clock skew reads as "just now", never "in 3 minutes") and
reads a package clock a test can pin.
ed5a65bc — Eugene Blikh 9 days ago
chrome: link the favicon through the shared head partial

The icon href was a literal in the layout, so a build with no static tree — a
test, a binary run out of a working copy — asked for /static/logo.svg once per
page and got a 404 each time. It is the chrome's FaviconHref now: our own logo
when this build ships one, checked the way the stylesheet already was, and
NewService's built-in data: URI when it does not. Both <link>s come from
srht-head-links, so the guard against <link href=""> is written once.

Also pins what the repo-list partial's optional fields do for a service that has
no timestamp in its schema: a card with no muted footer, not "0001-01-01".
9660c720 — Eugene Blikh 9 days ago
pages: read forms through FormValues

Every mutation on this surface read its fields with r.PostFormValue after a bare
r.ParseForm. PostFormValue was already the right half — r.Form would merge the
query string into the body, which would let a mutation be driven from a URL
somebody was linked to, and that request is exactly the one the same-origin
guard sees nothing wrong with — but the body was bounded only by net/http's
10 MiB default, on pages anyone can reach. FormValues is both properties in one
call, and the values are passed down explicitly instead of each sub-handler
reaching back into the request.
0b1e119a — Eugene Blikh 9 days ago
chimw: take the chi helpers from ecore

Three things this service did not have. Read routes are registered for HEAD as
well as GET, so `curl -I` and every uptime probe stop being answered with a 405
and a kilobyte of error page; the twin shares the handler, so it cannot say 200
where the GET says 404. chi's two routing failures now render our own page
instead of net/http's plain text — an unrouted URL here was the one refusal on
the instance that did not look like the service it came from. And the request
line is a slog record rather than chi's colourised line on stdout, which was the
only line this daemon emitted that was neither structured nor on stderr.

chi's own middleware package loses the chimw alias to the package written
against; it is chimiddleware now, as ecore's package doc asks.
84c33df2 — Eugene Blikh 9 days ago
test: build the fixture config and the keyset with ecoretest

The hand-built ini in web_test.go, the random fernet key in authn's
TestMain and the same seeding copied into the git-hook test are one call
to ecoretest now. The keys are fixed rather than generated on purpose:
they secure nothing inside a test process, and a constant keyset is what
lets two packages of this service initialise without the second rotating
what the first sealed with.

The synthetic instance runs in production mode, so the environment
banner is off in tests unless one asks for it.
0af9ccda — Eugene Blikh 9 days ago
web: guard mutations with ecore's csrf, cache and panic middleware

checkSameOrigin and originMatches are gone, and with them the three
per-handler calls that had to be remembered: csrf.Require sits over the
whole browser group, so the mutating route added next year is guarded by
being routed. The internal provisioning endpoint stays outside that
group deliberately — it is a service-to-service POST with no Origin and
its own network-key guard.

middleware.PrivateCache marks every page as one no cache may reuse for
the next viewer, which is only correct because the static handler opts
out per asset once it has found the file. RecoverPanics answers a panic
with the error page, and one that arrives after the response has started
by dropping the connection rather than appending an error to half a
document.
17fa0f16 — Eugene Blikh 9 days ago
web: render through ecore's pages and its error page

The page list, the per-page parse loop and the view-template loop are
gone: pages discovers every file in templates/, so a page is registered
by existing, and one that defines no content block is refused at startup
rather than served as chrome around a hole. 404.html and 403.html are
gone with them — the shared error page carries the same body, and its
prose is deliberately the same for a database that is not there and one
the viewer may not see.

The renderer that replaced them closes a leak: the old one wrote
"template render error: "+err.Error() into the response body, handing
the viewer template names and field paths. pages answers a fixed
sentence and returns the error for the log.

reltime and abstime come from chrome.Funcs now; ours called every future
instant "just now", where the shared one says "in 3 weeks".
f88846ac — Eugene Blikh 9 days ago
web: serve the static tree through ecore's assets

discoverStyleHref and the bare http.FileServer are sr-ht-ecore's assets
package now: one hashed-name pattern, the cache policy the hash implies
(immutable for a content-addressed name, an hour for the rest), and a
refusal to publish a directory listing of the build. The unhashed
fallback survives, but only when static/main.css is really there — an
href to a file this deployment does not ship is a 404 per page load,
which is what an empty Resolve exists to avoid.
bf7897cd — Eugene Blikh 9 days ago
web: draw the chrome from sr-ht-ecore

The brand, the service switcher, the login block, the environment banner
and the database listing were a local port of core.sr.ht's nav — one of
five such ports on this instance, and they had already drifted. They are
now sourcecraft.dev/bigbes/sr-ht-ecore/chrome, the one copy every custom
service draws from.

Deleted: web/chrome.go entire (navEntry, networkOrder, networkExcluded,
buildNetwork, basePage, loginURL, logoutURL), templates/nav.html,
templates/icons/circle.svg (ecore inlines the identical SVG), the
repoList partial, and the local dict/shortHash duplicates.

Added: one chrome.Service built in newApp from our config section with
the hashed stylesheet href set on it, a chrome.Page per request through
app.page, chrome.Attach on every template set, and chrome.Funcs as the
base of the funcmap. Handlers embed chrome.Page in their view structs
instead of copying its fields; the row browser sets ContainerClass to
container-fluid, since its column count is the table's and not ours.

Three behaviour changes come with ecore's policy, all deliberate: the
profile link now prefers hub's ~username page when hub.sr.ht is
configured (it was always meta's /profile), the brand carries a fixed
15rem min-width so the switcher starts at the same x on every service,
and a binary built without a stylesheet renders bare rather than linking
an empty href. The nav test went with the code it tested — ordering,
exclusions and login URLs are ecore's to cover — and what replaced it
asserts only what is ours: that pages are drawn through the chrome at
all, and that the row browser is full-bleed.

The auth path is untouched: a foreign bearer token is still accepted as
a meta.sr.ht PAT.
4520d5cf — Eugene Blikh 30 days ago
feat(web): pluggable alternative-view registry for repositories

A repository can now expose specialized, read-only "views" chosen by the
shape of its tables, while the generic table browser stays available as a
fallback. A View fingerprints the tables (Applies), builds a model from
the browse layer (Build), and renders its own template; views register at
init time via RegisterView and are dispatched by slug at
/~user/db/view/{slug}. The overview shows a tab per applicable view.

The template loader parses each registered view's template with the
shared chrome, so a new view plugs in by adding two files (its .go with
init()+RegisterView and its .html) plus nothing else — no edits to the
registry, router, loader, or handlers. Build receives the request query
values so a view can offer sub-modes (e.g. a detail pane). No SQL engine
is involved; views read through the existing BrowseSession surface.
2dfab043 — Eugene Blikh 30 days ago
rename module to sourcecraft.dev/bigbes/sr-ht-dolt; depend on sourcecraft sr-ht-core
944a35e9 — Eugene Blikh 30 days ago
web: router, handlers, and sourcehut chrome

Chi route table per plan, SourceHut chrome port (nav/layout from
core.sr.ht Bootstrap structure), dual-flow clone box, dolt-key
association page with hash-fragment prefill, same-origin CSRF checks.
Handlers depend on small injected interfaces (StoreManager, RepoStore,
BrowseOpener, UserResolver) with production adapters included.