Returning pagination metadata.
All checks were successful
Deploy Greenlight API / deploy (push) Successful in 2m48s
All checks were successful
Deploy Greenlight API / deploy (push) Successful in 2m48s
This commit is contained in:
@@ -237,14 +237,14 @@ func (app *application) listMoviesHandler(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call the GetAll() method to retrieve the movies, passing in the various filter parameters.
|
// Call the GetAll() method to retrieve the movies, passing in the various filter parameters.
|
||||||
movies, err := app.models.Movies.GetAll(input.Title, input.Genres, input.Filters)
|
movies, metadata, err := app.models.Movies.GetAll(input.Title, input.Genres, input.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.serverErrorResponse(w, r, err)
|
app.serverErrorResponse(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a JSON response containing the movie data
|
// Send a JSON response containing the movie data
|
||||||
err = app.writeJSON(w, http.StatusOK, envelope{"movies": movies}, nil)
|
err = app.writeJSON(w, http.StatusOK, envelope{"movies": movies, "metadata": metadata}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.serverErrorResponse(w, r, err)
|
app.serverErrorResponse(w, r, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,3 +50,27 @@ func (f Filters) sortDirection() string {
|
|||||||
}
|
}
|
||||||
return "ASC"
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// 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.
|
// 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'
|
// 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
|
// 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(`
|
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
|
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 = '{}')
|
||||||
@@ -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
|
// And then pass the args slice to QueryContext() as a variadic parameter
|
||||||
rows, err := m.DB.QueryContext(ctx, query, args...)
|
rows, err := m.DB.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
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
|
// Importantly, defer a call to rows.Close() to ensure the resultset is closed before GetAll() returns
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
|
totalRecords := 0
|
||||||
// Initialize an empty slice to hold the movie data.
|
// Initialize an empty slice to hold the movie data.
|
||||||
movies := []*Movie{}
|
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.
|
// 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(
|
err := rows.Scan(
|
||||||
|
&totalRecords,
|
||||||
&movie.ID,
|
&movie.ID,
|
||||||
&movie.CreatedAt,
|
&movie.CreatedAt,
|
||||||
&movie.Title,
|
&movie.Title,
|
||||||
@@ -245,7 +248,7 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
|
|||||||
&movie.Version,
|
&movie.Version,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, Metadata{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the Movie struct to the slice.
|
// 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.
|
// 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 {
|
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
|
// If everything went OK, then return the slice of movies
|
||||||
return movies, nil
|
// Include the metadata struct when returning
|
||||||
|
return movies, metadata, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user