tagliatelle

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

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 }