Paginating Lists

This commit is contained in:
Maxime Delporte
2025-11-15 16:34:40 +01:00
parent fc2e401f6f
commit 6fc5e725e6
2 changed files with 16 additions and 2 deletions

View File

@@ -23,6 +23,14 @@ func ValidateFilters(v *validator.Validator, f Filters) {
v.Check(validator.PermittedValue(f.Sort, f.SortSafelist...), "sort", "invalid sort value") v.Check(validator.PermittedValue(f.Sort, f.SortSafelist...), "sort", "invalid sort value")
} }
func (f Filters) limit() int {
return f.PageSize
}
func (f Filters) offset() int {
return (f.Page - 1) * f.PageSize
}
// sortColumn : Check that the client-provided Sort field matches one of the entries in our safelist and if it does, extract the column name from the Sort field by stripping the leading hyphen character (if one exists) // sortColumn : Check that the client-provided Sort field matches one of the entries in our safelist and if it does, extract the column name from the Sort field by stripping the leading hyphen character (if one exists)
func (f Filters) sortColumn() string { func (f Filters) sortColumn() string {
for _, safeValue := range f.SortSafelist { for _, safeValue := range f.SortSafelist {

View File

@@ -200,19 +200,25 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
// plainto_tsquery('simple', $1) takes a search value and turns it into a formatted query term that PostgreSQL full-text search can understand. As an example : "The Club" would result in the query term 'the' & 'club' // plainto_tsquery('simple', $1) takes a search value and turns it into a formatted query term that PostgreSQL full-text search can understand. As an example : "The Club" would result in the query term 'the' & 'club'
// The @@ operator is the matches' operator. To continue the example, the query term 'the' & 'club' will match rows which contain both lexemes 'the' and 'club'. // The @@ operator is the matches' operator. To continue the example, the query term 'the' & 'club' will match rows which contain both lexemes 'the' and 'club'.
// Add an ORDER BY clause and interpolate the sort column and direction. Importantly, notice that we also include a secondary sort on the movie ID to ensure a consistent ordering. // Add an ORDER BY clause and interpolate the sort column and direction. Importantly, notice that we also include a secondary sort on the movie ID to ensure a consistent ordering.
// Update the SQL query to include the LIMIT and OFFSET clauses with placeholder parameter values
query := fmt.Sprintf(` query := fmt.Sprintf(`
SELECT id, created_at, title, year, runtime, genres, version SELECT id, created_at, title, year, runtime, genres, version
FROM movies FROM movies
WHERE (to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '') WHERE (to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '')
AND (genres @> $2 OR $2 = '{}') AND (genres @> $2 OR $2 = '{}')
ORDER BY %s %s, id ASC`, filters.sortColumn(), filters.sortDirection()) ORDER BY %s %s, id ASC
LIMIT $3 OFFSET $4`, filters.sortColumn(), filters.sortDirection())
// Create a context with a 3-second timeout // Create a context with a 3-second timeout
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() defer cancel()
// As our SQL query now has quite a few placeholder parameters, let's collect the values for the placeholders in a slice. Notice here how we call the limit() and offset() methods on the Filters struct to get the appropriate values for the LIMIT and OFFSET clauses
args := []any{title, pq.Array(genres), filters.limit(), filters.offset()}
// Use QueryContext() to execute the query. This returns a sql.Rows resultset containing the result // Use QueryContext() to execute the query. This returns a sql.Rows resultset containing the result
rows, err := m.DB.QueryContext(ctx, query, title, pq.Array(genres)) // And then pass the args slice to QueryContext() as a variadic parameter
rows, err := m.DB.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }