refactor(web): extract shared fields from structs

Amolith and Crush created

Consolidates common data fields across web UI pages into reusable
BaseData, RepoBaseData, and PaginationData structs to reduce code
duplication and improve maintainability.

Implements: bug-7dfe278
Co-authored-by: Crush <crush@charm.land>

Change summary

pkg/web/webui.go          | 22 ++++++++++++++++++
pkg/web/webui_about.go    |  9 ++++---
pkg/web/webui_blob.go     | 49 ++++++++++++++++++++--------------------
pkg/web/webui_branches.go | 36 +++++++++++++++---------------
pkg/web/webui_commit.go   | 29 ++++++++++++-----------
pkg/web/webui_commits.go  | 40 ++++++++++++++++----------------
pkg/web/webui_home.go     | 24 ++++++++++----------
pkg/web/webui_overview.go | 33 ++++++++++++++-------------
pkg/web/webui_tags.go     | 44 ++++++++++++++++++------------------
pkg/web/webui_tree.go     | 33 ++++++++++++++-------------
10 files changed, 173 insertions(+), 146 deletions(-)

Detailed changes

pkg/web/webui.go 🔗

@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"github.com/charmbracelet/log/v2"
+	"github.com/charmbracelet/soft-serve/pkg/proto"
 	"github.com/gorilla/mux"
 )
 
@@ -35,6 +36,27 @@ const (
 	defaultBranchesPerPage = 20
 )
 
+// BaseData contains common fields for all web UI pages.
+type BaseData struct {
+	ServerName string
+	ActiveTab  string
+}
+
+// RepoBaseData contains common fields for repository-specific pages.
+type RepoBaseData struct {
+	BaseData
+	Repo          proto.Repository
+	DefaultBranch string
+}
+
+// PaginationData contains common fields for paginated views.
+type PaginationData struct {
+	Page        int
+	TotalPages  int
+	HasPrevPage bool
+	HasNextPage bool
+}
+
 // templateFuncs defines template helper functions available in HTML templates.
 // Functions include: splitPath (split path into components), joinPath (join path components),
 // parentPath (get parent directory), shortHash (truncate commit hash), formatDate (format timestamp),

pkg/web/webui_about.go 🔗

@@ -10,9 +10,8 @@ import (
 )
 
 type AboutData struct {
+	BaseData
 	ReadmeHTML template.HTML
-	ActiveTab  string
-	ServerName string
 }
 
 func about(w http.ResponseWriter, r *http.Request) {
@@ -35,9 +34,11 @@ func about(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := AboutData{
+		BaseData: BaseData{
+			ServerName: cfg.Name,
+			ActiveTab:  "about",
+		},
 		ReadmeHTML: readmeHTML,
-		ActiveTab:  "about",
-		ServerName: cfg.Name,
 	}
 
 	renderHTML(w, "about.html", data)

pkg/web/webui_blob.go 🔗

@@ -19,18 +19,15 @@ import (
 
 // BlobData contains data for rendering file content view.
 type BlobData struct {
-	Repo          proto.Repository
-	DefaultBranch string
-	Ref           string
-	Path          string
-	Content       string
-	RenderedHTML  template.HTML
-	IsBinary      bool
-	IsMarkdown    bool
-	ShowSource    bool
-	ActiveTab     string
-	ServerName    string
-	IsCommitHash  bool
+	RepoBaseData
+	Ref          string
+	Path         string
+	Content      string
+	RenderedHTML template.HTML
+	IsBinary     bool
+	IsMarkdown   bool
+	ShowSource   bool
+	IsCommitHash bool
 }
 
 // repoBlob handles file content view.
@@ -102,18 +99,22 @@ func repoBlob(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := BlobData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
-		Ref:           ref,
-		Path:          path,
-		Content:       string(content),
-		RenderedHTML:  renderedHTML,
-		IsBinary:      isBinaryContent(content),
-		IsMarkdown:    isMarkdown,
-		ShowSource:    showSource,
-		ActiveTab:     "tree",
-		ServerName:    cfg.Name,
-		IsCommitHash:  isCommitHash,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "tree",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		Ref:          ref,
+		Path:         path,
+		Content:      string(content),
+		RenderedHTML: renderedHTML,
+		IsBinary:     isBinaryContent(content),
+		IsMarkdown:   isMarkdown,
+		ShowSource:   showSource,
+		IsCommitHash: isCommitHash,
 	}
 
 	renderHTML(w, "blob.html", data)

pkg/web/webui_branches.go 🔗

@@ -19,16 +19,10 @@ type BranchInfo struct {
 
 // BranchesData contains data for rendering branches listing.
 type BranchesData struct {
-	Repo          proto.Repository
-	DefaultBranch string
+	RepoBaseData
+	PaginationData
 	Branches      []BranchInfo
-	ActiveTab     string
-	Page          int
-	TotalPages    int
 	TotalBranches int
-	HasPrevPage   bool
-	HasNextPage   bool
-	ServerName    string
 }
 
 // repoBranches handles branches listing page.
@@ -55,7 +49,7 @@ func repoBranches(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// First fetch to get total count and calculate pages
-	paginatedRefItems, totalBranches, err := FetchRefsPaginated(gr, RefTypeBranch, 0, 1, defaultBranch)
+	_, totalBranches, err := FetchRefsPaginated(gr, RefTypeBranch, 0, 1, defaultBranch)
 	if err != nil {
 		logger.Debug("failed to fetch branches", "repo", repo.Name(), "err", err)
 		renderInternalServerError(w, r)
@@ -79,7 +73,7 @@ func repoBranches(w http.ResponseWriter, r *http.Request) {
 	offset := (page - 1) * defaultBranchesPerPage
 
 	// Fetch only the branches we need for this page, pre-sorted
-	paginatedRefItems, totalBranches, err = FetchRefsPaginated(gr, RefTypeBranch, offset, defaultBranchesPerPage, defaultBranch)
+	paginatedRefItems, _, err := FetchRefsPaginated(gr, RefTypeBranch, offset, defaultBranchesPerPage, defaultBranch)
 	if err != nil {
 		logger.Debug("failed to fetch branches", "repo", repo.Name(), "err", err)
 		renderInternalServerError(w, r)
@@ -95,16 +89,22 @@ func repoBranches(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := BranchesData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "branches",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		PaginationData: PaginationData{
+			Page:        page,
+			TotalPages:  totalPages,
+			HasPrevPage: page > 1,
+			HasNextPage: page < totalPages,
+		},
 		Branches:      paginatedBranches,
-		ActiveTab:     "branches",
-		Page:          page,
-		TotalPages:    totalPages,
 		TotalBranches: totalBranches,
-		HasPrevPage:   page > 1,
-		HasNextPage:   page < totalPages,
-		ServerName:    cfg.Name,
 	}
 
 	renderHTML(w, "branches.html", data)

pkg/web/webui_commit.go 🔗

@@ -12,13 +12,10 @@ import (
 
 // CommitData contains data for rendering individual commit view.
 type CommitData struct {
-	Repo          proto.Repository
-	DefaultBranch string
-	Commit        *git.Commit
-	Diff          *git.Diff
-	ParentIDs     []string
-	ActiveTab     string
-	ServerName    string
+	RepoBaseData
+	Commit    *git.Commit
+	Diff      *git.Diff
+	ParentIDs []string
 }
 
 func repoCommit(w http.ResponseWriter, r *http.Request) {
@@ -61,13 +58,17 @@ func repoCommit(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := CommitData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
-		Commit:        commit,
-		Diff:          diff,
-		ParentIDs:     parentIDs,
-		ActiveTab:     "commits",
-		ServerName:    cfg.Name,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "commits",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		Commit:    commit,
+		Diff:      diff,
+		ParentIDs: parentIDs,
 	}
 
 	renderHTML(w, "commit.html", data)

pkg/web/webui_commits.go 🔗

@@ -14,16 +14,10 @@ import (
 
 // CommitsData contains data for rendering commit history.
 type CommitsData struct {
-	Repo          proto.Repository
-	DefaultBranch string
-	Ref           string
-	Commits       git.Commits
-	ActiveTab     string
-	Page          int
-	TotalPages    int
-	HasPrevPage   bool
-	HasNextPage   bool
-	ServerName    string
+	RepoBaseData
+	PaginationData
+	Ref     string
+	Commits git.Commits
 }
 
 // repoCommits handles commit history view.
@@ -89,16 +83,22 @@ func repoCommits(w http.ResponseWriter, r *http.Request) {
 	defaultBranch := getDefaultBranch(gr)
 
 	data := CommitsData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
-		Ref:           ref,
-		Commits:       commits,
-		ActiveTab:     "commits",
-		Page:          page,
-		TotalPages:    totalPages,
-		HasPrevPage:   page > 1,
-		HasNextPage:   page < totalPages,
-		ServerName:    cfg.Name,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "commits",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		PaginationData: PaginationData{
+			Page:        page,
+			TotalPages:  totalPages,
+			HasPrevPage: page > 1,
+			HasNextPage: page < totalPages,
+		},
+		Ref:     ref,
+		Commits: commits,
 	}
 
 	renderHTML(w, "commits.html", data)

pkg/web/webui_home.go 🔗

@@ -28,15 +28,11 @@ type HomeRepository struct {
 }
 
 type HomeData struct {
+	BaseData
+	PaginationData
 	Repo         proto.Repository
 	Repositories []HomeRepository
 	ReadmeHTML   template.HTML
-	ActiveTab    string
-	ServerName   string
-	Page         int
-	TotalPages   int
-	HasPrevPage  bool
-	HasNextPage  bool
 }
 
 type repoItem struct {
@@ -155,14 +151,18 @@ func home(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := HomeData{
+		BaseData: BaseData{
+			ServerName: cfg.Name,
+			ActiveTab:  "repositories",
+		},
+		PaginationData: PaginationData{
+			Page:        page,
+			TotalPages:  totalPages,
+			HasPrevPage: page > 1,
+			HasNextPage: page < totalPages,
+		},
 		Repositories: homeRepos,
 		ReadmeHTML:   readmeHTML,
-		ActiveTab:    "repositories",
-		ServerName:   cfg.Name,
-		Page:         page,
-		TotalPages:   totalPages,
-		HasPrevPage:  page > 1,
-		HasNextPage:  page < totalPages,
 	}
 
 	renderHTML(w, "home.html", data)

pkg/web/webui_overview.go 🔗

@@ -14,14 +14,11 @@ import (
 
 // OverviewData contains data for rendering repository overview page.
 type OverviewData struct {
-	Repo          proto.Repository
-	DefaultBranch string
-	IsEmpty       bool
-	SSHURL        string
-	HTTPURL       string
-	ActiveTab     string
-	ReadmeHTML    template.HTML
-	ServerName    string
+	RepoBaseData
+	IsEmpty    bool
+	SSHURL     string
+	HTTPURL    string
+	ReadmeHTML template.HTML
 }
 
 // repoOverview handles repository overview page.
@@ -54,14 +51,18 @@ func repoOverview(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := OverviewData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
-		IsEmpty:       isEmpty,
-		SSHURL:        sshURL,
-		HTTPURL:       httpURL,
-		ActiveTab:     "overview",
-		ReadmeHTML:    readmeHTML,
-		ServerName:    cfg.Name,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "overview",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		IsEmpty:    isEmpty,
+		SSHURL:     sshURL,
+		HTTPURL:    httpURL,
+		ReadmeHTML: readmeHTML,
 	}
 
 	renderHTML(w, "overview.html", data)

pkg/web/webui_tags.go 🔗

@@ -22,16 +22,10 @@ type TagInfo struct {
 
 // TagsData contains data for rendering tags listing.
 type TagsData struct {
-	Repo          proto.Repository
-	DefaultBranch string
-	Tags          []TagInfo
-	ActiveTab     string
-	Page          int
-	TotalPages    int
-	TotalTags     int
-	HasPrevPage   bool
-	HasNextPage   bool
-	ServerName    string
+	RepoBaseData
+	PaginationData
+	Tags      []TagInfo
+	TotalTags int
 }
 
 // repoTags handles tags listing page.
@@ -58,7 +52,7 @@ func repoTags(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// First fetch to get total count and calculate pages
-	paginatedRefItems, totalTags, err := FetchRefsPaginated(gr, RefTypeTag, 0, 1, "")
+	_, totalTags, err := FetchRefsPaginated(gr, RefTypeTag, 0, 1, "")
 	if err != nil {
 		logger.Debug("failed to fetch tags", "repo", repo.Name(), "err", err)
 		renderInternalServerError(w, r)
@@ -82,7 +76,7 @@ func repoTags(w http.ResponseWriter, r *http.Request) {
 	offset := (page - 1) * defaultTagsPerPage
 
 	// Fetch only the tags we need for this page, pre-sorted
-	paginatedRefItems, totalTags, err = FetchRefsPaginated(gr, RefTypeTag, offset, defaultTagsPerPage, "")
+	paginatedRefItems, _, err := FetchRefsPaginated(gr, RefTypeTag, offset, defaultTagsPerPage, "")
 	if err != nil {
 		logger.Debug("failed to fetch tags", "repo", repo.Name(), "err", err)
 		renderInternalServerError(w, r)
@@ -115,16 +109,22 @@ func repoTags(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := TagsData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
-		Tags:          paginatedTags,
-		ActiveTab:     "tags",
-		Page:          page,
-		TotalPages:    totalPages,
-		TotalTags:     totalTags,
-		HasPrevPage:   page > 1,
-		HasNextPage:   page < totalPages,
-		ServerName:    cfg.Name,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "tags",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		PaginationData: PaginationData{
+			Page:        page,
+			TotalPages:  totalPages,
+			HasPrevPage: page > 1,
+			HasNextPage: page < totalPages,
+		},
+		Tags:      paginatedTags,
+		TotalTags: totalTags,
 	}
 
 	renderHTML(w, "tags.html", data)

pkg/web/webui_tree.go 🔗

@@ -12,14 +12,11 @@ import (
 
 // TreeData contains data for rendering directory tree view.
 type TreeData struct {
-	Repo          proto.Repository
-	DefaultBranch string
-	Ref           string
-	Path          string
-	Entries       git.Entries
-	ActiveTab     string
-	ServerName    string
-	IsCommitHash  bool
+	RepoBaseData
+	Ref          string
+	Path         string
+	Entries      git.Entries
+	IsCommitHash bool
 }
 
 func repoTree(w http.ResponseWriter, r *http.Request) {
@@ -76,14 +73,18 @@ func repoTree(w http.ResponseWriter, r *http.Request) {
 	defaultBranch := getDefaultBranch(gr)
 
 	data := TreeData{
-		Repo:          repo,
-		DefaultBranch: defaultBranch,
-		Ref:           ref,
-		Path:          path,
-		Entries:       entries,
-		ActiveTab:     "tree",
-		ServerName:    cfg.Name,
-		IsCommitHash:  isCommitHash,
+		RepoBaseData: RepoBaseData{
+			BaseData: BaseData{
+				ServerName: cfg.Name,
+				ActiveTab:  "tree",
+			},
+			Repo:          repo,
+			DefaultBranch: defaultBranch,
+		},
+		Ref:          ref,
+		Path:         path,
+		Entries:      entries,
+		IsCommitHash: isCommitHash,
 	}
 
 	renderHTML(w, "tree.html", data)