Skip to content

Conversation

@gocanto
Copy link
Collaborator

@gocanto gocanto commented Jul 21, 2025

This change introduces a database-backed posts API endpoint, including repository, query filters, pagination utilities, handler logic, and response transformation.

The router has been updated to support the new posts handler, and supporting structs, methods, and utility functions have been added for filtering, pagination, and response formatting. Minor Makefile and dependency updates are included.

Summary by CodeRabbit

  • New Features

    • Introduced API endpoints to list and retrieve individual blog posts with support for advanced filtering, pagination, and detailed post data including author, categories, and tags.
    • Added standardized error responses for bad requests and not found errors.
    • Enhanced request parsing and response formatting for post-related API operations.
  • Bug Fixes

    • Improved internal error message formatting for consistency.
  • Chores

    • Updated build tooling to use a new flag for the development server.
    • Declared a previously indirect dependency as a direct dependency.

@coderabbitai
Copy link

coderabbitai bot commented Jul 21, 2025

Warning

Rate limit exceeded

@gocanto has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9a63dae and 021dc82.

📒 Files selected for processing (3)
  • handler/posts.go (1 hunks)
  • pkg/http/request.go (1 hunks)
  • pkg/support.go (1 hunks)

Walkthrough

This change introduces a database-backed posts API endpoint, including repository, query filters, pagination utilities, handler logic, and response transformation. The router is updated to support the new posts handler, and supporting structs, methods, and utility functions are added for filtering, pagination, and response formatting. Minor Makefile and dependency updates are included.

Changes

File(s) Change Summary
boost/app.go, boost/router.go Integrate database connection into router; add posts route and handler registration.
database/connection.go Add method to get a new GORM session with query fields enabled.
database/repository/posts.go Add methods to fetch posts with filters, eager loading, pagination; and to find post by slug.
database/repository/queries/posts.go Add function to apply dynamic post filters to GORM queries.
database/repository/queries/posts_filters.go Add struct and methods for post filter criteria with string sanitization.
database/repository/pagination/paginate.go, database/repository/pagination/pagination.go Add pagination struct, methods, and generic pagination utilities for data transformation and metadata.
handler/posts.go Add posts handler struct, constructor, and HTTP handler methods for paginated, filtered post retrieval.
handler/posts/response.go Add response data structures for posts, authors, categories, and tags.
handler/posts/transformer.go Add functions to transform DB entities to response types and parse pagination/filter parameters from requests.
config/makefile/app.mk Add -d flag to air in the watch target.
go.mod Move golang.org/x/text to direct dependencies.
pkg/http/request.go Add generic function to parse HTTP request body into typed struct with safe closure.
pkg/http/response.go Add BadRequestError and NotFound error constructors; modify InternalError message casing.
pkg/stringable.go Add ToLower method to Stringable type using language-aware lowercasing and trimming.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router
    participant PostsHandler
    participant PostsRepository
    participant DB

    Client->>Router: HTTP GET /posts
    Router->>PostsHandler: Handle(request, response)
    PostsHandler->>PostsRepository: GetPosts(filters, paginate)
    PostsRepository->>DB: Query posts with filters, joins, pagination
    DB-->>PostsRepository: Posts data
    PostsRepository-->>PostsHandler: Paginated posts
    PostsHandler->>PostsHandler: Transform posts to response format
    PostsHandler-->>Router: Write JSON response
    Router-->>Client: HTTP Response (paginated posts)
Loading

Estimated code review effort

4 (~90 minutes)

Possibly related PRs

  • Add fixtures #35: Introduces the initial Router struct and App refactoring; this PR builds on it by adding posts-related database integration and routing.

Poem

In the warren of code, a new path appears,
Posts now hop with filters, across frontiers.
Pagination leaps, responses shine bright,
Handlers and routers join in delight.
With every query, the data flows free—
A rabbit’s proud hop in the code canopy! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @gocanto, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request initiates the development of query functionality, specifically focusing on 'posts'. It establishes the necessary database integration within the application's routing layer, introduces a flexible and robust system for filtering and paginating post data, and sets up the foundational API endpoint for accessing this information. These changes are crucial for building out the application's data retrieval capabilities.

Highlights

  • Database Integration: The Router struct in boost/router.go has been updated to include a Db *database.Connection field, and this database connection is now passed to the router during application initialization in boost/app.go. This change enables routing and handler logic to directly access the database.
  • Posts Querying Infrastructure: New structures PostFilters and PaginatedResult[T any] have been introduced in database/repository/posts.go and database/repository/pagination.go respectively. A comprehensive GetPosts method has been added to repository.Posts, allowing for advanced filtering (by UUID, slug, title, author, category, tag) and robust pagination logic, including total record counts and next/previous page calculations.
  • New API Endpoint Setup: A new /posts API endpoint is now registered within the Router via a new Posts() method. This endpoint is handled by a newly introduced PostsHandler (in handler/posts.go), which leverages the repository.Posts to fetch data using the new filtering and pagination capabilities.
  • Development Tooling Update: The watch command in config/makefile/app.mk has been updated to run the air live-reloading tool with the -d (debug) flag, which might provide more verbose output or enable specific debug features during development.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request starts the implementation of a query endpoint for posts. It introduces a new handler, repository methods for querying posts with filters and pagination, and related data structures. The review identifies a few critical issues, such as a compile error and use of log.Fatalf that would crash the server, along with several medium-severity suggestions to improve code clarity, fix bugs, and complete the implementation.

@gocanto gocanto changed the title start working on query [feature] Get Posts/Post Jul 21, 2025
@gocanto gocanto marked this pull request as ready for review July 21, 2025 09:18
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

♻️ Duplicate comments (6)
boost/router.go (1)

34-43: Address handler signature compatibility and add HTTP method specification.

There are two concerns with this implementation:

  1. Handler signature compatibility: Based on past review comments, there may be a signature mismatch between the handler's Handle method and the expected http.ApiHandler interface.

  2. Missing HTTP method specification: Unlike other routes that specify "GET /path", this route only specifies "/posts", which will match all HTTP methods.

Consider these fixes:

 func (r *Router) Posts() {
 	repo := repository.Posts{DB: r.Db}
 	abstract := handler.MakePostsHandler(&repo)
 
 	resolver := r.PipelineFor(
 		abstract.Handle,
 	)
 
-	r.Mux.HandleFunc("/posts", resolver)
+	r.Mux.HandleFunc("GET /posts", resolver)
 }

Please verify that the handler's Handle method signature is compatible with http.ApiHandler.

database/repository/posts.go (1)

28-28: Implement missing IsPublished filter logic.

The IsPublished filter is defined in PostFilters but not implemented in ApplyPostsFilters function, leading to incomplete filtering functionality.

This matches the previous review comment about the incomplete implementation. The ApplyPostsFilters function in database/repository/queries/posts.go needs to handle the IsPublished filter.

database/repository/pagination.go (1)

10-19: Remove redundant Page field as noted in previous review.

The Page field duplicates the functionality of CurrentPage, creating confusion and potential inconsistency in the API response.

As mentioned in the previous review, remove the Page field and use only CurrentPage. This will require updating the usage in database/repository/posts.go accordingly.

type Pagination[T any] struct {
	Data         []T   `json:"data"`
-	Page         int   `json:"page"`
	Total        int64 `json:"total"`
	CurrentPage  int   `json:"current_page"`
	PageSize     int   `json:"page_size"`
	TotalPages   int   `json:"total_pages"`
	NextPage     *int  `json:"next_page,omitempty"`
	PreviousPage *int  `json:"previous_page,omitempty"`
}
database/repository/queries/posts.go (1)

7-49: Implement missing IsPublished filter logic.

The IsPublished filter defined in PostFilters is not implemented in this function, making the filtering incomplete. A post should be considered published if PublishedAt is not NULL and is in the past.

Add the missing implementation:

func ApplyPostsFilters(filters *PostFilters, query *gorm.DB) {
	if filters == nil {
		return
	}

+	if filters.IsPublished != nil {
+		if *filters.IsPublished {
+			query.Where("posts.published_at IS NOT NULL AND posts.published_at <= NOW()")
+		} else {
+			query.Where("posts.published_at IS NULL OR posts.published_at > NOW()")
+		}
+	}
+
	// ... rest of the filters
}
handler/posts.go (1)

25-29: Address hardcoded parameters by parsing query parameters.

The filter and pagination parameters are still hardcoded as noted in previous reviews. These values should be extracted from the HTTP request's query parameters to make the endpoint functional.

Consider implementing query parameter parsing:

func (h *PostsHandler) Handle(w baseHttp.ResponseWriter, r *baseHttp.Request) *http.ApiError {
-	filters := queries.PostFilters{Title: ""}
-	pagination := repository.Pagination[database.Post]{
-		Page:     1,
-		PageSize: 10,
-	}
+	filters := queries.PostFilters{
+		Title: r.URL.Query().Get("title"),
+	}
+	
+	page := 1
+	if p := r.URL.Query().Get("page"); p != "" {
+		if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
+			page = parsed
+		}
+	}
+	
+	pageSize := 10
+	if ps := r.URL.Query().Get("page_size"); ps != "" {
+		if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 100 {
+			pageSize = parsed
+		}
+	}
+	
+	pagination := repository.Pagination[database.Post]{
+		Page:     page,
+		PageSize: pageSize,
+	}
handler/posts/transformer.go (1)

5-32: Fix field mapping bug in author transformation.

There's a critical bug on line 29 where p.UpdatedAt is used instead of p.Author.UpdatedAt for the author's UpdatedAt field.

This issue was also identified in the handler file. The same fix applies here:

		Author: UserData{
			UUID:              p.Author.UUID,
			FirstName:         p.Author.FirstName,
			LastName:          p.Author.LastName,
			Username:          p.Author.Username,
			DisplayName:       p.Author.DisplayName,
			Bio:               p.Author.Bio,
			PictureFileName:   p.Author.PictureFileName,
			ProfilePictureURL: p.Author.ProfilePictureURL,
			IsAdmin:           p.Author.IsAdmin,
			CreatedAt:         p.Author.CreatedAt,
-			UpdatedAt:         p.UpdatedAt,
+			UpdatedAt:         p.Author.UpdatedAt,
		},
🧹 Nitpick comments (5)
boost/router.go (1)

35-35: Consider using constructor pattern for repository.

The repository is initialized directly instead of using a constructor. For consistency with the handler pattern, consider adding a repository constructor.

-	repo := repository.Posts{DB: r.Db}
+	repo := repository.MakePosts(r.Db)
database/repository/posts.go (1)

35-38: Consider performance implications of separate count query.

The current approach executes two separate queries (count + data fetch). For better performance, consider implementing a single query approach or ensure proper indexing on filtered columns.

For high-traffic scenarios, consider:

  1. Caching count results when filters are static
  2. Adding database indexes on commonly filtered columns (title, published_at, author_id)
  3. Using window functions for count if the database supports it
handler/posts/response.go (2)

7-22: Consider adding omitempty tags for optional fields.

Some fields like Excerpt, Content, and CoverImageURL might be empty and should use omitempty in their JSON tags to avoid sending empty strings in responses.

type PostResponse struct {
	UUID          string     `json:"uuid"`
	Author        UserData   `json:"author"`
	Slug          string     `json:"slug"`
	Title         string     `json:"title"`
-	Excerpt       string     `json:"excerpt"`
-	Content       string     `json:"content"`
-	CoverImageURL string     `json:"cover_image_url"`
+	Excerpt       string     `json:"excerpt,omitempty"`
+	Content       string     `json:"content,omitempty"`
+	CoverImageURL string     `json:"cover_image_url,omitempty"`
	PublishedAt   *time.Time `json:"published_at"`
	CreatedAt     time.Time  `json:"created_at"`
	UpdatedAt     time.Time  `json:"updated_at"`

	// Associations
	Categories []CategoryData `json:"categories"`
	Tags       []TagData      `json:"tags"`
}

24-36: Add omitempty tags for optional user fields.

Fields like Bio, PictureFileName, and ProfilePictureURL are likely optional and should include omitempty tags.

type UserData struct {
	UUID              string    `json:"uuid"`
	FirstName         string    `json:"first_name"`
	LastName          string    `json:"last_name"`
	Username          string    `json:"username"`
	DisplayName       string    `json:"display_name"`
-	Bio               string    `json:"bio"`
-	PictureFileName   string    `json:"picture_file_name"`
-	ProfilePictureURL string    `json:"profile_picture_url"`
+	Bio               string    `json:"bio,omitempty"`
+	PictureFileName   string    `json:"picture_file_name,omitempty"`
+	ProfilePictureURL string    `json:"profile_picture_url,omitempty"`
	IsAdmin           bool      `json:"is_admin"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"updated_at"`
}
handler/posts/transformer.go (1)

34-66: Consider optimizing slice allocation for better performance.

Both MapCategories and MapTags functions could benefit from pre-allocating slices with known capacity to avoid multiple memory reallocations during append operations.

func MapCategories(categories []database.Category) []CategoryData {
-	var data []CategoryData
+	data := make([]CategoryData, 0, len(categories))

func MapTags(tags []database.Tag) []TagData {
-	var data []TagData
+	data := make([]TagData, 0, len(tags))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a536631 and 15c1a3b.

📒 Files selected for processing (12)
  • boost/app.go (2 hunks)
  • boost/router.go (3 hunks)
  • config/makefile/app.mk (1 hunks)
  • database/connection.go (1 hunks)
  • database/repository/pagination.go (1 hunks)
  • database/repository/posts.go (2 hunks)
  • database/repository/queries/filters.go (1 hunks)
  • database/repository/queries/posts.go (1 hunks)
  • go.mod (1 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/response.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
boost/app.go (1)
database/repository/posts.go (1)
  • Posts (11-15)
boost/router.go (3)
database/connection.go (1)
  • Connection (12-17)
database/repository/posts.go (1)
  • Posts (11-15)
handler/posts.go (1)
  • MakePostsHandler (18-22)
handler/posts/response.go (3)
database/repository/categories.go (1)
  • Categories (11-13)
database/repository/tags.go (1)
  • Tags (13-15)
database/repository/posts.go (1)
  • Posts (11-15)
handler/posts/transformer.go (2)
handler/posts/response.go (4)
  • PostResponse (7-22)
  • UserData (24-36)
  • CategoryData (38-45)
  • TagData (47-57)
database/model.go (2)
  • Category (86-98)
  • Tag (107-119)
database/repository/queries/posts.go (1)
database/repository/queries/filters.go (1)
  • PostFilters (9-16)
database/repository/queries/filters.go (1)
database/model.go (2)
  • Category (86-98)
  • Tag (107-119)
🔇 Additional comments (19)
config/makefile/app.mk (1)

28-28: Clarify the purpose of the -d flag addition.

What specific functionality does the -d flag enable for the air command? Please document this change in the comment above or in a commit message to help other developers understand the modification.

go.mod (1)

13-13: Good practice moving indirect dependency to direct.

This correctly reflects the direct usage of golang.org/x/text in the new filtering functionality.

boost/app.go (2)

45-45: Good integration of database connection.

The database connection is properly assigned to the router, following the established dependency injection pattern.


73-73: Consistent route registration pattern.

The posts route follows the same initialization pattern as other routes in the Boot method.

boost/router.go (2)

4-5: Good addition of required imports.

The new imports for database and repository packages are appropriately added.


17-17: Database field properly integrated.

The database connection field follows the existing struct pattern and enables repository access.

database/repository/posts.go (1)

7-7: LGTM! Import addition is appropriate.

The new import for the queries package is necessary for the ApplyPostsFilters function usage.

database/repository/pagination.go (2)

70-87: LGTM! Excellent generic transformation utility.

The MapPaginatedResult function is well-designed with clear documentation and proper type safety. The implementation correctly preserves all pagination metadata while transforming the data.


78-86: MapPaginatedResult is safe to keep as-is
MapPaginatedResult only references CurrentPage (not Page), so removing the redundant Page field from Pagination will not break this function. No changes required here.

database/repository/queries/filters.go (2)

9-16: LGTM! Well-designed filter structure.

The PostFilters struct is well-designed with appropriate field types. The use of *bool for IsPublished correctly allows for three states (true/false/nil), which is ideal for filtering logic.


18-42: Excellent sanitization approach with proper localization.

The getter methods provide consistent sanitization using locale-aware case conversion and trimming. This ensures reliable case-insensitive filtering across the application.

database/repository/queries/posts.go (2)

7-11: LGTM! Proper null check for defensive programming.

The null check for filters prevents potential nil pointer dereferences and handles the case where no filters are applied gracefully.


12-14: LGTM! Correct implementation of title filtering.

The title filter correctly uses case-insensitive partial matching with proper parameterization to prevent SQL injection.

handler/posts.go (4)

1-12: LGTM: Clean package structure and imports.

The package declaration and imports are well-organized with appropriate aliasing for the baseHttp package to avoid conflicts.


14-22: LGTM: Well-structured handler with dependency injection.

The PostsHandler struct and constructor follow good dependency injection patterns, making the code testable and maintainable.


33-37: LGTM: Improved error handling.

Good improvement from the previous log.Fatalf usage. The current implementation properly logs the error and returns an API error response without terminating the server.


39-42: LGTM: Clean data transformation.

The use of MapPaginatedResult with the transformer function provides a clean separation of concerns for data transformation.

handler/posts/transformer.go (2)

1-4: LGTM: Clean package structure.

The package declaration and minimal imports are appropriate for this transformer module.


34-49: LGTM: Correct category mapping implementation.

The MapCategories function correctly transforms database categories to API response format with proper field mappings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
handler/posts/transformer.go (1)

59-59: Fix misleading variable name in MapTags function.

The loop variable is named category but should be tag since it's iterating over tags.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15c1a3b and 1b5af9e.

📒 Files selected for processing (4)
  • database/repository/pagination.go (1 hunks)
  • database/repository/posts.go (2 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • database/repository/posts.go
  • database/repository/pagination.go
  • handler/posts.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
handler/posts/transformer.go (3)

34-34: Fix incorrect field mapping in Author data.

The Author's UpdatedAt field is incorrectly using the post's UpdatedAt value instead of the author's.


59-59: Fix misleading variable name in MapTags function.

The loop variable is named category but should be tag since it's iterating over tags. This could lead to confusion during maintenance.


77-91: Add validation for minimum pagination values.

The function should validate that page and pageSize are positive values to prevent potential issues with zero or negative pagination parameters.

database/repository/pagination.go (2)

32-32: Remove redundant Page field from Pagination struct.

The Page field is redundant as CurrentPage serves the same purpose. Using a single field improves clarity and reduces potential confusion.

Also applies to: 55-55, 57-57


65-73: Use CurrentPage consistently throughout pagination logic.

After removing the redundant Page field, all references should use CurrentPage consistently.

🧹 Nitpick comments (1)
database/repository/pagination.go (1)

44-47: Improve default page size handling.

Consider adding validation for the page number as well, similar to how page size is validated.

 func Paginate[T any](data []T, attr PaginationAttr) *Pagination[T] {
+	page := attr.Page
+	if page <= 0 {
+		page = 1
+	}
+
 	pSize := float64(attr.Limit)
 	if pSize <= 0 {
 		pSize = 10
 	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b5af9e and 879d719.

📒 Files selected for processing (4)
  • database/repository/pagination.go (1 hunks)
  • database/repository/posts.go (2 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • database/repository/posts.go
  • handler/posts.go
🔇 Additional comments (1)
database/repository/pagination.go (1)

102-110: Add missing Page field assignment in MapPaginatedResult.

The MapPaginatedResult function doesn't copy the Page field from the source pagination. This creates inconsistency in the returned pagination structure.

Apply this diff to fix the missing field:

 return &Pagination[D]{
 	Data:         mappedData,
+	Page:         source.Page,
 	Total:        source.Total,
 	CurrentPage:  source.CurrentPage,
 	PageSize:     source.PageSize,
 	TotalPages:   source.TotalPages,
 	NextPage:     source.NextPage,
 	PreviousPage: source.PreviousPage,
 }

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (6)
database/repository/posts.go (3)

18-21: Add input validation for pagination parameters.

The method should validate that pagination parameters are positive when provided to prevent potential issues with negative page numbers or zero/negative page sizes.


22-32: Fix potential query inconsistency between count and data queries.

The use of Distinct("posts.id, posts.published_at") on the main query but not on the count query could lead to incorrect pagination metadata. The count query should reflect the same distinct logic.


34-53: Remove redundant Distinct() call and optimize query execution.

The Distinct() call on line 42 is redundant since you already applied distinct selection earlier. This should be removed for cleaner code.

handler/posts/transformer.go (3)

25-37: Fix incorrect field mapping in Author data.

The Author's UpdatedAt field is incorrectly using the post's UpdatedAt value instead of the author's.


61-61: Fix misleading variable name in MapTags function.

The loop variable is named category but should be tag since it's iterating over tags. This could lead to confusion during maintenance.


75-99: Add validation for minimum pagination values.

The function should validate that page and pageSize are positive values to prevent potential issues with zero or negative pagination parameters.

🧹 Nitpick comments (1)
database/repository/pagination/pagination.go (1)

22-58: Add nil check and page bounds validation.

The function should handle edge cases more robustly:

  1. Add a nil check for the paginate parameter
  2. Validate that the current page is within reasonable bounds
func MakePagination[T any](data []T, paginate *Paginate) *Pagination[T] {
+	if paginate == nil {
+		return nil
+	}
+	
	pSize := float64(paginate.Limit)
	if pSize <= 0 {
		pSize = 10
	}

	totalPages := int(
		math.Ceil(paginate.GetNumItemsAsFloat() / pSize),
	)
+	
+	// Ensure current page is at least 1
+	currentPage := paginate.Page
+	if currentPage < 1 {
+		currentPage = 1
+	}

	pagination := Pagination[T]{
		Data:         data,
-		Page:         paginate.Page,
+		Page:         currentPage,
		Total:        paginate.GetNumItemsAsInt(),
		PageSize:     paginate.Limit,
		TotalPages:   totalPages,
		NextPage:     nil,
		PreviousPage: nil,
	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 879d719 and af6a663.

📒 Files selected for processing (5)
  • database/repository/pagination/paginate.go (1 hunks)
  • database/repository/pagination/pagination.go (1 hunks)
  • database/repository/posts.go (2 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • database/repository/pagination/paginate.go
  • handler/posts.go
🔇 Additional comments (3)
database/repository/posts.go (1)

7-8: LGTM! New imports are properly utilized.

The pagination and queries imports are correctly added and used in the new GetPosts method.

database/repository/pagination/pagination.go (2)

5-20: LGTM! Well-designed generic pagination structure.

The Pagination struct is well-designed with appropriate use of generics and pointer fields for optional next/previous page values. The JSON tags are correctly applied.


72-89: LGTM! Excellent generic transformation utility.

The HydratePagination function is well-implemented with proper generic type handling, comprehensive documentation, and correct preservation of pagination metadata. The transformation logic is clean and efficient.

@gocanto gocanto marked this pull request as draft July 22, 2025 03:13
@gocanto gocanto marked this pull request as ready for review July 22, 2025 04:18
@gocanto
Copy link
Collaborator Author

gocanto commented Jul 22, 2025

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Jul 22, 2025

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
database/connection.go (1)

76-78: Fix the GetSession method implementation - this critical issue was not properly addressed.

The method still returns an unconnected gorm.Session that will cause runtime errors when used in repository operations. This is the same fundamental issue identified in the previous review.

Apply this fix to create a proper GORM session connected to the database:

 func (c *Connection) GetSession() *gorm.Session {
-	return &gorm.Session{QueryFields: true}
+	return c.driver.Session(&gorm.Session{QueryFields: true})
 }

Alternatively, if you need to return a *gorm.DB configured with the session:

-func (c *Connection) GetSession() *gorm.Session {
-	return &gorm.Session{QueryFields: true}
+func (c *Connection) GetSession() *gorm.DB {
+	return c.driver.Session(&gorm.Session{QueryFields: true})
 }
🧹 Nitpick comments (1)
database/repository/queries/posts_filters.go (1)

38-42: Consider optimizing caser creation for better performance.

The sanitization logic is sound, but creating a new cases.Caser instance for each call could impact performance if called frequently.

Consider creating the caser as a package-level variable:

+var englishLowerCaser = cases.Lower(language.English)
+
 func (f PostFilters) sanitiseString(seed string) string {
-	caser := cases.Lower(language.English)
-
-	return strings.TrimSpace(caser.String(seed))
+	return strings.TrimSpace(englishLowerCaser.String(seed))
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af6a663 and 7569139.

📒 Files selected for processing (8)
  • database/connection.go (1 hunks)
  • database/repository/pagination/pagination.go (1 hunks)
  • database/repository/posts.go (2 hunks)
  • database/repository/queries/posts.go (1 hunks)
  • database/repository/queries/posts_filters.go (1 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/response.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • handler/posts.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • handler/posts/response.go
  • database/repository/posts.go
  • database/repository/pagination/pagination.go
  • handler/posts/transformer.go
🔇 Additional comments (5)
database/repository/queries/posts_filters.go (2)

9-16: Well-structured filter design with good tri-state logic.

The PostFilters struct is well-designed, particularly the use of *bool for IsPublished to handle three states (true, false, unset). The filtering fields cover comprehensive search criteria.


18-36: Clean getter pattern with consistent sanitization.

The getter methods provide a clean interface with consistent sanitization. Good separation of concerns.

database/repository/queries/posts.go (3)

8-11: Good defensive programming with nil check.

Proper nil check prevents potential panics when filters are not provided.


17-24: Excellent fix for OR condition grouping.

The text filtering now properly groups OR conditions in a single WHERE clause, addressing the previous review concern about logical grouping.


26-38: Great improvements to author filtering.

The author filtering now includes proper grouping with parentheses and uses ILIKE for partial matching, addressing both issues from the previous review. Good addition of soft delete protection.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
go.mod (1)

3-3: Invalid Go version specified

Go version 1.24 doesn't exist. The latest stable version is Go 1.22. This will cause build failures.

Apply this fix:

-go 1.24
+go 1.22
♻️ Duplicate comments (6)
database/connection.go (1)

76-78: Fix the GetSession method implementation

Similar to the existing Session() method issue, this returns a disconnected gorm.Session that won't work with database operations. The session must be created from the database connection.

Apply this fix:

 func (c *Connection) GetSession() *gorm.Session {
-	return &gorm.Session{QueryFields: true}
+	return c.driver.Session(&gorm.Session{QueryFields: true})
 }
database/repository/posts.go (2)

18-21: Add input validation for pagination parameters.

The method should validate that pagination parameters are positive to prevent potential issues with negative page numbers or invalid limits.


31-34: Ensure count query consistency with main query.

The count query should use the same distinct logic as the main query to ensure accurate pagination metadata, especially when joins from filtering are involved.

#!/bin/bash
# Verify if ApplyPostsFilters function uses joins that could affect count accuracy
ast-grep --pattern 'func ApplyPostsFilters($_, $_) {
  $$$
}'
database/repository/queries/posts.go (2)

45-51: Fix inconsistency between WHERE operators and parameter usage.

The WHERE clause uses exact match operators (=) but the parameters include wildcards (%), which won't work as expected. For partial matching, use ILIKE operators.

Apply this fix to make category filtering consistent:

			Where("("+
-				"LOWER(categories.slug) = ? OR LOWER(categories.name) = ? OR LOWER(categories.description) = ?"+
+				"LOWER(categories.slug) ILIKE ? OR LOWER(categories.name) ILIKE ? OR LOWER(categories.description) ILIKE ?"+
				")",

59-65: Fix inconsistency between WHERE operators and parameter usage.

Same issue as category filtering - using exact match operators (=) with wildcard parameters (%).

Apply this fix to make tag filtering consistent:

			Where("("+
-				"LOWER(tags.slug) = ? OR LOWER(tags.name) = ? OR LOWER(tags.description) = ?"+
+				"LOWER(tags.slug) ILIKE ? OR LOWER(tags.name) ILIKE ? OR LOWER(tags.description) ILIKE ?"+
				")",
handler/posts/transformer.go (1)

105-107: Implement filter parsing from HTTP request.

The function currently returns an empty PostFilters struct instead of parsing actual filter parameters from the HTTP request. This needs to be implemented to make filtering functional.

func GetFiltersFrom(r *baseHttp.Request) queries.PostFilters {
-	return queries.PostFilters{}
+	query := r.URL.Query()
+	return queries.PostFilters{
+		Title:    query.Get("title"),
+		Text:     query.Get("text"), 
+		Author:   query.Get("author"),
+		Category: query.Get("category"),
+		Tag:      query.Get("tag"),
+	}
}

Would you like me to help implement the complete filter parsing logic based on expected query parameters?

🧹 Nitpick comments (1)
database/repository/posts.go (1)

18-18: Consider using pointer for filters parameter consistency.

The method uses a value receiver for filters but a pointer for paginate. For consistency and to avoid unnecessary copying, consider using a pointer for filters as well.

-func (p Posts) GetPosts(filters queries.PostFilters, paginate pagination.Paginate) (*pagination.Pagination[database.Post], error) {
+func (p Posts) GetPosts(filters *queries.PostFilters, paginate *pagination.Paginate) (*pagination.Pagination[database.Post], error) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a536631 and 7569139.

📒 Files selected for processing (13)
  • boost/app.go (2 hunks)
  • boost/router.go (3 hunks)
  • config/makefile/app.mk (1 hunks)
  • database/connection.go (1 hunks)
  • database/repository/pagination/paginate.go (1 hunks)
  • database/repository/pagination/pagination.go (1 hunks)
  • database/repository/posts.go (2 hunks)
  • database/repository/queries/posts.go (1 hunks)
  • database/repository/queries/posts_filters.go (1 hunks)
  • go.mod (1 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/response.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
🔇 Additional comments (21)
database/repository/pagination/paginate.go (1)

1-24: Well-designed pagination utility struct.

The Paginate struct provides a clean foundation for pagination with appropriate type conversion methods and follows Go naming conventions effectively.

handler/posts/response.go (1)

47-54: Circular reference issue has been resolved.

The TagData struct no longer contains the Posts field that was mentioned in previous reviews, eliminating the circular reference concern. The current implementation is clean and appropriate for API responses.

database/repository/posts.go (1)

38-44: LGTM on query structure and preloading.

The preloading strategy and ordering are well-implemented for the posts retrieval functionality.

database/repository/queries/posts_filters.go (2)

9-16: Excellent filter struct design.

The PostFilters struct demonstrates good practices with:

  • Appropriate field types for different filter scenarios
  • Tri-state boolean using *bool for IsPublished
  • Clear field documentation for partial matching behavior

18-42: Well-implemented sanitization methods.

The getter methods with consistent sanitization using locale-aware case conversion (cases.Lower(language.English)) and whitespace trimming provide robust input handling for filtering operations.

database/repository/pagination/pagination.go (4)

5-6: Good constants for pagination bounds.

The MinPage and MaxLimit constants provide clear boundaries for pagination parameters.


13-21: Excellent generic pagination struct design.

The Pagination[T] struct demonstrates excellent Go generics usage with:

  • Comprehensive pagination metadata
  • Proper JSON tags with omitempty for optional fields
  • Pointer types for NextPage/PreviousPage to handle nil states cleanly

23-59: Robust MakePagination implementation.

The function handles edge cases well with defensive programming:

  • Fallback to default page size when invalid
  • Correct total pages calculation using math.Ceil
  • Proper boundary checking for next/previous page logic

73-90: Well-designed transformation utility.

The HydratePagination function provides clean type transformation while preserving all pagination metadata. The comprehensive documentation and clear type parameter descriptions make it highly maintainable.

handler/posts.go (2)

17-21: LGTM on the constructor implementation.

The constructor function properly initializes the PostsHandler with the provided repository dependency.


23-47: Good improvement on error handling and parameter extraction.

The handler correctly addresses the previously identified issues:

  • Uses structured logging instead of log.Fatalf
  • Extracts filter and pagination parameters from the HTTP request
  • Returns appropriate HTTP error responses without exposing internal details
database/repository/queries/posts.go (3)

13-15: LGTM on title filtering implementation.

The title filtering correctly uses ILIKE with wildcards for case-insensitive partial matching.


17-24: Good fix for text filtering logical grouping.

The OR conditions for text filtering are now properly grouped within a single WHERE clause, addressing the previous logical grouping issue.


26-38: Excellent implementation of author filtering.

The author filtering correctly:

  • Uses proper JOIN syntax
  • Includes soft delete checks
  • Groups OR conditions properly
  • Uses ILIKE with wildcards for flexible matching
handler/posts/transformer.go (4)

12-39: LGTM on post response transformation.

The transformation function correctly maps all post fields including the fixed UpdatedAt field mapping for the author (now uses p.Author.UpdatedAt instead of p.UpdatedAt).


41-56: Clean implementation of category transformation.

The category mapping function is well-structured and correctly transforms all category fields.


58-73: Good implementation of tag transformation.

The tag mapping function correctly transforms tag data without the variable naming issues noted in previous reviews.


75-103: Excellent pagination parameter parsing with proper validation.

The function now includes proper validation for pagination parameters, addressing the previous concern about minimum values. The implementation correctly:

  • Uses constants from the pagination package
  • Validates minimum page values
  • Enforces maximum limit constraints
config/makefile/app.mk (1)

28-28: LGTM! Development tooling improvement.

Adding the -d flag to the air command enhances the development workflow by enabling daemon/debug mode for live reloading.

boost/app.go (2)

73-73: Router.Posts method confirmed
The Posts() method is defined on the Router struct in boost/router.go, so the call in boost/app.go is valid.

• Location: boost/router.go, lines 34–43


45-45: Router struct’s Db field verified – changes approved

I’ve confirmed that in boost/router.go, the Router struct includes the field

Db *database.Connection

as expected. The added router.Posts() call in Boot aligns with the existing router setup pattern. No further changes needed—this is good to merge.

@gocanto gocanto marked this pull request as draft July 22, 2025 05:43
@gocanto gocanto marked this pull request as ready for review July 22, 2025 07:21
@gocanto
Copy link
Collaborator Author

gocanto commented Jul 22, 2025

@gemini-code-assist review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
pkg/http/request.go (1)

16-22: Consider simplifying the closer function structure.

The defer inside the closer function that's returned but not immediately called can be confusing. Consider a simpler approach:

-	closer := func() {
-		defer func(Body io.ReadCloser) {
-			if issue := Body.Close(); issue != nil {
-				slog.Error("ParseRequestBody: " + issue.Error())
-			}
-		}(r.Body)
-	}
+	closer := func() {
+		if issue := r.Body.Close(); issue != nil {
+			slog.Error("ParseRequestBody: " + issue.Error())
+		}
+	}
database/repository/queries/posts_filters.go (1)

36-40: Minor optimization opportunity: redundant whitespace trimming.

The sanitiseString method performs whitespace trimming twice - once in MakeStringable and once after ToLower(). Since ToLower() already trims whitespace, the additional TrimSpace call is redundant.

 func (f PostFilters) sanitiseString(seed string) string {
 	str := pkg.MakeStringable(seed)
 
-	return strings.TrimSpace(str.ToLower())
+	return str.ToLower()
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59f5245 and ffa800e.

📒 Files selected for processing (10)
  • boost/router.go (3 hunks)
  • database/repository/posts.go (2 hunks)
  • database/repository/queries/posts.go (1 hunks)
  • database/repository/queries/posts_filters.go (1 hunks)
  • handler/posts.go (1 hunks)
  • handler/posts/response.go (1 hunks)
  • handler/posts/transformer.go (1 hunks)
  • pkg/http/request.go (1 hunks)
  • pkg/http/response.go (1 hunks)
  • pkg/stringable.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • boost/router.go
  • database/repository/posts.go
  • database/repository/queries/posts.go
🔇 Additional comments (9)
pkg/stringable.go (1)

22-26: Well-implemented internationalization-aware case conversion.

The ToLower() method correctly uses the golang.org/x/text/cases package for proper Unicode case conversion with English language rules, followed by whitespace trimming. This approach ensures consistent case-insensitive filtering.

pkg/http/response.go (1)

75-87: Well-structured error constructor functions.

The new BadRequestError and NotFound functions follow a consistent pattern with the existing InternalError function, using appropriate HTTP status codes and formatted error messages. The consistent lowercase styling in error messages maintains uniformity across the API.

database/repository/queries/posts_filters.go (1)

8-34: Well-designed filter structure with consistent sanitization.

The PostFilters struct provides a clean interface for filtering posts with consistent sanitization across all filter fields. The getter methods ensure case-insensitive filtering by leveraging the new ToLower() method.

handler/posts/response.go (2)

55-62: Previous circular reference concern has been resolved.

The TagData struct no longer contains the Posts field that was flagged in previous reviews, eliminating the potential circular reference issue with PostResponse.Tags.


7-30: Well-structured API response contracts.

The request and response structs are well-designed with proper JSON tags, appropriate field types, and clear separation of concerns. The IndexRequestBody aligns with the filtering capabilities, and PostResponse provides comprehensive post data with nested associations.

handler/posts.go (1)

59-79: Well-implemented Show method.

The method properly handles edge cases with appropriate HTTP status codes and clear error messages. Good error handling pattern.

handler/posts/transformer.go (3)

14-41: Correct implementation of post response mapping.

The function properly maps all fields including the nested author data. The previously reported UpdatedAt field mapping issue has been correctly addressed.


60-75: Proper implementation with correct variable naming.

The function correctly uses 'tag' as the loop variable, addressing the previously reported naming issue.


107-114: Complete implementation of filter parsing.

The function properly maps request body fields to post filters, addressing the previously reported stub implementation issue.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces new API endpoints for listing and retrieving posts with filtering and pagination. The code includes new handler functions, repository methods, and data structures for handling post requests and responses. Several improvements can be made to error handling, query optimization, and code clarity.

@gocanto
Copy link
Collaborator Author

gocanto commented Jul 22, 2025

@coderabbitai summary

@coderabbitai
Copy link

coderabbitai bot commented Jul 22, 2025

✅ Actions performed

Summary regeneration triggered.

@gocanto gocanto merged commit cd21cb4 into main Jul 22, 2025
4 checks passed
@gocanto gocanto deleted the feature/posts branch July 22, 2025 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants