Returning pagination metadata.
All checks were successful
Deploy Greenlight API / deploy (push) Successful in 2m48s

This commit is contained in:
Maxime Delporte
2025-11-15 16:51:13 +01:00
parent 28b25fed6e
commit e93375d441
3 changed files with 39 additions and 8 deletions

View File

@@ -50,3 +50,27 @@ func (f Filters) sortDirection() string {
}
return "ASC"
}
// Metadata struct for holding the pagination metadata
type Metadata struct {
CurrentPage int `json:"current_page,omitempty"`
PageSize int `json:"page_size,omitempty"`
FirstPage int `json:"first_page,omitempty"`
LastPage int `json:"last_page,omitempty"`
TotalRecords int `json:"total_records,omitempty"`
}
// calculateMetadata : calculates the appropriate pagination metadata values given the total number of records, current page, and page size values. Note that when the last page value is calculated, we are dividing two int values, and when dividing integer types in Go the result will also be an integer type, with the modulus (or reminder) dropped. So, for instance, if there were 12 records in total and a page of 5, the last page value would be (12+5-1)/5 = 3.2, which is then truncated to 3 by Go.
func calculateMetadata(totalRecords, page, pageSize int) Metadata {
if totalRecords == 0 {
return Metadata{}
}
return Metadata{
CurrentPage: page,
PageSize: pageSize,
FirstPage: 1,
LastPage: (totalRecords + pageSize - 1) / pageSize,
TotalRecords: totalRecords,
}
}

View File

@@ -194,15 +194,16 @@ func (m MovieModel) Delete(id int64) error {
}
// GetAll : Returns a slice of movies. Although we're not using them right now, we've set this up to accept the various filter parameters as arguments
func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*Movie, error) {
func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*Movie, Metadata, error) {
// Construct the SQL query to retrieve all movie records.
// to_tsvector('simple', title) transforms 'The Breakfast Club' into 'breakfast' 'club' 'the'. The 'simple' parameter's value is the configuration.
// 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'.
// 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
// Update the SQL query to include the window function which counts the total (filtered) records
query := fmt.Sprintf(`
SELECT id, created_at, title, year, runtime, genres, version
SELECT count(*) OVER(), id, created_at, title, year, runtime, genres, version
FROM movies
WHERE (to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '')
AND (genres @> $2 OR $2 = '{}')
@@ -220,12 +221,13 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
// And then pass the args slice to QueryContext() as a variadic parameter
rows, err := m.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
return nil, Metadata{}, err
}
// Importantly, defer a call to rows.Close() to ensure the resultset is closed before GetAll() returns
defer rows.Close()
totalRecords := 0
// Initialize an empty slice to hold the movie data.
movies := []*Movie{}
@@ -236,6 +238,7 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
// Scan the values from the row into the Movie struct. Again, note that we're using the pq.Array() adapter on the genres field here.
err := rows.Scan(
&totalRecords,
&movie.ID,
&movie.CreatedAt,
&movie.Title,
@@ -245,7 +248,7 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
&movie.Version,
)
if err != nil {
return nil, err
return nil, Metadata{}, err
}
// Add the Movie struct to the slice.
@@ -254,10 +257,14 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
// When the rows.Next() loop has finished, call rows.Err() to retrieve any error that was encountered during the iteration.
if err = rows.Err(); err != nil {
return nil, err
return nil, Metadata{}, err
}
// Generate a Metadata struct, passing in the total record count and pagination parameters from the client
metadata := calculateMetadata(totalRecords, filters.Page, filters.PageSize)
// If everything went OK, then return the slice of movies
return movies, nil
// Include the metadata struct when returning
return movies, metadata, nil
}