Skip to content

feat: Blog Groups - #27

Open
phedayat wants to merge 11 commits into
JulienTant:mainfrom
phedayat:feat-blog-groups
Open

feat: Blog Groups#27
phedayat wants to merge 11 commits into
JulienTant:mainfrom
phedayat:feat-blog-groups

Conversation

@phedayat

@phedayat phedayat commented Jul 26, 2026

Copy link
Copy Markdown

Summary

Adds support for blog groups, allowing users to quickly pull articles from many specific blogs at once. Good for organization as well.

Test plan

  • golangci-lint run passes
  • gotestsum -- ./... passes
  • Manually verified (if applicable)

Summary by CodeRabbit

  • New Features
    • Added optional groups when adding and tracking blogs.
    • Added group filtering to blog listings, scans, and article views.
    • Group matching is case-insensitive and requires an exact match.
    • Blog listings now display group information when available.
  • Bug Fixes
    • Group filtering now returns all matching blogs and articles, while clearly reporting when no matches are found.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds blog groups with database persistence, case-insensitive exact filtering, CLI flags, group-scoped scanning and article listing, migration support, unit tests, and end-to-end coverage.

Changes

Blog group support

Layer / File(s) Summary
Persist and query blog groups
internal/model/model.go, internal/storage/..., internal/storage/migrations/...
Adds Blog.Group, stores it in group_name, loads it from queries, and filters blogs and articles by exact case-insensitive group matches.
Propagate group filters through services
internal/controller/..., internal/scanner/...
Extends blog creation, article retrieval, and all-blog scanning with group arguments and adds corresponding tests.
Expose group-aware CLI commands
internal/cli/...
Adds group flags to blog creation, listing, scanning, and article commands, including group-aware empty-state messages and optional filter handling.
Verify grouped command behavior
e2e/e2e_test.go, e2e/expected/*
Covers grouped blog display, filtering, scanning, article listing, multiple matches, and prefix non-matches through end-to-end expected outputs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Controller
  participant Scanner
  participant Database
  CLI->>Controller: Add blog with group
  Controller->>Database: Persist group_name
  CLI->>Database: List blogs by group
  CLI->>Scanner: Scan all blogs by group
  Scanner->>Database: Load matching blogs
  Database-->>Scanner: Group-filtered blogs
  CLI->>Controller: List articles by group
  Controller->>Database: Query group-filtered articles
  Database-->>Controller: Matching articles
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and directly matches the main change: adding blog group support.
Description check ✅ Passed The description follows the required Summary and Test plan template and includes relevant validation details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands.

@phedayat phedayat changed the title Blog Groups feat: Blog Groups Jul 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/storage/database.go`:
- Around line 163-165: Update both group-name filters in
internal/storage/database.go at lines 163-165 and 395-397 to wrap the
LOWER(group_name) SQL predicates with Squirrel’s expression API, such as
sq.Expr, before passing them to query.Where; preserve the existing conditions
and bound group-name arguments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d6b04e9f-72f3-49ee-a750-49ac0160a3f8

📥 Commits

Reviewing files that changed from the base of the PR and between 8d18c6e and 5845685.

📒 Files selected for processing (21)
  • e2e/e2e_test.go
  • e2e/expected/24_add_with_group.txt
  • e2e/expected/25_blogs_shows_group.txt
  • e2e/expected/26_blogs_filter_group.txt
  • e2e/expected/27_blogs_filter_group_no_match.txt
  • e2e/expected/28_scan_filter_group.txt
  • e2e/expected/29_scan_filter_group_no_match.txt
  • e2e/expected/30_articles_filter_group.txt
  • e2e/expected/31_blogs_filter_group_multi.txt
  • e2e/expected/32_blogs_filter_group_prefix_no_match.txt
  • internal/cli/commands.go
  • internal/cli/commands_test.go
  • internal/controller/controller.go
  • internal/controller/controller_test.go
  • internal/model/model.go
  • internal/scanner/scanner.go
  • internal/scanner/scanner_test.go
  • internal/storage/database.go
  • internal/storage/database_test.go
  • internal/storage/migrations/000004_add_blog_group.down.sql
  • internal/storage/migrations/000004_add_blog_group.up.sql

Comment on lines +163 to +165
if groupName != nil && *groupName != "" {
query = query.Where("LOWER(group_name) = LOWER(?)", *groupName)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)internal/storage/database\.go$|(^|/)go\.mod$'

echo "== outline =="
ast-grep outline internal/storage/database.go --view expanded || true

echo "== relevant lines =="
cat -n internal/storage/database.go | sed -n '130,180p;370,410p'

echo "== Where usages in file with raw-looking strings =="
rg -n '\.Where\(' internal/storage/database.go

Repository: JulienTant/blogwatcher-cli

Length of output: 7129


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== imports and sq package alias =="
cat -n internal/storage/database.go | sed -n '1,40p'

echo "== go module squirrel declarations =="
cat -n go.mod | sed -n '1,160p'

echo "== search for SqExpr / Raw SQL / builder helpers =="
rg -n 'type Sq|func sq::|newSquirrel|RawExpr|Where\("LOWER|Use\(" |Build|SqlString|string\(' .

Repository: JulienTant/blogwatcher-cli

Length of output: 5490


🌐 Web query:

Masterminds/squirrel v1.5.4 Where string function signature

💡 Result:

In Masterminds/squirrel version 1.5.4, the Where function signature for builders (such as SelectBuilder and StatementBuilderType) is: func (b Builder) Where(pred interface{}, args...interface{}) Builder The pred argument accepts several types, including a string representing a SQL expression [1][2]. When providing a string, any required SQL placeholders should be passed as subsequent arguments in the args variadic parameter [1][3]. If the pred argument is a string containing placeholders, Squirrel will use the provided args to bind the values to those placeholders [1][3]. If pred is an empty string or nil, the call is ignored [1]. The function will panic if the provided pred is not of a supported type (such as string, map[string]interface{}, or a Sqlizer implementation) [1][4].

Citations:


🌐 Web query:

Masterminds/squirrel v1.5.4 Where string accepts SQL function documentation

💡 Result:

In Masterminds/squirrel v1.5.4, the Where method does not have a specific "SQL function" mode, but it fully supports passing custom SQL strings, which allows you to use SQL functions directly [1]. When using the Where method with a string argument, Squirrel treats the string as a raw SQL expression [1]. If the expression contains placeholders (such as the standard? character), you must provide the corresponding arguments as additional parameters to the Where method [1]. For example, to use a SQL function like UPPER or a custom function: users.Where("UPPER(name) =?", "ALICE") If your SQL function requires complex syntax or does not use standard placeholders, you can embed it directly into the string: users.Where("created_at > NOW - INTERVAL '1 day'") The library's documentation explicitly notes that when you pass a string to Where, it is treated as a SQL expression, and it will handle placeholder substitution (and placeholder formatting, if configured) as needed [1][2]. If your use case is highly complex or dynamic, you can also consider creating custom types that implement the Sqlizer interface, which allows for more programmatic control over how the SQL is generated [3].

Citations:


🌐 Web query:

Masterminds/squirrel v1.5.4 pkg.go.dev select.go Where SQL template handling

💡 Result:

The Masterminds/squirrel library (v1.5.4) does not use traditional "SQL templates" (such as Go's text/template or html/template packages) to handle SQL generation [1][2]. Instead, it uses a fluent, builder-based API to construct SQL queries programmatically [2][3]. In select.go, the ToSql method generates a SQL string and a slice of arguments by concatenating various SQL clauses (like SELECT, FROM, WHERE, JOIN) that have been added to the builder [1][4]. The internal logic relies on: 1. Builder Composition: The SelectBuilder accumulates different parts of the query (e.g., columns, joins, where clauses) into a selectData struct [1][5]. 2. Placeholder Substitution: Once the query parts are concatenated into a raw SQL string, Squirrel uses the PlaceholderFormat associated with the builder to replace placeholders (typically "?") with the specific format required by the database driver, such as Dollar signs ($1, $2, etc., for PostgreSQL) [1][4][6]. 3. Argument Handling: Parameters provided to methods like Where or Columns are tracked in a slice of interfaces (args []interface{}), which are returned alongside the final SQL string to ensure safe query execution [1][7][6]. If you are looking for ways to handle complex SQL structures, Squirrel allows you to inject raw SQL fragments using methods like Expr, or by using custom types that implement the Sqlizer interface, which requires a ToSql method [7][8][5]. It is not a template engine; it is a query builder [2][3].

Citations:


Use a Squirrel SQL expression for the group filters.

Both group predicates pass raw SQL strings into Where. Wrap them with Squirrel’s SQL-expression API/converter instead; for example, query.Where(sq.Expr("LOWER(group_name) = LOWER(?)", *groupName)) so the grouped-blog article filter is not evaluated as an existing string predicate.

📍 Affects 1 file
  • internal/storage/database.go#L163-L165 (this comment)
  • internal/storage/database.go#L395-L397
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/database.go` around lines 163 - 165, Update both group-name
filters in internal/storage/database.go at lines 163-165 and 395-397 to wrap the
LOWER(group_name) SQL predicates with Squirrel’s expression API, such as
sq.Expr, before passing them to query.Where; preserve the existing conditions
and bound group-name arguments.

Source: Coding guidelines

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.

1 participant