Skip to content

feat: async file post-processing pipeline for company logos#561

Open
smarcet wants to merge 5 commits into
mainfrom
feature/company-logo-postprocessing
Open

feat: async file post-processing pipeline for company logos#561
smarcet wants to merge 5 commits into
mainfrom
feature/company-logo-postprocessing

Conversation

@smarcet

@smarcet smarcet commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

ref:https://app.clickup.com/t/9014802374/86bah99tf

  • Add FileInfoDTO (owner entity class/id/member, filepath, md5, size, mime_type, source_bucket)
  • Add FileProcessingJob (ShouldQueue) dispatching to FilePostProcessorService
  • Add FilePostProcessorService with locateService() switch dispatch; throws InvalidArgumentException for unknown entity classes instead of silent false
  • Add IFilePostProcessorService / IFilePostProcessorForChildEntity interfaces
  • Add CompanyService::processFileForChildEntity() handling 'logo' and 'big_logo' members: downloads from remote storage, verifies size and MD5, builds UploadedFile, cleans up temp files in finally block
  • Fix md5_file() false detection: I/O failure and hash mismatch now produce distinct error messages
  • Use Company::LogoAllowedExtensions constant in addCompanyLogo/addCompanyBigLogo (removed local copies)
  • Update OAuth2CompaniesApiController: addCompanyLogo/addCompanyBigLogo now accept both multipart/form-data (file upload) and application/json (async file-api payload); intval() cast on company_id
  • Update OpenAPI specs for both endpoints: dual RequestBody with multipart and JSON MediaType entries; 412 removed; description updated
  • Fix CompanyValidationRulesFactory::buildForFileInfo() signature (remove unused $data param)
  • Remove unused Illuminate\Support\Facades\Request import from controller
  • Register FilePostProcessorService in AppServiceProvider and ModelServicesProvider

Tests:

  • Add CompanyFileProcessingTest (5 unit tests): unknown member name, MD5 mismatch vs I/O error distinction, big_logo mismatch, unknown entity class throws, FileInfoDTO queue serialization round-trip
  • Add OAuth2CompaniesApiTest dual-flow functional tests (6 tests): logo multipart, logo JSON payload, logo JSON missing fields -> 412, big_logo multipart, big_logo JSON payload, backward-compat shim

Summary by CodeRabbit

  • New Features

    • Company logos now support dual upload methods: direct multipart file upload or JSON payload referencing a file from storage
    • Added optional file metadata support including MD5 hash verification and MIME type tracking
    • Implemented asynchronous background processing for logo uploads
  • Tests

    • Added comprehensive test coverage for logo upload workflows via both multipart and JSON methods
    • Added unit tests for file post-processing validation and serialization

- Add FileInfoDTO (owner entity class/id/member, filepath, md5, size, mime_type, source_bucket)
- Add FileProcessingJob (ShouldQueue) dispatching to FilePostProcessorService
- Add FilePostProcessorService with locateService() switch dispatch; throws InvalidArgumentException for unknown entity classes instead of silent false
- Add IFilePostProcessorService / IFilePostProcessorForChildEntity interfaces
- Add CompanyService::processFileForChildEntity() handling 'logo' and 'big_logo' members: downloads from remote storage, verifies size and MD5, builds UploadedFile, cleans up temp files in finally block
- Fix md5_file() false detection: I/O failure and hash mismatch now produce distinct error messages
- Use Company::LogoAllowedExtensions constant in addCompanyLogo/addCompanyBigLogo (removed local copies)
- Update OAuth2CompaniesApiController: addCompanyLogo/addCompanyBigLogo now accept both multipart/form-data (file upload) and application/json (async file-api payload); intval() cast on company_id
- Update OpenAPI specs for both endpoints: dual RequestBody with multipart and JSON MediaType entries; 412 removed; description updated
- Fix CompanyValidationRulesFactory::buildForFileInfo() signature (remove unused $data param)
- Remove unused Illuminate\Support\Facades\Request import from controller
- Register FilePostProcessorService in AppServiceProvider and ModelServicesProvider

Tests:
- Add CompanyFileProcessingTest (5 unit tests): unknown member name, MD5 mismatch vs I/O error distinction, big_logo mismatch, unknown entity class throws, FileInfoDTO queue serialization round-trip
- Add OAuth2CompaniesApiTest dual-flow functional tests (6 tests): logo multipart, logo JSON payload, logo JSON missing fields -> 412, big_logo multipart, big_logo JSON payload, backward-compat shim
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@smarcet, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 7 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9503524-ea7d-4e73-9934-72598aa2c10d

📥 Commits

Reviewing files that changed from the base of the PR and between 476846c and 4e70016.

📒 Files selected for processing (8)
  • Libs/Utils/FileUtils.php
  • app/Http/Utils/FileUploadInfo.php
  • app/Jobs/FileProcessingJob.php
  • app/Services/Model/FileInfoDTO.php
  • app/Services/Model/Imp/CompanyService.php
  • app/Swagger/CompaniesSchemas.php
  • app/Swagger/FileDTOSchema.php
  • tests/oauth2/OAuth2CompaniesApiTest.php
📝 Walkthrough

Walkthrough

This PR adds a second company logo upload path: in addition to multipart file uploads, endpoints now accept JSON payloads referencing files already stored in a file API. A new FileInfoDTO, IFilePostProcessorForChildEntity/Service interface hierarchy, FilePostProcessorService router, and FileProcessingJob queued job form the async processing backbone. FileUploadInfo and FileUtils are hardened with extra metadata fields and safer temp-file creation.

Changes

Company Logo Dual-Path Upload

Layer / File(s) Summary
Core contracts, DTO, and allowed extensions
app/Services/Model/FileInfoDTO.php, app/Services/Model/IFilePostProcessorForChildEntity.php, app/Services/Model/IFilePostProcessorService.php, app/Services/Model/ICompanyService.php, app/Models/Foundation/Main/Companies/Company.php
Introduces FileInfoDTO readonly DTO, IFilePostProcessorForChildEntity and IFilePostProcessorService interfaces, extends ICompanyService to inherit the child-entity processor contract, and adds Company::LogoAllowedExtensions.
file_dto custom validator and FileUploadInfo metadata
app/Providers/AppServiceProvider.php, app/Http/Utils/FileUploadInfo.php
Registers a file_dto custom Laravel validator rule with a replacer message; expands FileUploadInfo constructor, build(), and buildFromPayload() to carry and expose md5, mime_type, and source_bucket.
FileProcessingJob and FilePostProcessorService routing
app/Jobs/FileProcessingJob.php, app/Services/FilePostProcessorService.php, app/Services/ModelServicesProvider.php
Adds FileProcessingJob (queued, 3 retries, 600 s timeout) that calls IFilePostProcessorService; adds FilePostProcessorService that resolves the per-entity handler via switch and delegates; registers the singleton binding.
CompanyService: processFileForChildEntity and addCompany logo dispatching
app/Services/Model/Imp/CompanyService.php, Libs/Utils/FileUtils.php
CompanyService implements processFileForChildEntity (downloads remote file, verifies MD5, wraps in UploadedFile, uploads logo, cleans up in finally); addCompany dispatches FileProcessingJob for logo/big_logo payload fields; extension validation updated to use LogoAllowedExtensions. FileUtils uses tempnam for safe temp file creation.
Controller dual-path upload and validation factory
app/Http/Controllers/Apis/Protected/Main/Factories/CompanyValidationRulesFactory.php, app/Http/Controllers/Apis/Protected/Main/OAuth2CompaniesApiController.php
CompanyValidationRulesFactory adds buildForFileInfo() and optional logo/big_logo file_dto rules. Controller addCompanyLogo/addCompanyBigLogo branch on file presence: multipart uses existing flow; JSON payload builds FileInfoDTO and calls processFileForChildEntity synchronously. OpenAPI schemas updated for dual media-type.
Unit and API tests
tests/Unit/Services/CompanyFileProcessingTest.php, tests/oauth2/OAuth2CompaniesApiTest.php
Unit tests cover unknown member name, MD5 mismatch, unknown entity class routing, and DTO serialize round-trip. API tests cover multipart, JSON payload, and missing-fields (412) flows for both logo and big logo.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant OAuth2CompaniesApiController
  participant CompanyService
  participant FilePostProcessorService
  participant FileProcessingJob
  participant FileUtils

  rect rgba(100, 149, 237, 0.5)
    Note over Client,CompanyService: Multipart upload path
    Client->>OAuth2CompaniesApiController: POST /companies/{id}/logo (multipart file)
    OAuth2CompaniesApiController->>CompanyService: addCompanyLogo(company_id, FileUploadInfo)
    CompanyService-->>OAuth2CompaniesApiController: IEntity
    OAuth2CompaniesApiController-->>Client: 201 JSON
  end

  rect rgba(144, 238, 144, 0.5)
    Note over Client,FileUtils: JSON file-API payload path
    Client->>OAuth2CompaniesApiController: POST /companies/{id}/logo (JSON filepath/filename/md5/size)
    OAuth2CompaniesApiController->>CompanyService: processFileForChildEntity(FileInfoDTO{owner_member_name=logo})
    CompanyService->>FileUtils: getFileFromRemoteStorageOnTempStorage(filepath)
    FileUtils-->>CompanyService: local temp path
    CompanyService->>CompanyService: verify MD5, create UploadedFile
    CompanyService->>CompanyService: addCompanyLogo(company_id, UploadedFile)
    CompanyService->>FileUtils: cleanLocalAndRemoteFile(...)
    CompanyService-->>OAuth2CompaniesApiController: IEntity
    OAuth2CompaniesApiController-->>Client: 201 JSON
  end

  rect rgba(255, 165, 100, 0.5)
    Note over CompanyService,FileProcessingJob: addCompany async logo dispatch
    CompanyService->>FileProcessingJob: dispatch(FileInfoDTO) via JobDispatcher
    FileProcessingJob->>FilePostProcessorService: postProcessFileFromFileApi(FileInfoDTO)
    FilePostProcessorService->>FilePostProcessorService: locateService(Company::class)
    FilePostProcessorService->>CompanyService: processFileForChildEntity(FileInfoDTO)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • caseylocker

Poem

🐇 A logo once arrived by file,
Now JSON paths stretch many a mile.
FileInfoDTO hops through the queue,
MD5 checked, the colors stay true.
With tempnam safe and validators bright,
The rabbit ships logos left and right! 🖼️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% 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 'feat: async file post-processing pipeline for company logos' clearly and accurately summarizes the main change—introducing an async file post-processing pipeline for company logos. It is concise, specific, and directly reflects the primary objective of the changeset.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/company-logo-postprocessing

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-561/

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

smarcet added 2 commits June 19, 2026 17:41
…t schemas

- Add reusable FileDTO schema with filepath, filename, md5, size (required) and mime_type, source_bucket (optional)
- Add optional logo and big_logo properties (ref FileDTO) to CompanyCreateRequest and CompanyUpdateRequest schemas
@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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: 5

🤖 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/Utils/FileUploadInfo.php`:
- Around line 142-167: In the buildFromPayload method, use the normalized
trimmed path variable consistently and always retrieve the actual file size from
storage. Replace the untrimmed $payload['filepath'] with the trimmed $filepath
variable in the $disk->exists() call on line 147, and all subsequent references
including the logging on line 155 and the disk->path() call on line 161.
Additionally, remove the conditional check that allows trusting the
client-provided $payload['size'] value on line 153 and instead always retrieve
the actual size from the disk using $disk->size($filepath), ensuring the
zero-size validation guard cannot be bypassed by forged client metadata.

In `@app/Jobs/FileProcessingJob.php`:
- Around line 36-38: The handle method in FileProcessingJob is not checking the
return value from the postProcessFileFromFileApi call on the injected
IFilePostProcessorService. When the service method returns false, the job
currently completes successfully instead of failing and retrying. Capture the
return value from the postProcessFileFromFileApi call with $this->fileInfoDTO as
the argument, check if it returns false, and throw an exception in that case to
properly fail the job and trigger retries.

In `@app/Services/Model/FileInfoDTO.php`:
- Around line 28-30: The __toString() method in the FileInfoDTO class currently
calls json_encode() directly, but json_encode() can return false on encoding
errors, which triggers a TypeError when returning from a method declared to
return string. Wrap the json_encode call with a null check or ternary operator
to handle the false case, providing a fallback string value (such as an empty
object representation or error message) to ensure the method always returns a
valid string instead of false.

In `@app/Services/Model/Imp/CompanyService.php`:
- Around line 306-308: The finally block in the CompanyService cleanup logic
unconditionally deletes both local and remote files using
cleanLocalAndRemoteFile, which destroys the remote source file even when
processing fails, preventing retries. Modify the logic so that the remote file
(referenced in file_info_dto->filepath) is only deleted on successful processing
completion, while the local file can still be cleaned up in the finally block.
Apply this fix to both occurrences of the finally block that call
cleanLocalAndRemoteFile (around lines 306-308 and 332-334).

In `@tests/oauth2/OAuth2CompaniesApiTest.php`:
- Around line 108-124: The testAddCompanyLogoViaJsonPayload method and other
JSON-flow tests are faking storage disks but still depend on the
file_upload.storage_driver configuration resolving to 'local', making them
vulnerable to environment or config drift. Explicitly pin the storage driver
configuration to 'local' at the beginning of each affected JSON test method
(including testAddCompanyLogoViaJsonPayload and the test at lines 203-219) using
Laravel's Config facade to set the driver value explicitly, ensuring the tests
use the faked 'local' disk regardless of environment settings.
🪄 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: 16d1129f-3e6d-4bfa-8e10-d74fe3630acf

📥 Commits

Reviewing files that changed from the base of the PR and between 5a64bc5 and 476846c.

📒 Files selected for processing (16)
  • Libs/Utils/FileUtils.php
  • app/Http/Controllers/Apis/Protected/Main/Factories/CompanyValidationRulesFactory.php
  • app/Http/Controllers/Apis/Protected/Main/OAuth2CompaniesApiController.php
  • app/Http/Utils/FileUploadInfo.php
  • app/Jobs/FileProcessingJob.php
  • app/Models/Foundation/Main/Companies/Company.php
  • app/Providers/AppServiceProvider.php
  • app/Services/FilePostProcessorService.php
  • app/Services/Model/FileInfoDTO.php
  • app/Services/Model/ICompanyService.php
  • app/Services/Model/IFilePostProcessorForChildEntity.php
  • app/Services/Model/IFilePostProcessorService.php
  • app/Services/Model/Imp/CompanyService.php
  • app/Services/ModelServicesProvider.php
  • tests/Unit/Services/CompanyFileProcessingTest.php
  • tests/oauth2/OAuth2CompaniesApiTest.php

Comment thread app/Http/Utils/FileUploadInfo.php Outdated
Comment thread app/Jobs/FileProcessingJob.php
Comment thread app/Services/Model/FileInfoDTO.php
Comment thread app/Services/Model/Imp/CompanyService.php
Comment thread tests/oauth2/OAuth2CompaniesApiTest.php
@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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

smarcet added 2 commits June 19, 2026 18:01
- FileUploadInfo::buildFromPayload: use trimmed $filepath consistently in exists(),
  log, and path() calls; always get file size from disk (never trust client payload)
- FileProcessingJob::handle: throw RuntimeException when postProcessFileFromFileApi
  returns false so the job fails and triggers retries instead of completing silently
- FileInfoDTO::__toString: guard json_encode false return with ?: '{}' fallback
- CompanyService::processFileForChildEntity: use success flag so remote source file
  is only deleted on success; on failure only local temp is cleaned up, preserving
  the remote file for queue job retries (both logo and big_logo cases)
- FileUtils: add cleanLocalFile() helper for local-only cleanup
- OAuth2CompaniesApiTest: pin file_upload.storage_driver to 'local' in JSON-path
  tests so they are immune to environment/config drift
- FileProcessingJob: add backoff() [30s, 60s] so retries don't fire back-to-back
  against a throttled S3 endpoint
- FileUtils: preserve original exception chain in ValidationException re-throw
  so upstream handlers and job failure logs retain the root cause and stack trace
@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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

1 similar comment
@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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

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