tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

include-pagination.go (5024B)


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