Skip to content

Conversation

@GautierDele
Copy link
Member

@GautierDele GautierDele commented Apr 16, 2025

Summary by CodeRabbit

  • New Features
    • Added support for applying access control to Laravel Scout search queries, enabling filtered search results based on user permissions.
  • Tests
    • Introduced comprehensive tests to verify access control logic on Scout queries.
  • Chores
    • Updated dependencies to include Laravel Scout for search functionality.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 16, 2025

Walkthrough

This update introduces support for applying access control logic to Laravel Scout search queries within the application. The Control and Perimeter classes are extended to provide methods and callbacks for modifying Scout query builders, mirroring existing Eloquent query support. The AccessServiceProvider now registers a macro on Scout's builder to apply these controls automatically. The test suite is expanded with new tests that verify the correct application of access controls to Scout queries, and the test model is updated to use the Scout Searchable trait. Additionally, the laravel/scout package is added as a development dependency.

Changes

File(s) Change Summary
composer.json Added laravel/scout as a development dependency.
src/AccessServiceProvider.php Added registration of a controlled macro on Scout's builder to apply access control logic for authenticated users.
src/Controls/Control.php Added methods to support access control on Scout queries: scoutQueried, applyScoutQueryControl, and noResultScoutQuery.
src/Perimeters/Perimeter.php Added $scoutQueryCallback property and methods for setting and applying Scout query callbacks.
tests/Feature/ControlsScoutQueryTest.php New test class verifying access control logic on Scout queries with various user perimeters and model states.
tests/Support/Access/Controls/ModelControl.php Added scoutQuery callbacks to perimeters for filtering Scout queries by boolean flags.
tests/Support/Models/Model.php Added the Laravel\Scout\Searchable trait to the test model.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Auth
    participant LaravelScoutBuilder as Scout\Builder
    participant AccessServiceProvider
    participant Control
    participant Perimeter

    User->>Scout\Builder: Initiate search query
    Scout\Builder->>AccessServiceProvider: controlled() macro
    AccessServiceProvider->>Control: Create control instance for model
    Control->>Control: scoutQueried(Scout\Builder, Auth::user())
    Control->>Perimeter: applyScoutQueryCallback(Scout\Builder, user)
    Perimeter-->>Control: Modified Scout\Builder
    Control-->>Scout\Builder: Return controlled query
Loading

Poem

A hop, a skip, to Scout we go,
Where queries now respect what rabbits know.
With perimeters set and controls in place,
Search results filtered with elegant grace.
Tests abound to keep things tight—
Now Scout and access both feel right!
🐇✨

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d975241 and 8aa1603.

📒 Files selected for processing (3)
  • src/AccessServiceProvider.php (2 hunks)
  • src/Controls/Control.php (4 hunks)
  • src/Perimeters/Perimeter.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Perimeters/Perimeter.php
  • src/AccessServiceProvider.php
  • src/Controls/Control.php

🪧 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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
Contributor

@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

🧹 Nitpick comments (7)
src/AccessServiceProvider.php (1)

49-58: Good implementation of conditional Scout integration.

The bootScoutBuilder method correctly checks for Scout's existence before adding functionality, making this integration truly optional.

Consider adding a PHPDoc comment to describe the purpose of this method:

+/**
+ * Register the Scout Builder macro if Laravel Scout is installed.
+ *
+ * @return void
+ */
 protected function bootScoutBuilder(): void
 {
     if (class_exists(Builder::class)) {
         Builder::macro('controlled', function (Builder $builder) {
             $control = $builder->model->newControl();

             return $control->queried($builder, Auth::user());
         });
     }
 }
tests/Support/Access/Controls/ModelControl.php (1)

29-31: Consider importing the Scout Builder class.

The Scout query filters are correctly implemented, but the code uses fully qualified class names instead of importing the class.

Consider adding an import for the Scout Builder class at the top of the file:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
+use Laravel\Scout\Builder as ScoutBuilder;
use Lomkit\Access\Controls\Control;

Then use the imported class name in the callbacks:

-                ->scoutQuery(function (\Laravel\Scout\Builder $query, Model $user) {
+                ->scoutQuery(function (ScoutBuilder $query, Model $user) {
                     return $query->where('is_shared', true);
                 })

Also applies to: 40-42, 51-53, 62-64

src/Controls/Control.php (3)

15-15: Make the TODO comment more descriptive.

The current TODO comment "change readme image" is vague and doesn't provide enough context about what needs to be changed or why.

-    // @TODO: change readme image
+    // @TODO: Update readme image to include the new Scout query functionality

135-154: Consider reducing code duplication with the query control logic.

The applyScoutQueryControl method is almost identical to applyQueryControl, differing only in type signatures and callback calls. This creates maintenance overhead as any logic changes would need to be duplicated in both methods.

Consider extracting the common logic into a template method or using a more generic approach that could handle both query types. For example:

protected function applyQueryControlGeneric($query, Model $user, string $type = 'eloquent'): mixed
{
    $noResultCallback = function ($query) use ($type) {
        return $type === 'eloquent' ? $this->noResultQuery($query) : $this->noResultScoutQuery($query);
    };

    foreach ($this->perimeters() as $perimeter) {
        if ($perimeter->applyAllowedCallback($user)) {
            $method = $type === 'eloquent' ? 'applyQueryCallback' : 'applyScoutQueryCallback';
            $query = $perimeter->$method($query, $user);

            $noResultCallback = function ($query) {return $query; };

            if (!$perimeter->overlays()) {
                return $query;
            }
        }
    }

    return $noResultCallback($query);
}

Then, the specific methods could use this generic implementation:

protected function applyQueryControl(Builder $query, Model $user): Builder
{
    return $this->applyQueryControlGeneric($query, $user, 'eloquent');
}

protected function applyScoutQueryControl(\Laravel\Scout\Builder $query, Model $user): \Laravel\Scout\Builder
{
    return $this->applyQueryControlGeneric($query, $user, 'scout');
}

168-171: Consider a more robust approach for ensuring no Scout results.

Using a field name like __NOT_A_VALID_FIELD__ to ensure no results might be fragile if a future database schema includes a field with that name.

Consider a more robust approach, such as:

- return $query->where('__NOT_A_VALID_FIELD__', 0);
+ return $query->whereRaw('1=0'); // Guaranteed to return no results

Or documenting the intention more clearly:

- return $query->where('__NOT_A_VALID_FIELD__', 0);
+ // Use an intentionally invalid field name to ensure no results are returned
+ // This approach is used because Scout doesn't support whereRaw
+ return $query->where('__lomkit_access_no_results__', 0);
tests/Feature/ControlsScoutQueryTest.php (2)

85-106: Consider removing unused test data.

In this test, you create models with is_client set to true, but these models aren't relevant to the test assertion which only checks for the is_shared condition.

 Model::factory()
-    ->state(['is_client' => true])
-    ->count(50)
-    ->create();
-Model::factory()
     ->state(['is_shared' => true])
     ->count(50)
     ->create();
-Model::factory()
-    ->state(['is_own' => true])
-    ->count(50)
-    ->create();

1-107: Consider adding tests for actual search results.

The current tests only verify that the correct conditions are added to the Scout query builder, but they don't verify that the actual search results are filtered correctly.

Consider adding additional assertions that execute the search and verify the results:

public function test_control_scout_queried_actually_filters_results(): void
{
    Auth::user()->update(['should_client' => true]);

    // Create models with different is_client values
    $clientModels = Model::factory()
        ->state(['is_client' => true])
        ->count(3)
        ->create();
    
    $nonClientModels = Model::factory()
        ->state(['is_client' => false])
        ->count(3)
        ->create();

    // Execute the controlled search
    $results = Model::search()
        ->controlled() // Assuming there's a 'controlled' macro that applies the current user's control
        ->get();
    
    // Assert that only client models are returned
    $this->assertCount(3, $results);
    foreach ($results as $result) {
        $this->assertTrue($result->is_client);
    }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 64303f7 and d975241.

📒 Files selected for processing (7)
  • composer.json (1 hunks)
  • src/AccessServiceProvider.php (2 hunks)
  • src/Controls/Control.php (4 hunks)
  • src/Perimeters/Perimeter.php (3 hunks)
  • tests/Feature/ControlsScoutQueryTest.php (1 hunks)
  • tests/Support/Access/Controls/ModelControl.php (4 hunks)
  • tests/Support/Models/Model.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
tests/Support/Models/Model.php (1)
tests/Support/Database/Factories/ModelFactory.php (1)
  • ModelFactory (8-28)
tests/Support/Access/Controls/ModelControl.php (2)
src/Perimeters/Perimeter.php (2)
  • scoutQuery (112-117)
  • query (105-110)
tests/Support/Models/Model.php (1)
  • Model (11-25)
src/Controls/Control.php (4)
src/Perimeters/Perimeter.php (4)
  • query (105-110)
  • applyAllowedCallback (65-68)
  • applyScoutQueryCallback (40-43)
  • overlays (134-137)
tests/Support/Models/Model.php (1)
  • Model (11-25)
tests/Support/Access/Controls/ModelControl.php (1)
  • perimeters (15-69)
src/Perimeters/OverlayPerimeter.php (1)
  • overlays (14-17)
src/Perimeters/Perimeter.php (1)
tests/Support/Models/Model.php (1)
  • Model (11-25)
tests/Feature/ControlsScoutQueryTest.php (3)
tests/Support/Models/Model.php (1)
  • Model (11-25)
tests/Support/Access/Controls/ModelControl.php (1)
  • ModelControl (13-70)
src/Controls/Control.php (1)
  • scoutQueried (101-104)
🪛 GitHub Actions: tests
tests/Support/Models/Model.php

[error] 11-11: PHP Fatal error: Trait "Laravel\Scout\Searchable" not found.

🔇 Additional comments (11)
composer.json (1)

19-19:

❓ Verification inconclusive

Good addition of Laravel Scout as a dev dependency.

This addition properly integrates Laravel Scout functionality while keeping it optional for production use, aligning with the PR objectives to support Scout queries.


🏁 Script executed:

#!/bin/bash
# Check Laravel Scout version compatibility with supported Laravel versions

# Display the supported Laravel versions in composer.json
echo "Supported Laravel versions:"
cat composer.json | grep "laravel/framework"

# Check Laravel Scout package compatibility information
curl -s https://packagist.org/packages/laravel/scout.json | jq '.package.versions["10.0.0"].require."illuminate/database"'

Length of output: 285


Attention: Verify Composer File Location & Version Constraint

The addition of Laravel Scout ("laravel/scout": "^10") appears to align with the PR’s objectives for supporting Scout queries. However, our automated checks revealed two points that need clarification:

  • The script was unable to locate a composer.json file at the repository root. Please confirm that composer.json is correctly placed and updated in the expected location.
  • The Packagist query for version 10.0.0 did not return any value for "illuminate/database", which might be due to using an incorrect version key or a change in the package’s dependency definitions. Verify that the version constraint and its associated dependencies are correctly defined for your intended usage.

Once these points are confirmed, the integration looks solid.

tests/Support/Access/Controls/ModelControl.php (1)

29-31:

❓ Verification inconclusive

Verify that the Scout query implementation is consistent with Eloquent.

Scout's query filtering works differently than Eloquent's. For Eloquent filters, you're using orWhere but for Scout filters you're using where. This might lead to inconsistent behavior.


🏁 Script executed:

#!/bin/bash
# Check if the Scout builder's where method works the same as Eloquent's orWhere

# Find the Control class that processes these queries to see how they're handled
rg -A 10 "function scoutQueried" --type php

Length of output: 767


Action: Validate Consistency of Scout Query Filters

  • The snippet in tests/Support/Access/Controls/ModelControl.php uses a Scout query closure that applies a simple where filter:
                  ->scoutQuery(function (\Laravel\Scout\Builder $query, Model $user) {
                      return $query->where('is_shared', true);
                  })
  • In contrast, Eloquent filter logic elsewhere uses orWhere, which means the filtering behavior might differ.
  • The scoutQueried method in src/Controls/Control.php delegates query modifications via applyScoutQueryControl(). This confirms that the Scout query is being processed as designed.
  • Since Laravel Scout’s filtering mechanism inherently supports only the where method (without an orWhere alternative), please verify that this difference is intentional. If an OR-type condition is needed for consistency, consider either revising the filtering logic or adding documentation to clarify the design choice.
src/Perimeters/Perimeter.php (4)

11-11: Good addition of the new property for Scout query callback.

The new protected $scoutQueryCallback property aligns well with the existing code style and design patterns in the class.


20-20: Initialize default Scout query callback correctly.

The default implementation properly returns the query unchanged, which is a sensible default that matches the pattern used for other callbacks.


40-43: Well-structured method for applying the Scout query callback.

This method follows the same pattern as the existing applyQueryCallback method, maintaining consistency in the codebase. The return type is correctly specified as \Laravel\Scout\Builder.


112-117: Good implementation of the fluent setter for Scout query callback.

The scoutQuery method maintains the same fluent interface pattern used elsewhere in the class, allowing for method chaining and maintaining a consistent API.

src/Controls/Control.php (1)

93-104: Good implementation of the Scout query control method.

The scoutQueried method follows the same pattern as the existing queried method, providing a consistent API extension for Scout queries. The method is well-documented with appropriate PHPDoc comments.

tests/Feature/ControlsScoutQueryTest.php (4)

6-18: Good test for the no-passing perimeter scenario.

This test properly verifies that when no perimeter passes, the system applies a condition that should yield no results. The assertion correctly checks the wheres property of the Scout query.


20-36: Well-structured test for client perimeter.

This test clearly sets up the necessary conditions, creates the appropriate model instances, and verifies that the client perimeter's Scout query callback is correctly applied.


38-59: Good test for overlayed perimeters.

This test properly verifies the behavior of an overlayed shared perimeter combined with a client perimeter, ensuring both conditions are applied to the Scout query.


61-83: Comprehensive test for shared and own perimeters.

This test validates that when both shared and own perimeters apply, the Scout query includes the appropriate conditions for both perimeters.

coderabbitai bot added a commit that referenced this pull request Apr 16, 2025
Docstrings generation was requested by @GautierDele.

* #11 (comment)

The following files were modified:

* `src/AccessServiceProvider.php`
* `src/Controls/Control.php`
* `src/Perimeters/Perimeter.php`
* `tests/Support/Access/Controls/ModelControl.php`
* `tests/Support/Models/Model.php`
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 16, 2025

Note

Generated docstrings for this pull request at #12

* 📝 Add docstrings to `feature/scout-queries`

Docstrings generation was requested by @GautierDele.

* #11 (comment)

The following files were modified:

* `src/AccessServiceProvider.php`
* `src/Controls/Control.php`
* `src/Perimeters/Perimeter.php`
* `tests/Support/Access/Controls/ModelControl.php`
* `tests/Support/Models/Model.php`

* Apply fixes from StyleCI

* ♻️ removed unecessary comments

* Apply fixes from StyleCI

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: StyleCI Bot <bot@styleci.io>
Co-authored-by: Gautier DELEGLISE <gautier.deleglise@gmail.com>
@GautierDele GautierDele merged commit 99b9d0d into master Apr 16, 2025
21 checks passed
@GautierDele GautierDele deleted the feature/scout-queries branch April 16, 2025 13:52
@coderabbitai coderabbitai bot mentioned this pull request Apr 16, 2025
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