package web import ( "errors" "net/http" "strconv" "github.com/go-chi/chi/v5" "sourcecraft.dev/bigbes/sr-ht-dolt/browse" "sourcecraft.dev/bigbes/sr-ht-dolt/core" ) const ( // logPageSize is the number of commits per /log page. logPageSize = 20 // rowsPageSize is the number of rows per /table page. rowsPageSize = 50 ) // openBrowse opens a browse session for repo, writing a 500 and returning // ok=false on failure. The caller must Close the returned session. func (a *app) openBrowse(w http.ResponseWriter, r *http.Request, repo *core.Repo) (BrowseSession, bool) { sess, err := a.cfg.Browse.Open(r.Context(), repo.Path) if err != nil { http.Error(w, "failed to open database", http.StatusInternalServerError) return nil, false } return sess, true } // handleLog renders a paginated commit log for a branch (or ref). Pages after // the first are reached via ?from= (the nextHash from the prior page); the // active branch is chosen with ?branch=. func (a *app) handleLog(w http.ResponseWriter, r *http.Request) { repo, _, _, ok := a.loadRepoForBrowse(w, r) if !ok { return } sess, ok := a.openBrowse(w, r, repo) if !ok { return } defer sess.Close() branches, err := sess.Branches(r.Context()) if err != nil { http.Error(w, "failed to list branches", http.StatusInternalServerError) return } branch := r.URL.Query().Get("branch") if branch == "" { branch = browse.DefaultBranch(branches) } fromHash := r.URL.Query().Get("from") commits, nextHash, err := sess.Log(r.Context(), branch, fromHash, logPageSize) if err != nil { if errors.Is(err, browse.ErrRefNotFound) { a.notFound(w, r) return } http.Error(w, "failed to read log", http.StatusInternalServerError) return } view := struct { basePage Repo *core.Repo Branches []browse.Branch Branch string Commits []browse.CommitInfo NextHash string }{ basePage: a.newBasePage(r, "Log — "+repo.OwnerName+"/"+repo.Name), Repo: repo, Branches: branches, Branch: branch, Commits: commits, NextHash: nextHash, } a.render(w, http.StatusOK, "log.html", view) } // handleCommit renders a single commit's per-table diff summary. func (a *app) handleCommit(w http.ResponseWriter, r *http.Request) { repo, _, _, ok := a.loadRepoForBrowse(w, r) if !ok { return } sess, ok := a.openBrowse(w, r, repo) if !ok { return } defer sess.Close() hash := chi.URLParam(r, "hash") summary, err := sess.CommitSummary(r.Context(), hash) if err != nil { if errors.Is(err, browse.ErrRefNotFound) { a.notFound(w, r) return } http.Error(w, "failed to read commit", http.StatusInternalServerError) return } view := struct { basePage Repo *core.Repo Summary *browse.CommitDiff }{ basePage: a.newBasePage(r, "Commit "+shortHash(hash)+" — "+repo.OwnerName+"/"+repo.Name), Repo: repo, Summary: summary, } a.render(w, http.StatusOK, "commit.html", view) } // handleTree renders the tables (with schemas) present at a ref. func (a *app) handleTree(w http.ResponseWriter, r *http.Request) { repo, _, _, ok := a.loadRepoForBrowse(w, r) if !ok { return } sess, ok := a.openBrowse(w, r, repo) if !ok { return } defer sess.Close() ref := chi.URLParam(r, "ref") tables, err := sess.Tables(r.Context(), ref) if err != nil { if errors.Is(err, browse.ErrRefNotFound) { a.notFound(w, r) return } http.Error(w, "failed to read tables", http.StatusInternalServerError) return } view := struct { basePage Repo *core.Repo Ref string Tables []browse.TableInfo }{ basePage: a.newBasePage(r, "Tree "+ref+" — "+repo.OwnerName+"/"+repo.Name), Repo: repo, Ref: ref, Tables: tables, } a.render(w, http.StatusOK, "tree.html", view) } // handleTable renders a table's schema and a paginated page of its rows. Pages // are selected with ?page=N (1-based). func (a *app) handleTable(w http.ResponseWriter, r *http.Request) { repo, _, _, ok := a.loadRepoForBrowse(w, r) if !ok { return } sess, ok := a.openBrowse(w, r, repo) if !ok { return } defer sess.Close() ref := chi.URLParam(r, "ref") table := chi.URLParam(r, "table") page := 1 if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil && p > 1 { page = p } offset := (page - 1) * rowsPageSize rows, err := sess.Rows(r.Context(), ref, table, offset, rowsPageSize) if err != nil { if errors.Is(err, browse.ErrRefNotFound) || errors.Is(err, browse.ErrTableNotFound) { a.notFound(w, r) return } http.Error(w, "failed to read rows", http.StatusInternalServerError) return } totalPages := (rows.Total + rowsPageSize - 1) / rowsPageSize if totalPages < 1 { totalPages = 1 } view := struct { basePage Repo *core.Repo Ref string Table string Rows *browse.RowPage Page int TotalPages int HasPrev bool HasNext bool }{ basePage: a.newBasePage(r, table+" — "+repo.OwnerName+"/"+repo.Name), Repo: repo, Ref: ref, Table: table, Rows: rows, Page: page, TotalPages: totalPages, HasPrev: page > 1, HasNext: page < totalPages, } a.render(w, http.StatusOK, "table.html", view) }