include-pagination.go (4855B)
1 package main 2 3 import ( 4 "database/sql" 5 "net/http" 6 "net/url" 7 "strconv" 8 "strings" 9 ) 10 11 func pageFromRequest(r *http.Request) int { 12 if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil && p > 0 { 13 return p 14 } 15 return 1 16 } 17 18 func perPageFromConfig(fallback int) int { 19 if n, err := strconv.Atoi(config.ItemsPerPage); err == nil && n > 0 { 20 return n 21 } 22 return fallback 23 } 24 25 func getUntaggedFilesPaginated(page, perPage int) ([]File, int, error) { 26 // Get total count 27 var total int 28 err := db.QueryRow(`SELECT COUNT(*) FROM files f LEFT JOIN file_tags ft ON ft.file_id = f.id WHERE ft.file_id IS NULL`).Scan(&total) 29 if err != nil { 30 return nil, 0, err 31 } 32 33 offset := (page - 1) * perPage 34 files, err := queryFilesWithTags(` 35 SELECT f.id, f.filename, f.path, COALESCE(f.description, '') as description 36 FROM files f 37 LEFT JOIN file_tags ft ON ft.file_id = f.id 38 WHERE ft.file_id IS NULL 39 ORDER BY f.id DESC 40 LIMIT ? OFFSET ? 41 `, perPage, offset) 42 43 return files, total, err 44 } 45 46 func buildPageDataWithPagination(title string, data interface{}, page, total, perPage int, r *http.Request) PageData { 47 pd := buildPageData(title, data) 48 pd.Pagination = calculatePagination(page, total, perPage) 49 pd.Pagination.PageBaseURL = pageBaseURL(r) 50 return pd 51 } 52 53 // pageBaseURL returns a URL base suitable for appending page=N. 54 // It preserves all existing query parameters except 'page'. 55 // e.g. /search?q=cats → "?q=cats&" 56 // /browse → "?" 57 func pageBaseURL(r *http.Request) string { 58 params := r.URL.Query() 59 params.Del("page") 60 if encoded := params.Encode(); encoded != "" { 61 return "?" + encoded + "&" 62 } 63 return "?" 64 } 65 66 func calculatePagination(page, total, perPage int) *Pagination { 67 totalPages := (total + perPage - 1) / perPage 68 if totalPages < 1 { 69 totalPages = 1 70 } 71 72 return &Pagination{ 73 CurrentPage: page, 74 TotalPages: totalPages, 75 HasPrev: page > 1, 76 HasNext: page < totalPages, 77 PrevPage: page - 1, 78 NextPage: page + 1, 79 PerPage: perPage, 80 } 81 } 82 83 func getSearchResultsPaginated(query string, page, perPage int) ([]File, int, error) { 84 sqlPattern := "%" + strings.ReplaceAll(strings.ReplaceAll(strings.ToLower(query), "*", "%"), "?", "_") + "%" 85 86 var total int 87 err := db.QueryRow(` 88 SELECT COUNT(DISTINCT f.id) 89 FROM files f 90 LEFT JOIN file_tags ft ON ft.file_id = f.id 91 LEFT JOIN tags t ON t.id = ft.tag_id 92 WHERE LOWER(f.filename) LIKE ? OR LOWER(f.description) LIKE ? OR LOWER(t.value) LIKE ? 93 `, sqlPattern, sqlPattern, sqlPattern).Scan(&total) 94 if err != nil { 95 return nil, 0, err 96 } 97 98 offset := (page - 1) * perPage 99 rows, err := db.Query(` 100 SELECT f.id, f.filename, f.path, COALESCE(f.description, '') AS description, 101 c.name AS category, t.value AS tag 102 FROM files f 103 LEFT JOIN file_tags ft ON ft.file_id = f.id 104 LEFT JOIN tags t ON t.id = ft.tag_id 105 LEFT JOIN categories c ON c.id = t.category_id 106 WHERE f.id IN ( 107 SELECT DISTINCT f2.id 108 FROM files f2 109 LEFT JOIN file_tags ft2 ON ft2.file_id = f2.id 110 LEFT JOIN tags t2 ON t2.id = ft2.tag_id 111 WHERE LOWER(f2.filename) LIKE ? OR LOWER(f2.description) LIKE ? OR LOWER(t2.value) LIKE ? 112 ORDER BY f2.filename 113 LIMIT ? OFFSET ? 114 ) 115 ORDER BY f.filename 116 `, sqlPattern, sqlPattern, sqlPattern, perPage, offset) 117 if err != nil { 118 return nil, 0, err 119 } 120 defer rows.Close() 121 122 fileMap := make(map[int]*File) 123 var orderedIDs []int 124 for rows.Next() { 125 var id int 126 var filename, path, description, category, tag sql.NullString 127 if err := rows.Scan(&id, &filename, &path, &description, &category, &tag); err != nil { 128 return nil, 0, err 129 } 130 f, exists := fileMap[id] 131 if !exists { 132 f = &File{ 133 ID: id, 134 Filename: filename.String, 135 Path: path.String, 136 EscapedFilename: url.PathEscape(filename.String), 137 Description: description.String, 138 Tags: make(map[string][]string), 139 } 140 fileMap[id] = f 141 orderedIDs = append(orderedIDs, id) 142 } 143 if category.Valid && tag.Valid && tag.String != "" { 144 f.Tags[category.String] = append(f.Tags[category.String], tag.String) 145 } 146 } 147 if err := rows.Err(); err != nil { 148 return nil, 0, err 149 } 150 151 files := make([]File, 0, len(orderedIDs)) 152 for _, id := range orderedIDs { 153 files = append(files, *fileMap[id]) 154 } 155 return files, total, nil 156 } 157 158 func getTaggedFilesPaginated(page, perPage int) ([]File, int, error) { 159 // Get total count 160 var total int 161 err := db.QueryRow(`SELECT COUNT(DISTINCT f.id) FROM files f JOIN file_tags ft ON ft.file_id = f.id`).Scan(&total) 162 if err != nil { 163 return nil, 0, err 164 } 165 166 offset := (page - 1) * perPage 167 files, err := queryFilesWithTags(` 168 SELECT DISTINCT f.id, f.filename, f.path, COALESCE(f.description, '') as description 169 FROM files f 170 JOIN file_tags ft ON ft.file_id = f.id 171 ORDER BY f.id DESC 172 LIMIT ? OFFSET ? 173 `, perPage, offset) 174 175 return files, total, err 176 }