Skip to content

feat(sponsors): add bulk upsert endpoint for sponsor services statistics#556

Open
romanetar wants to merge 1 commit into
mainfrom
feature/bulk-sponsor-statistics
Open

feat(sponsors): add bulk upsert endpoint for sponsor services statistics#556
romanetar wants to merge 1 commit into
mainfrom
feature/bulk-sponsor-statistics

Conversation

@romanetar

@romanetar romanetar commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

ref https://app.clickup.com/t/9014802374/86b8pwxha?comment=90140214990302

Summary by CodeRabbit

  • New Features

    • Added a bulk update API endpoint for sponsor services statistics, enabling simultaneous updates to multiple sponsors' tracking metrics (forms quantity, purchases quantity, pages quantity, documents quantity).
  • Tests

    • Added test coverage for the bulk update endpoint, including success cases and validation error handling.

Signed-off-by: romanetar <roman_ag@hotmail.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request adds a complete bulk update endpoint for sponsor services statistics. Changes span validation rules, service interface and implementation, HTTP controller with OpenAPI documentation, route registration, database migration for endpoint authorization, and comprehensive test coverage.

Changes

Bulk Sponsor Statistics Update Feature

Layer / File(s) Summary
Service Validation Rules and Contract
app/Http/Controllers/Apis/Protected/Summit/Factories/SponsorServicesStatisticsValidationRulesFactory.php, app/Services/Model/ISummitSponsorService.php
Validation rules factory adds buildForBulkUpdate() enforcing *.sponsor_id as required integer and optional quantity fields. Service interface declares bulkUpdateSponsorServicesStatistics() contract returning SponsorStatistics[].
Service Implementation and Business Logic
app/Services/Model/Imp/SummitSponsorService.php
Implements transactional bulk update: iterates payloads, resolves sponsors by ID, creates statistics via factory if absent or populates existing, accumulates results, throws EntityNotFoundException on missing sponsor.
Controller Endpoint and HTTP Routing
app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorApiController.php, routes/api_v1.php
Controller adds bulkUpdateSponsorServicesStatistics() with OpenAPI metadata, validates payload, invokes service, serializes results to HTTP 200. Route registers authenticated PUT endpoint at /api/v1/summits/{id}/sponsors/all/sponsorservices-statistics/bulk.
Endpoint Registration and Configuration
database/migrations/config/Version20260611155110.php, database/seeders/ApiEndpointsSeeder.php
Migration seeds endpoint with PUT method, WriteSummitData scope, and authorization for SuperAdmins, Administrators, and SummitAdministrators. Seeder confirms configuration.
Test Coverage
tests/oauth2/OAuth2SummitSponsorApiTest.php
Happy path test verifies bulk update of multiple sponsors returns 200 with correct statistics. Validation test confirms invalid payload returns 412.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • smarcet

Poem

A rabbit hops through sponsor stats so fine, 🐰
Now bulk updates in batches align,
Validation rules ensure each ID's right,
Routes and tests shine bright,
The endpoint dances in the summit's light! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a bulk upsert endpoint for sponsor services statistics, which is the primary feature across all modified files.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/bulk-sponsor-statistics

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 and usage tips.

@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-556/

This page is automatically updated on each push to this PR.

@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: 2

🧹 Nitpick comments (1)
tests/oauth2/OAuth2SummitSponsorApiTest.php (1)

1662-1701: ⚡ Quick win

Consider verifying additional response fields for completeness.

The test verifies forms_qty for both results but doesn't assert the other quantity fields sent in the first payload (purchases_qty, pages_qty, documents_qty). Adding these assertions would provide more comprehensive coverage and catch potential field mapping issues.

✅ Suggested additional assertions
     $this->assertIsArray($results);
     $this->assertCount(2, $results);
     $this->assertEquals(100, $results[0]->forms_qty);
+    $this->assertEquals(200, $results[0]->purchases_qty);
+    $this->assertEquals(300, $results[0]->pages_qty);
+    $this->assertEquals(400, $results[0]->documents_qty);
     $this->assertEquals(10, $results[1]->forms_qty);
🤖 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 `@tests/oauth2/OAuth2SummitSponsorApiTest.php` around lines 1662 - 1701, In
testBulkUpdateSponsorServicesStatistics, extend the assertions after decoding
$results to verify the other fields returned by
bulkUpdateSponsorServicesStatistics: assert that $results[0]->purchases_qty,
$results[0]->pages_qty and $results[0]->documents_qty equal 200, 300 and 400
respectively, and also assert the behavior for the second payload (e.g.
$results[1]->purchases_qty/$results[1]->pages_qty/$results[1]->documents_qty are
null or unchanged) to ensure all sent fields are correctly mapped in the
response.
🤖 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
`@app/Http/Controllers/Apis/Protected/Summit/Factories/SponsorServicesStatisticsValidationRulesFactory.php`:
- Around line 36-44: The buildForBulkUpdate validation allows nested keys but
doesn't require each top-level bulk item to be an array; add a rule forcing each
element to be an array so malformed non-array items fail validation. In
buildForBulkUpdate (method buildForBulkUpdate) add a rule '*' => 'array' (or
'*'=> ['array']) before the '*.sponsor_id' / '*.forms_qty' etc. entries so each
bulk element is validated as an array prior to the nested wildcard checks.

In `@tests/oauth2/OAuth2SummitSponsorApiTest.php`:
- Around line 1703-1729: The
testBulkUpdateSponsorServicesStatisticsValidationError assigns $response but
never uses it and only asserts HTTP 412; update the test to consume the response
from the action call and assert the error412 response structure (ensure the JSON
contains top-level keys "message", "errors", and "code" per the error412 helper
pattern) instead of leaving $response unused—locate the action invocation to
OAuth2SummitSponsorApiController@bulkUpdateSponsorServicesStatistics and add
assertions that decode the response body and verify those fields.

---

Nitpick comments:
In `@tests/oauth2/OAuth2SummitSponsorApiTest.php`:
- Around line 1662-1701: In testBulkUpdateSponsorServicesStatistics, extend the
assertions after decoding $results to verify the other fields returned by
bulkUpdateSponsorServicesStatistics: assert that $results[0]->purchases_qty,
$results[0]->pages_qty and $results[0]->documents_qty equal 200, 300 and 400
respectively, and also assert the behavior for the second payload (e.g.
$results[1]->purchases_qty/$results[1]->pages_qty/$results[1]->documents_qty are
null or unchanged) to ensure all sent fields are correctly mapped in the
response.
🪄 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

Run ID: e98a607a-41c9-411c-9154-ac38ff443034

📥 Commits

Reviewing files that changed from the base of the PR and between cfc7642 and 674a91f.

📒 Files selected for processing (8)
  • app/Http/Controllers/Apis/Protected/Summit/Factories/SponsorServicesStatisticsValidationRulesFactory.php
  • app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorApiController.php
  • app/Services/Model/ISummitSponsorService.php
  • app/Services/Model/Imp/SummitSponsorService.php
  • database/migrations/config/Version20260611155110.php
  • database/seeders/ApiEndpointsSeeder.php
  • routes/api_v1.php
  • tests/oauth2/OAuth2SummitSponsorApiTest.php

Comment on lines +36 to +44
public static function buildForBulkUpdate(array $payload = []): array
{
return [
'*.sponsor_id' => 'required|integer',
'*.forms_qty' => 'sometimes|integer',
'*.purchases_qty' => 'sometimes|integer',
'*.pages_qty' => 'sometimes|integer',
'*.documents_qty' => 'sometimes|integer',
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate each bulk element as an array before nested wildcard checks.

Line 39 validates nested keys, but each top-level element is not required to be an array. A malformed object-shaped payload can bypass validation and then fail at Line 1289 in app/Services/Model/Imp/SummitSponsorService.php when $item['sponsor_id'] is accessed, producing a 500 instead of a 412 validation response.

Suggested fix
     public static function buildForBulkUpdate(array $payload = []): array
     {
         return [
+            '*'               => 'required|array',
             '*.sponsor_id'    => 'required|integer',
             '*.forms_qty'     => 'sometimes|integer',
             '*.purchases_qty' => 'sometimes|integer',
             '*.pages_qty'     => 'sometimes|integer',
             '*.documents_qty' => 'sometimes|integer',
         ];
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static function buildForBulkUpdate(array $payload = []): array
{
return [
'*.sponsor_id' => 'required|integer',
'*.forms_qty' => 'sometimes|integer',
'*.purchases_qty' => 'sometimes|integer',
'*.pages_qty' => 'sometimes|integer',
'*.documents_qty' => 'sometimes|integer',
];
public static function buildForBulkUpdate(array $payload = []): array
{
return [
'*' => 'required|array',
'*.sponsor_id' => 'required|integer',
'*.forms_qty' => 'sometimes|integer',
'*.purchases_qty' => 'sometimes|integer',
'*.pages_qty' => 'sometimes|integer',
'*.documents_qty' => 'sometimes|integer',
];
}
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 36-36: Avoid unused parameters such as '$payload'. (undefined)

(UnusedFormalParameter)

🤖 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
`@app/Http/Controllers/Apis/Protected/Summit/Factories/SponsorServicesStatisticsValidationRulesFactory.php`
around lines 36 - 44, The buildForBulkUpdate validation allows nested keys but
doesn't require each top-level bulk item to be an array; add a rule forcing each
element to be an array so malformed non-array items fail validation. In
buildForBulkUpdate (method buildForBulkUpdate) add a rule '*' => 'array' (or
'*'=> ['array']) before the '*.sponsor_id' / '*.forms_qty' etc. entries so each
bulk element is validated as an array prior to the nested wildcard checks.

Comment on lines +1703 to +1729
public function testBulkUpdateSponsorServicesStatisticsValidationError()
{
$params = [
'id' => self::$summit->getId(),
];

// forms_qty is a string instead of integer — should fail validation
$data = [
[
'sponsor_id' => self::$sponsors[0]->getId(),
'forms_qty' => 'not-an-integer',
],
];

$response = $this->action(
"PUT",
"OAuth2SummitSponsorApiController@bulkUpdateSponsorServicesStatistics",
$params,
[],
[],
[],
$this->getAuthHeaders(),
json_encode($data)
);

$this->assertResponseStatus(412);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Address unused variable and verify error response structure.

The $response variable is assigned but never used. Additionally, the test only verifies the HTTP 412 status but doesn't validate the error response structure. Based on the error412 helper pattern, the response should contain message, errors, and code fields.

🧪 Proposed fix
     $response = $this->action(
         "PUT",
         "OAuth2SummitSponsorApiController@bulkUpdateSponsorServicesStatistics",
         $params,
         [],
         [],
         [],
         $this->getAuthHeaders(),
         json_encode($data)
     );

+    $content = $response->getContent();
     $this->assertResponseStatus(412);
+    $error = json_decode($content);
+    $this->assertNotNull($error);
+    $this->assertEquals('Validation Failed', $error->message);
+    $this->assertNotEmpty($error->errors);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function testBulkUpdateSponsorServicesStatisticsValidationError()
{
$params = [
'id' => self::$summit->getId(),
];
// forms_qty is a string instead of integer — should fail validation
$data = [
[
'sponsor_id' => self::$sponsors[0]->getId(),
'forms_qty' => 'not-an-integer',
],
];
$response = $this->action(
"PUT",
"OAuth2SummitSponsorApiController@bulkUpdateSponsorServicesStatistics",
$params,
[],
[],
[],
$this->getAuthHeaders(),
json_encode($data)
);
$this->assertResponseStatus(412);
}
public function testBulkUpdateSponsorServicesStatisticsValidationError()
{
$params = [
'id' => self::$summit->getId(),
];
// forms_qty is a string instead of integer — should fail validation
$data = [
[
'sponsor_id' => self::$sponsors[0]->getId(),
'forms_qty' => 'not-an-integer',
],
];
$response = $this->action(
"PUT",
"OAuth2SummitSponsorApiController@bulkUpdateSponsorServicesStatistics",
$params,
[],
[],
[],
$this->getAuthHeaders(),
json_encode($data)
);
$content = $response->getContent();
$this->assertResponseStatus(412);
$error = json_decode($content);
$this->assertNotNull($error);
$this->assertEquals('Validation Failed', $error->message);
$this->assertNotEmpty($error->errors);
}
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 1717-1717: Avoid unused local variables such as '$response'. (undefined)

(UnusedLocalVariable)

🤖 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 `@tests/oauth2/OAuth2SummitSponsorApiTest.php` around lines 1703 - 1729, The
testBulkUpdateSponsorServicesStatisticsValidationError assigns $response but
never uses it and only asserts HTTP 412; update the test to consume the response
from the action call and assert the error412 response structure (ensure the JSON
contains top-level keys "message", "errors", and "code" per the error412 helper
pattern) instead of leaving $response unused—locate the action invocation to
OAuth2SummitSponsorApiController@bulkUpdateSponsorServicesStatistics and add
assertions that decode the response body and verify those fields.

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