From 5cc1196085f26e550802af91911ef623d0cf4a31 Mon Sep 17 00:00:00 2001 From: lbenedar Date: Wed, 18 Mar 2026 18:16:31 +0300 Subject: [PATCH] ch9.8 --- cmd/api/movies.go | 4 ++-- internal/data/filters.go | 23 +++++++++++++++++++++++ internal/data/models.go | 2 +- internal/data/movies.go | 24 +++++++++++++++--------- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/cmd/api/movies.go b/cmd/api/movies.go index d06c89a..24a4ebd 100644 --- a/cmd/api/movies.go +++ b/cmd/api/movies.go @@ -198,13 +198,13 @@ func (app *application) listMoviesHandler(w http.ResponseWriter, r *http.Request return } - 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 { app.serverErrorResponse(w, r, err) return } - 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 { app.serverErrorResponse(w, r, err) } diff --git a/internal/data/filters.go b/internal/data/filters.go index 4fbda6e..22fc67c 100644 --- a/internal/data/filters.go +++ b/internal/data/filters.go @@ -1,6 +1,7 @@ package data import ( + "math" "strings" "gitea.local.lab/Lbenedar/greenlight/internal/validator" @@ -13,6 +14,28 @@ type Filters struct { SortSafelist []string } +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"` +} + +func calculateMetadata(totalRecords, page, pageSize int) Metadata { + if totalRecords == 0 { + return Metadata{} + } + + return Metadata{ + CurrentPage: page, + PageSize: pageSize, + FirstPage: 1, + LastPage: int(math.Ceil(float64(totalRecords) / float64(pageSize))), + TotalRecords: totalRecords, + } +} + func ValidateFilters(v *validator.Validator, f Filters) { v.Check(f.Page > 0, "page", "must be greater than zero") v.Check(f.Page <= 10_000_000, "page", "must be a maximum of 10 million") diff --git a/internal/data/models.go b/internal/data/models.go index e4a539f..293edbb 100644 --- a/internal/data/models.go +++ b/internal/data/models.go @@ -16,7 +16,7 @@ type Models struct { Get(id int64) (*Movie, error) Update(movie *Movie) error Delete(id int64) error - GetAll(title string, genres []string, filter Filters) ([]*Movie, error) + GetAll(title string, genres []string, filter Filters) ([]*Movie, Metadata, error) } } diff --git a/internal/data/movies.go b/internal/data/movies.go index c33ff5c..523b164 100644 --- a/internal/data/movies.go +++ b/internal/data/movies.go @@ -135,9 +135,9 @@ func (m MovieModel) Delete(id int64) error { return nil } -func (m MovieModel) GetAll(title string, genres []string, filter Filters) ([]*Movie, error) { +func (m MovieModel) GetAll(title string, genres []string, filter Filters) ([]*Movie, Metadata, error) { 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 = '{}') @@ -147,18 +147,22 @@ func (m MovieModel) GetAll(title string, genres []string, filter Filters) ([]*Mo ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - rows, err := m.DB.QueryContext(ctx, query, title, pq.Array(genres), filter.limit(), filter.offset()) + args := []any{title, pq.Array(genres), filter.limit(), filter.offset()} + + rows, err := m.DB.QueryContext(ctx, query, args...) if err != nil { - return nil, err + return nil, Metadata{}, err } defer rows.Close() + totalRecords := 0 movies := []*Movie{} for rows.Next() { var movie Movie err := rows.Scan( + &totalRecords, &movie.ID, &movie.CreatedAt, &movie.Title, @@ -168,16 +172,18 @@ func (m MovieModel) GetAll(title string, genres []string, filter Filters) ([]*Mo &movie.Version, ) if err != nil { - return nil, err + return nil, Metadata{}, err } movies = append(movies, &movie) } if err = rows.Err(); err != nil { - return nil, err + return nil, Metadata{}, err } - return movies, nil + metadata := calculateMetadata(totalRecords, filter.Page, filter.PageSize) + + return movies, metadata, nil } type MockMovieModel struct{} @@ -198,8 +204,8 @@ func (m MockMovieModel) Delete(id int64) error { return nil } -func (m MockMovieModel) GetAll(title string, genres []string, filter Filters) ([]*Movie, error) { - return nil, nil +func (m MockMovieModel) GetAll(title string, genres []string, filter Filters) ([]*Movie, Metadata, error) { + return nil, Metadata{}, nil } func ValidateMovie(v *validator.Validator, movie *Movie) {