Signed-off-by: dusk y.bera003.06@protonmail.com
+3
-3
appview/db/issues.go
+3
-3
appview/db/issues.go
···
98
whereClause = " where " + strings.Join(conditions, " and ")
99
}
100
101
-
pLower := FilterGte("row_num", page.Offset+1)
102
-
pUpper := FilterLte("row_num", page.Offset+page.Limit)
103
104
args = append(args, pLower.Arg()...)
105
args = append(args, pUpper.Arg()...)
···
244
}
245
246
func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) {
247
-
return GetIssuesPaginated(e, pagination.FirstPage(), filters...)
248
}
249
250
func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*models.Issue, error) {
···
98
whereClause = " where " + strings.Join(conditions, " and ")
99
}
100
101
+
pLower := FilterGte("row_num", page.Count*page.No+1)
102
+
pUpper := FilterLte("row_num", page.Count*(page.No+1))
103
104
args = append(args, pLower.Arg()...)
105
args = append(args, pUpper.Arg()...)
···
244
}
245
246
func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) {
247
+
return GetIssuesPaginated(e, pagination.Page{No: 0, Count: 30}, filters...)
248
}
249
250
func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*models.Issue, error) {
+3
-3
appview/db/notifications.go
+3
-3
appview/db/notifications.go
···
68
limit ? offset ?
69
`, whereClause)
70
71
-
args = append(args, page.Limit, page.Offset)
72
73
rows, err := e.QueryContext(context.Background(), query, args...)
74
if err != nil {
···
142
limit ? offset ?
143
`, whereClause)
144
145
-
args = append(args, page.Limit, page.Offset)
146
147
rows, err := e.QueryContext(context.Background(), query, args...)
148
if err != nil {
···
247
248
// GetNotifications retrieves notifications with filters
249
func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) {
250
-
return GetNotificationsPaginated(e, pagination.FirstPage(), filters...)
251
}
252
253
func CountNotifications(e Execer, filters ...filter) (int64, error) {
···
68
limit ? offset ?
69
`, whereClause)
70
71
+
args = append(args, page.Count, page.Count*page.No+1)
72
73
rows, err := e.QueryContext(context.Background(), query, args...)
74
if err != nil {
···
142
limit ? offset ?
143
`, whereClause)
144
145
+
args = append(args, page.Count, page.Count*page.No+1)
146
147
rows, err := e.QueryContext(context.Background(), query, args...)
148
if err != nil {
···
247
248
// GetNotifications retrieves notifications with filters
249
func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) {
250
+
return GetNotificationsPaginated(e, pagination.Page{No: 0, Count: 30}, filters...)
251
}
252
253
func CountNotifications(e Execer, filters ...filter) (int64, error) {
+19
-8
appview/issues/issues.go
+19
-8
appview/issues/issues.go
···
769
isOpen = true
770
}
771
772
-
page, ok := r.Context().Value("page").(pagination.Page)
773
-
if !ok {
774
-
log.Println("failed to get page")
775
-
page = pagination.FirstPage()
776
-
}
777
-
778
user := rp.oauth.GetUser(r)
779
f, err := rp.repoResolver.Resolve(r)
780
if err != nil {
···
782
return
783
}
784
785
openVal := 0
786
if isOpen {
787
openVal = 1
788
}
789
issues, err := db.GetIssuesPaginated(
790
rp.db,
791
-
page,
792
db.FilterEq("repo_at", f.RepoAt()),
793
db.FilterEq("open", openVal),
794
)
···
820
Issues: issues,
821
LabelDefs: defs,
822
FilteringByOpen: isOpen,
823
-
Page: page,
824
})
825
}
826
···
769
isOpen = true
770
}
771
772
user := rp.oauth.GetUser(r)
773
f, err := rp.repoResolver.Resolve(r)
774
if err != nil {
···
776
return
777
}
778
779
+
issueCounts, err := db.GetIssueCount(rp.db, f.RepoAt())
780
+
if err != nil {
781
+
log.Println("failed to get issue count", err)
782
+
rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
783
+
return
784
+
}
785
+
page, ok := r.Context().Value("page").(pagination.Page)
786
+
if !ok {
787
+
log.Println("failed to get page")
788
+
page = pagination.Page{No: 0, Count: 10}
789
+
}
790
+
issueCount := issueCounts.Closed
791
+
if isOpen {
792
+
issueCount = issueCounts.Open
793
+
}
794
+
paginate := pagination.NewFromPage(page, issueCount)
795
+
796
openVal := 0
797
if isOpen {
798
openVal = 1
799
}
800
issues, err := db.GetIssuesPaginated(
801
rp.db,
802
+
paginate.CurrentPage(),
803
db.FilterEq("repo_at", f.RepoAt()),
804
db.FilterEq("open", openVal),
805
)
···
831
Issues: issues,
832
LabelDefs: defs,
833
FilteringByOpen: isOpen,
834
+
Pagination: paginate,
835
})
836
}
837
+1
-1
appview/issues/router.go
+1
-1
appview/issues/router.go
+24
-22
appview/middleware/middleware.go
+24
-22
appview/middleware/middleware.go
···
81
}
82
}
83
84
-
func Paginate(next http.Handler) http.Handler {
85
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
86
-
page := pagination.FirstPage()
87
88
-
offsetVal := r.URL.Query().Get("offset")
89
-
if offsetVal != "" {
90
-
offset, err := strconv.Atoi(offsetVal)
91
-
if err != nil {
92
-
log.Println("invalid offset")
93
-
} else {
94
-
page.Offset = offset
95
}
96
-
}
97
98
-
limitVal := r.URL.Query().Get("limit")
99
-
if limitVal != "" {
100
-
limit, err := strconv.Atoi(limitVal)
101
-
if err != nil {
102
-
log.Println("invalid limit")
103
-
} else {
104
-
page.Limit = limit
105
}
106
-
}
107
108
-
ctx := context.WithValue(r.Context(), "page", page)
109
-
next.ServeHTTP(w, r.WithContext(ctx))
110
-
})
111
}
112
113
func (mw Middleware) knotRoleMiddleware(group string) middlewareFunc {
···
81
}
82
}
83
84
+
func Paginate(paginationCount int) func(next http.Handler) http.Handler {
85
+
return func(next http.Handler) http.Handler {
86
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
87
+
page := pagination.Page{No: 0, Count: paginationCount}
88
89
+
pageNoVal := r.URL.Query().Get("page")
90
+
if pageNoVal != "" {
91
+
pageNo, err := strconv.Atoi(pageNoVal)
92
+
if err != nil {
93
+
log.Println("invalid page no")
94
+
} else if pageNo > 0 {
95
+
page.No = pageNo - 1
96
+
}
97
}
98
99
+
pageCountVal := r.URL.Query().Get("count")
100
+
if pageCountVal != "" {
101
+
pageCount, err := strconv.Atoi(pageCountVal)
102
+
if err != nil {
103
+
log.Println("invalid page count")
104
+
} else {
105
+
page.Count = pageCount
106
+
}
107
}
108
109
+
ctx := context.WithValue(r.Context(), "page", page)
110
+
next.ServeHTTP(w, r.WithContext(ctx))
111
+
})
112
+
}
113
}
114
115
func (mw Middleware) knotRoleMiddleware(group string) middlewareFunc {
+4
-3
appview/notifications/notifications.go
+4
-3
appview/notifications/notifications.go
···
34
35
r.Group(func(r chi.Router) {
36
r.Use(middleware.AuthMiddleware(n.oauth))
37
-
r.With(middleware.Paginate).Get("/", n.notificationsPage)
38
r.Post("/{id}/read", n.markRead)
39
r.Post("/read-all", n.markAllRead)
40
r.Delete("/{id}", n.deleteNotification)
···
49
page, ok := r.Context().Value("page").(pagination.Page)
50
if !ok {
51
log.Println("failed to get page")
52
-
page = pagination.FirstPage()
53
}
54
55
total, err := db.CountNotifications(
···
61
n.pages.Error500(w)
62
return
63
}
64
65
notifications, err := db.GetNotificationsWithEntities(
66
n.db,
···
84
LoggedInUser: user,
85
Notifications: notifications,
86
UnreadCount: unreadCount,
87
-
Page: page,
88
Total: total,
89
})
90
}
···
34
35
r.Group(func(r chi.Router) {
36
r.Use(middleware.AuthMiddleware(n.oauth))
37
+
r.With(middleware.Paginate(30)).Get("/", n.notificationsPage)
38
r.Post("/{id}/read", n.markRead)
39
r.Post("/read-all", n.markAllRead)
40
r.Delete("/{id}", n.deleteNotification)
···
49
page, ok := r.Context().Value("page").(pagination.Page)
50
if !ok {
51
log.Println("failed to get page")
52
+
page = pagination.Page{No: 0, Count: 30}
53
}
54
55
total, err := db.CountNotifications(
···
61
n.pages.Error500(w)
62
return
63
}
64
+
paginate := pagination.NewFromPage(page, int(total))
65
66
notifications, err := db.GetNotificationsWithEntities(
67
n.db,
···
85
LoggedInUser: user,
86
Notifications: notifications,
87
UnreadCount: unreadCount,
88
+
Pagination: paginate,
89
Total: total,
90
})
91
}
+3
-3
appview/pages/pages.go
+3
-3
appview/pages/pages.go
···
320
RepoGroups []*models.RepoGroup
321
LabelDefs map[string]*models.LabelDefinition
322
GfiLabel *models.LabelDefinition
323
-
Page pagination.Page
324
}
325
326
func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error {
···
341
LoggedInUser *oauth.User
342
Notifications []*models.NotificationWithEntity
343
UnreadCount int
344
-
Page pagination.Page
345
Total int64
346
}
347
···
968
Active string
969
Issues []models.Issue
970
LabelDefs map[string]*models.LabelDefinition
971
-
Page pagination.Page
972
FilteringByOpen bool
973
}
974
···
320
RepoGroups []*models.RepoGroup
321
LabelDefs map[string]*models.LabelDefinition
322
GfiLabel *models.LabelDefinition
323
+
Pagination pagination.Pagination
324
}
325
326
func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error {
···
341
LoggedInUser *oauth.User
342
Notifications []*models.NotificationWithEntity
343
UnreadCount int
344
+
Pagination pagination.Pagination
345
Total int64
346
}
347
···
968
Active string
969
Issues []models.Issue
970
LabelDefs map[string]*models.LabelDefinition
971
+
Pagination pagination.Pagination
972
FilteringByOpen bool
973
}
974
+7
-7
appview/pages/templates/goodfirstissues/index.html
+7
-7
appview/pages/templates/goodfirstissues/index.html
···
130
</div>
131
{{ end }}
132
133
-
{{ if or (gt .Page.Offset 0) (eq (len .RepoGroups) .Page.Limit) }}
134
<div class="flex justify-center mt-8">
135
<div class="flex gap-2">
136
-
{{ if gt .Page.Offset 0 }}
137
-
{{ $prev := .Page.Previous }}
138
<a
139
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
140
hx-boost="true"
141
-
href="/goodfirstissues?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
142
>
143
{{ i "chevron-left" "w-4 h-4" }}
144
previous
···
147
<div></div>
148
{{ end }}
149
150
-
{{ if eq (len .RepoGroups) .Page.Limit }}
151
-
{{ $next := .Page.Next }}
152
<a
153
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
154
hx-boost="true"
155
-
href="/goodfirstissues?offset={{ $next.Offset }}&limit={{ $next.Limit }}"
156
>
157
next
158
{{ i "chevron-right" "w-4 h-4" }}
···
130
</div>
131
{{ end }}
132
133
+
{{ if or .Pagination.HasPreviousPage .Pagination.HasNextPage }}
134
<div class="flex justify-center mt-8">
135
<div class="flex gap-2">
136
+
{{ if .Pagination.HasPreviousPage }}
137
+
{{ $prev := .Pagination.PreviousPage }}
138
<a
139
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
140
hx-boost="true"
141
+
href="/goodfirstissues?page={{ add $prev.No 1 }}&count={{ $prev.Count }}"
142
>
143
{{ i "chevron-left" "w-4 h-4" }}
144
previous
···
147
<div></div>
148
{{ end }}
149
150
+
{{ if .Pagination.HasNextPage }}
151
+
{{ $next := .Pagination.NextPage }}
152
<a
153
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
154
hx-boost="true"
155
+
href="/goodfirstissues?page={{ add $next.No 1 }}&count={{ $next.Count }}"
156
>
157
next
158
{{ i "chevron-right" "w-4 h-4" }}
+6
-7
appview/pages/templates/notifications/list.html
+6
-7
appview/pages/templates/notifications/list.html
···
35
36
{{ define "pagination" }}
37
<div class="flex justify-end mt-4 gap-2">
38
-
{{ if gt .Page.Offset 0 }}
39
-
{{ $prev := .Page.Previous }}
40
<a
41
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
42
hx-boost="true"
43
-
href = "/notifications?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
44
>
45
{{ i "chevron-left" "w-4 h-4" }}
46
previous
···
49
<div></div>
50
{{ end }}
51
52
-
{{ $next := .Page.Next }}
53
-
{{ if lt $next.Offset .Total }}
54
-
{{ $next := .Page.Next }}
55
<a
56
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
57
hx-boost="true"
58
-
href = "/notifications?offset={{ $next.Offset }}&limit={{ $next.Limit }}"
59
>
60
next
61
{{ i "chevron-right" "w-4 h-4" }}
···
35
36
{{ define "pagination" }}
37
<div class="flex justify-end mt-4 gap-2">
38
+
{{ if .Pagination.HasPreviousPage }}
39
+
{{ $prev := .Pagination.PreviousPage }}
40
<a
41
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
42
hx-boost="true"
43
+
href = "/notifications?page={{ add $prev.No 1 }}&count={{ $prev.Count }}"
44
>
45
{{ i "chevron-left" "w-4 h-4" }}
46
previous
···
49
<div></div>
50
{{ end }}
51
52
+
{{ if .Pagination.HasNextPage }}
53
+
{{ $next := .Pagination.NextPage }}
54
<a
55
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
56
hx-boost="true"
57
+
href = "/notifications?page={{ add $next.No 1 }}&count={{ $next.Count }}"
58
>
59
next
60
{{ i "chevron-right" "w-4 h-4" }}
+6
-6
appview/pages/templates/repo/issues/issues.html
+6
-6
appview/pages/templates/repo/issues/issues.html
···
50
{{ $currentState = "open" }}
51
{{ end }}
52
53
-
{{ if gt .Page.Offset 0 }}
54
-
{{ $prev := .Page.Previous }}
55
<a
56
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
57
hx-boost="true"
58
-
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
59
>
60
{{ i "chevron-left" "w-4 h-4" }}
61
previous
···
64
<div></div>
65
{{ end }}
66
67
-
{{ if eq (len .Issues) .Page.Limit }}
68
-
{{ $next := .Page.Next }}
69
<a
70
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
71
hx-boost="true"
72
-
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}"
73
>
74
next
75
{{ i "chevron-right" "w-4 h-4" }}
···
50
{{ $currentState = "open" }}
51
{{ end }}
52
53
+
{{ if .Pagination.HasPreviousPage }}
54
+
{{ $prev := .Pagination.PreviousPage }}
55
<a
56
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
57
hx-boost="true"
58
+
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&page={{ add $prev.No 1 }}&count={{ $prev.Count }}"
59
>
60
{{ i "chevron-left" "w-4 h-4" }}
61
previous
···
64
<div></div>
65
{{ end }}
66
67
+
{{ if .Pagination.HasNextPage }}
68
+
{{ $next := .Pagination.NextPage }}
69
<a
70
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
71
hx-boost="true"
72
+
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&page={{ add $next.No 1 }}&count={{ $next.Count }}"
73
>
74
next
75
{{ i "chevron-right" "w-4 h-4" }}
+61
-13
appview/pagination/page.go
+61
-13
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: 30,
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
}
···
1
package pagination
2
3
+
type Pagination struct {
4
Offset int // where to start from
5
+
Limit int // number of items on a single page
6
+
Total int // total number of items
7
+
}
8
+
9
+
type Page struct {
10
+
No int // page number
11
+
Count int // number of items on this page
12
+
}
13
+
14
+
func New(offset, limit, total int) Pagination {
15
+
return Pagination{
16
+
Offset: offset,
17
+
Limit: limit,
18
+
Total: total,
19
+
}
20
}
21
22
+
func NewFromPage(page Page, total int) Pagination {
23
+
return New(page.No*page.Count, page.Count, total)
24
+
}
25
+
26
+
func (p Pagination) FirstPage() Page {
27
return Page{
28
+
No: 0,
29
+
Count: p.Limit,
30
}
31
}
32
33
+
func (p Pagination) CurrentPage() Page {
34
+
return Page{
35
+
No: p.Offset / p.Limit,
36
+
Count: p.Limit,
37
+
}
38
+
}
39
+
40
+
func (p Pagination) LastPage() Page {
41
+
return Page{
42
+
No: (p.Total - 1) / p.Limit,
43
+
Count: p.Limit,
44
+
}
45
+
}
46
+
47
+
func (p Pagination) PreviousPage() Page {
48
if p.Offset-p.Limit < 0 {
49
+
return p.FirstPage()
50
} else {
51
return Page{
52
+
No: (p.Offset - p.Limit) / p.Limit,
53
+
Count: p.Limit,
54
}
55
}
56
}
57
58
+
func (p Pagination) NextPage() Page {
59
+
if p.Offset+p.Limit >= p.Total {
60
+
return p.LastPage()
61
+
} else {
62
+
return Page{
63
+
No: (p.Offset + p.Limit) / p.Limit,
64
+
Count: p.Limit,
65
+
}
66
}
67
}
68
+
69
+
func (p Pagination) HasPreviousPage() bool {
70
+
return p.CurrentPage().No > 0
71
+
}
72
+
73
+
func (p Pagination) HasNextPage() bool {
74
+
return p.CurrentPage().No+1 < p.TotalPageCount()
75
+
}
76
+
77
+
func (p Pagination) TotalPageCount() int {
78
+
return (p.Total + p.Limit - 1) / p.Limit
79
+
}
+1
-1
appview/repo/feed.go
+1
-1
appview/repo/feed.go
+9
-6
appview/state/gfi.go
+9
-6
appview/state/gfi.go
···
20
21
page, ok := r.Context().Value("page").(pagination.Page)
22
if !ok {
23
-
page = pagination.FirstPage()
24
}
25
26
goodFirstIssueLabel := fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue")
···
37
LoggedInUser: user,
38
RepoGroups: []*models.RepoGroup{},
39
LabelDefs: make(map[string]*models.LabelDefinition),
40
-
Page: page,
41
})
42
return
43
}
···
50
allIssues, err := db.GetIssuesPaginated(
51
s.db,
52
pagination.Page{
53
-
Limit: 500,
54
},
55
db.FilterIn("repo_at", repoUris),
56
db.FilterEq("open", 1),
···
61
return
62
}
63
64
var goodFirstIssues []models.Issue
65
for _, issue := range allIssues {
66
if issue.Labels.ContainsLabel(goodFirstIssueLabel) {
···
98
return sortedGroups[i].Repo.Name < sortedGroups[j].Repo.Name
99
})
100
101
-
groupStart := page.Offset
102
-
groupEnd := page.Offset + page.Limit
103
if groupStart > len(sortedGroups) {
104
groupStart = len(sortedGroups)
105
}
···
145
LoggedInUser: user,
146
RepoGroups: paginatedGroups,
147
LabelDefs: labelDefsMap,
148
-
Page: page,
149
GfiLabel: labelDefsMap[goodFirstIssueLabel],
150
})
151
}
···
20
21
page, ok := r.Context().Value("page").(pagination.Page)
22
if !ok {
23
+
page = pagination.Page{No: 0, Count: 30}
24
}
25
26
goodFirstIssueLabel := fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue")
···
37
LoggedInUser: user,
38
RepoGroups: []*models.RepoGroup{},
39
LabelDefs: make(map[string]*models.LabelDefinition),
40
+
Pagination: pagination.NewFromPage(page, 0),
41
})
42
return
43
}
···
50
allIssues, err := db.GetIssuesPaginated(
51
s.db,
52
pagination.Page{
53
+
No: 0,
54
+
Count: 500,
55
},
56
db.FilterIn("repo_at", repoUris),
57
db.FilterEq("open", 1),
···
62
return
63
}
64
65
+
pagination := pagination.NewFromPage(page, len(allIssues))
66
+
67
var goodFirstIssues []models.Issue
68
for _, issue := range allIssues {
69
if issue.Labels.ContainsLabel(goodFirstIssueLabel) {
···
101
return sortedGroups[i].Repo.Name < sortedGroups[j].Repo.Name
102
})
103
104
+
groupStart := page.Count * page.No
105
+
groupEnd := page.Count * (page.No + 1)
106
if groupStart > len(sortedGroups) {
107
groupStart = len(sortedGroups)
108
}
···
148
LoggedInUser: user,
149
RepoGroups: paginatedGroups,
150
LabelDefs: labelDefsMap,
151
+
Pagination: pagination,
152
GfiLabel: labelDefsMap[goodFirstIssueLabel],
153
})
154
}