forked from tangled.org/core
this repo has no description

appview: repo/issues: paginate issues

loading up issues on @tangled.sh/core should be much faster because we
only have to resolve ~10 DIDs at a time. with a saturated did-handle
cache, this should be near-instant page load times.

authored by oppi.li and committed by Tangled 8a571743 3646ff6d

Changed files
+153 -28
appview
db
middleware
pages
templates
repo
issues
pagination
state
+35 -20
appview/db/issues.go
··· 5 5 "time" 6 6 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 + "tangled.sh/tangled.sh/core/appview/pagination" 8 9 ) 9 10 10 11 type Issue struct { ··· 102 103 return ownerDid, err 103 104 } 104 105 105 - func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool) ([]Issue, error) { 106 + func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) { 106 107 var issues []Issue 107 108 openValue := 0 108 109 if isOpen { ··· 110 111 } 111 112 112 113 rows, err := e.Query( 113 - `select 114 - i.owner_did, 115 - i.issue_id, 116 - i.created, 117 - i.title, 118 - i.body, 119 - i.open, 120 - count(c.id) 121 - from 122 - issues i 123 - left join 124 - comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id 125 - where 126 - i.repo_at = ? and i.open = ? 127 - group by 128 - i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open 129 - order by 130 - i.created desc`, 131 - repoAt, openValue) 114 + ` 115 + with numbered_issue as ( 116 + select 117 + i.owner_did, 118 + i.issue_id, 119 + i.created, 120 + i.title, 121 + i.body, 122 + i.open, 123 + count(c.id) as comment_count, 124 + row_number() over (order by i.created desc) as row_num 125 + from 126 + issues i 127 + left join 128 + comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id 129 + where 130 + i.repo_at = ? and i.open = ? 131 + group by 132 + i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open 133 + ) 134 + select 135 + owner_did, 136 + issue_id, 137 + created, 138 + title, 139 + body, 140 + open, 141 + comment_count 142 + from 143 + numbered_issue 144 + where 145 + row_num between ? and ?`, 146 + repoAt, openValue, page.Offset+1, page.Offset+page.Limit) 132 147 if err != nil { 133 148 return nil, err 134 149 }
+32
appview/middleware/middleware.go
··· 1 1 package middleware 2 2 3 3 import ( 4 + "context" 4 5 "log" 5 6 "net/http" 7 + "strconv" 6 8 "time" 7 9 8 10 comatproto "github.com/bluesky-social/indigo/api/atproto" 9 11 "github.com/bluesky-social/indigo/xrpc" 10 12 "tangled.sh/tangled.sh/core/appview" 11 13 "tangled.sh/tangled.sh/core/appview/auth" 14 + "tangled.sh/tangled.sh/core/appview/pagination" 12 15 ) 13 16 14 17 type Middleware func(http.Handler) http.Handler ··· 92 95 }) 93 96 } 94 97 } 98 + 99 + func Paginate(next http.Handler) http.Handler { 100 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 101 + page := pagination.FirstPage() 102 + 103 + offsetVal := r.URL.Query().Get("offset") 104 + if offsetVal != "" { 105 + offset, err := strconv.Atoi(offsetVal) 106 + if err != nil { 107 + log.Println("invalid offset") 108 + } else { 109 + page.Offset = offset 110 + } 111 + } 112 + 113 + limitVal := r.URL.Query().Get("limit") 114 + if limitVal != "" { 115 + limit, err := strconv.Atoi(limitVal) 116 + if err != nil { 117 + log.Println("invalid limit") 118 + } else { 119 + page.Limit = limit 120 + } 121 + } 122 + 123 + ctx := context.WithValue(r.Context(), "page", page) 124 + next.ServeHTTP(w, r.WithContext(ctx)) 125 + }) 126 + }
+7 -6
appview/pages/pages.go
··· 20 20 "tangled.sh/tangled.sh/core/appview/auth" 21 21 "tangled.sh/tangled.sh/core/appview/db" 22 22 "tangled.sh/tangled.sh/core/appview/pages/markup" 23 + "tangled.sh/tangled.sh/core/appview/pagination" 23 24 "tangled.sh/tangled.sh/core/appview/state/userutil" 24 25 "tangled.sh/tangled.sh/core/patchutil" 25 26 "tangled.sh/tangled.sh/core/types" ··· 651 652 } 652 653 653 654 type RepoIssuesParams struct { 654 - LoggedInUser *auth.User 655 - RepoInfo RepoInfo 656 - Active string 657 - Issues []db.Issue 658 - DidHandleMap map[string]string 659 - 655 + LoggedInUser *auth.User 656 + RepoInfo RepoInfo 657 + Active string 658 + Issues []db.Issue 659 + DidHandleMap map[string]string 660 + Page pagination.Page 660 661 FilteringByOpen bool 661 662 } 662 663
+38
appview/pages/templates/repo/issues/issues.html
··· 70 70 </div> 71 71 {{ end }} 72 72 </div> 73 + 74 + {{ block "pagination" . }} {{ end }} 75 + 76 + {{ end }} 77 + 78 + {{ define "pagination" }} 79 + <div class="flex justify-end mt-4 gap-2"> 80 + {{ $currentState := "closed" }} 81 + {{ if .FilteringByOpen }} 82 + {{ $currentState = "open" }} 83 + {{ end }} 84 + 85 + {{ if gt .Page.Offset 0 }} 86 + {{ $prev := .Page.Previous }} 87 + <a 88 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 89 + hx-boost="true" 90 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 91 + > 92 + {{ i "chevron-left" "w-4 h-4" }} 93 + previous 94 + </a> 95 + {{ else }} 96 + <div></div> 97 + {{ end }} 98 + 99 + {{ if eq (len .Issues) .Page.Limit }} 100 + {{ $next := .Page.Next }} 101 + <a 102 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 103 + hx-boost="true" 104 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}" 105 + > 106 + next 107 + {{ i "chevron-right" "w-4 h-4" }} 108 + </a> 109 + {{ end }} 110 + </div> 73 111 {{ end }}
+31
appview/pagination/page.go
··· 1 + package pagination 2 + 3 + type Page struct { 4 + Offset int // where to start from 5 + Limit int // number of items in a page 6 + } 7 + 8 + func FirstPage() Page { 9 + return Page{ 10 + Offset: 0, 11 + Limit: 10, 12 + } 13 + } 14 + 15 + func (p Page) Previous() Page { 16 + if p.Offset-p.Limit < 0 { 17 + return FirstPage() 18 + } else { 19 + return Page{ 20 + Offset: p.Offset - p.Limit, 21 + Limit: p.Limit, 22 + } 23 + } 24 + } 25 + 26 + func (p Page) Next() Page { 27 + return Page{ 28 + Offset: p.Offset + p.Limit, 29 + Limit: p.Limit, 30 + } 31 + }
+9 -1
appview/state/repo.go
··· 28 28 "tangled.sh/tangled.sh/core/appview/db" 29 29 "tangled.sh/tangled.sh/core/appview/pages" 30 30 "tangled.sh/tangled.sh/core/appview/pages/markup" 31 + "tangled.sh/tangled.sh/core/appview/pagination" 31 32 "tangled.sh/tangled.sh/core/types" 32 33 33 34 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 1559 1560 isOpen = true 1560 1561 } 1561 1562 1563 + page, ok := r.Context().Value("page").(pagination.Page) 1564 + if !ok { 1565 + log.Println("failed to get page") 1566 + page = pagination.FirstPage() 1567 + } 1568 + 1562 1569 user := s.auth.GetUser(r) 1563 1570 f, err := fullyResolvedRepo(r) 1564 1571 if err != nil { ··· 1566 1573 return 1567 1574 } 1568 1575 1569 - issues, err := db.GetIssues(s.db, f.RepoAt, isOpen) 1576 + issues, err := db.GetIssues(s.db, f.RepoAt, isOpen, page) 1570 1577 if err != nil { 1571 1578 log.Println("failed to get issues", err) 1572 1579 s.pages.Notice(w, "issues", "Failed to load issues. Try again later.") ··· 1593 1600 Issues: issues, 1594 1601 DidHandleMap: didHandleMap, 1595 1602 FilteringByOpen: isOpen, 1603 + Page: page, 1596 1604 }) 1597 1605 return 1598 1606 }
+1 -1
appview/state/router.go
··· 68 68 r.Get("/blob/{ref}/raw/*", s.RepoBlobRaw) 69 69 70 70 r.Route("/issues", func(r chi.Router) { 71 - r.Get("/", s.RepoIssues) 71 + r.With(middleware.Paginate).Get("/", s.RepoIssues) 72 72 r.Get("/{issue}", s.RepoSingleIssue) 73 73 74 74 r.Group(func(r chi.Router) {