Skip to content

Conversation

@nickpismenkov
Copy link
Contributor

@nickpismenkov nickpismenkov commented Jan 8, 2026

Note

Adds organization-name filtering to the admin users list when including organizations, and plumbs it through API, services, and repositories with total counts.

  • API: GET /v1/admin/users accepts search_by_name (only when include_organizations=true); OpenAPI docs and debug logs updated
  • Services/ports: list_users_with_organizations now takes search_by_name and returns (users, total); callers updated to use returned total
  • Database: UserRepository::list_with_organizations adds ILIKE-based org name filtering with escaped wildcards, adds a separate total-count query, and returns (rows, total); AdminCompositeRepository forwards param and total

Written by Cursor Bugbot for commit ab74384. This will update automatically on new commits. Configure here.

@cursor
Copy link

cursor bot commented Jan 8, 2026

You have run out of free Bugbot PR reviews for this billing cycle. This will reset on January 9.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @nickpismenkov, 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 introduces a significant improvement to the administrative user listing functionality by enabling filtering based on organization names. This enhancement allows administrators to more efficiently locate users associated with specific organizations. The change is implemented end-to-end, from the API query parameter definition down to the database query logic, ensuring robust and consistent behavior.

Highlights

  • API Endpoint Enhancement: The /admin/users endpoint now supports filtering users by organization name through a new optional query parameter, search_by_name.
  • Service Layer Integration: The list_users_with_organizations function across the AdminService and AdminRepository traits, and their implementations, has been updated to accept and propagate the new search_by_name parameter.
  • Database Query Modification: The underlying SQL query for listing users with organizations has been modified to include a WHERE clause that performs a case-insensitive partial match on the organization name using ILIKE.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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 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. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

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.

@nickpismenkov
Copy link
Contributor Author

@claude review

gemini-code-assist[bot]

This comment was marked as outdated.

@claude
Copy link

claude bot commented Jan 8, 2026

Code Review - PR #348

I've reviewed this pull request and found 2 critical issues that need to be addressed:

⚠️ Critical Issues

1. Total Count Calculation is Incorrect with Filtering (crates/services/src/admin/mod.rs:197-201)

The get_active_user_count() returns the total count of ALL active users, but when search_by_name is provided, the total should reflect only the filtered users. This breaks pagination UX - the frontend will show incorrect "X of Y results" information.

Current code:

let total = self
    .repository
    .get_active_user_count()  // ❌ Returns all users, ignores filter
    .await
    .map_err(|e| AdminError::InternalError(e.to_string()))?;

Fix: Either:

  • Option A: Add a get_filtered_user_count(search_by_name: Option<String>) method to AdminRepository
  • Option B: Return the count from the filtered query itself (using COUNT(*) OVER() window function)

Example Option B:

// In user.rs query, add:
SELECT DISTINCT ON (u.id)
    u.*,
    COUNT(*) OVER() as total_count,  -- Add this
    o.id as organization_id,
    ...
WHERE u.is_active = true
  AND ($3::TEXT IS NULL OR o.name ILIKE $3 || '%')

2. SQL Pattern Matching is Vulnerable to Special Characters (crates/database/src/repositories/user.rs:296)

The ILIKE $3 || '%' pattern is constructed by appending %, but user input can contain ILIKE wildcards (%, _) which will be interpreted as pattern matchers instead of literal characters.

Example exploit:

  • User searches for % → matches all organizations
  • User searches for test_org_ matches any single character

Current code:

WHERE u.is_active = true
  AND ($3::TEXT IS NULL OR o.name ILIKE $3 || '%')  -- ❌ No escaping

Fix: Escape the user input before using it in ILIKE:

// In user.rs before the query:
let escaped_search = search_by_name.as_ref().map(|s| {
    s.replace('\\', '\\\\')
     .replace('%', '\\%')
     .replace('_', '\\_')
});

// Then use escaped_search in the query
&[&limit, &offset, &escaped_search]

Or use PostgreSQL's LIKE with explicit escaping:

AND ($3::TEXT IS NULL OR o.name ILIKE ($3 || '%') ESCAPE '\\'

✅ Positives

  • SQL injection is properly prevented via parameterized queries ($1, $2, $3)
  • Case-insensitive search with ILIKE is appropriate
  • Prefix matching (ILIKE $3 || '%') has good index support if there's a btree(lower(name)) index

⚠️ Status: Changes Requested

These issues will cause incorrect pagination totals and unexpected wildcard matching behavior. Please fix before merging.

@nickpismenkov
Copy link
Contributor Author

@claude review

@claude
Copy link

claude bot commented Jan 8, 2026

Code Review - PR #348 (Follow-up)

I've reviewed this pull request and all previously identified critical issues have been resolved. However, I found one critical logic issue that needs to be addressed:

⚠️ Critical Issue

Incorrect Filter Logic with LEFT JOIN (crates/database/src/repositories/user.rs:304)

The filter condition AND ($3::TEXT IS NULL OR o.name ILIKE ($3 || '%') ESCAPE '\') is applied in the WHERE clause with a LEFT JOIN, which causes incorrect results:

Problem:

  • Users without organizations have o.name = NULL
  • The condition o.name ILIKE (...) evaluates to NULL (not FALSE) when o.name is NULL
  • SQL's three-valued logic means NULL AND anything = NULL, which filters out those rows
  • Result: When search_by_name is provided, users without organizations are incorrectly excluded from results

Example scenario:

  • User A has organization "Acme Corp"
  • User B has no organization (NULL)
  • Search for "Acme" → Only User A is returned ✅
  • Search for anything → User B is incorrectly excluded ❌

Fix: Move the filter to the JOIN condition instead of WHERE

This way:

  • When search_by_name = NULL: All users are included (current behavior preserved)
  • When search_by_name = 'Acme': User A (with "Acme Corp") → returned with org info ✅ AND User B (no org) → returned with org_data = None ✅ (not excluded)

Alternative approach (if you want to exclude users without matching orgs):
If the intention is to ONLY show users with matching organizations (excluding users without orgs when filtering), then the current logic is correct but the API documentation should be updated to clarify this behavior.


✅ Previous Issues - Confirmed Fixed

  1. Total Count Calculation - Now uses COUNT(*) OVER() window function to get filtered count ✅
  2. SQL Wildcard Escaping - Properly escapes backslash, percent, and underscore characters ✅

📋 Recommendation

Clarify the expected behavior:

  1. Should searching by org name exclude users without organizations?
  2. Or should it include all users, with only the org info filtered?

The current implementation does #1, but most UIs expect #2. Please confirm the desired behavior and adjust the query accordingly.


⚠️ Status: Changes Requested

Please fix the LEFT JOIN filter logic or clarify/document the intended behavior of excluding users without organizations when filtering.

nickpismenkov and others added 2 commits January 9, 2026 08:45
Support partial match

Co-authored-by: Pierre LE GUEN <26087574+PierreLeGuen@users.noreply.github.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@nickpismenkov
Copy link
Contributor Author

@claude review

@claude
Copy link

claude bot commented Jan 9, 2026

Code Review - PR #348 (Final Review)

Context from Previous Reviews

This is a follow-up review. Previous feedback identified:

  1. Fixed: Total count calculation now uses filtered query instead of get_active_user_count()
  2. Fixed: SQL wildcard escaping properly implemented
  3. Fixed: Filter logic with LEFT JOIN corrected using OR o.id IS NULL clause

✅ All Critical Issues Resolved

I've reviewed the current implementation and all previously identified critical issues have been addressed.

The implementation correctly:

  • Calculates filtered total counts for accurate pagination
  • Escapes SQL wildcards to prevent pattern injection
  • Includes users without organizations when filtering
  • Follows the repository's hexagonal architecture pattern
  • Adheres to logging/privacy guidelines (only logs IDs, not content)

The PR is ready to merge. ✅

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.

3 participants