Skip to content

Release v0.1.13 — Phase 13: Feature Access Service#182

Merged
menvil merged 13 commits into
mainfrom
release/v0.1.13-phase13-feature-access-service
Jun 2, 2026
Merged

Release v0.1.13 — Phase 13: Feature Access Service#182
menvil merged 13 commits into
mainfrom
release/v0.1.13-phase13-feature-access-service

Conversation

@menvil

@menvil menvil commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds FeatureAccessService as single source of truth for plan feature flags and limits (free / pro / max)
  • Enforces plan-aware max file size and storage quota in the upload flow
  • Applies plan-based retention (expires_at) to uploaded and result files via updated FileExpirationPolicy
  • Shows current plan limits (max file, storage, retention, API access) in the user dropdown

Tasks

Task Description
CONV-181 config/feature-access.php — per-plan feature flags and limits
CONV-182–186 FeatureAccessServiceallows(), limit(), limits()
CONV-187 PlanLimit readonly DTO
CONV-188–189 Plan-aware max file size in upload; config/livewire.php ceiling raised to 2 GB
CONV-190–191 StorageUsageService — active bytes, excludes expired/deleted
CONV-192–193 Storage quota check in StoreUploadedFileAction; StorageLimitExceededException
CONV-194–195 FileExpirationPolicy uses FeatureAccessService for retention days
CONV-196 UserDropdown View Component shows plan limits
CONV-197 Feature access integration test

Test plan

  • composer test — 368 tests pass
  • composer lint — clean
  • npm run build — clean

🤖 Generated with Claude Code


Summary by cubic

Adds a plan-aware Feature Access system and enforces max file size, storage quota, and retention during uploads. Updates the dashboard to show plan limits and raises Livewire temporary upload ceiling to 2 GB with a 60‑minute window; addresses CONV-181–197.

  • New Features

    • config/feature-access.php with per-plan limits; FeatureAccessService (allows(), limit(), limits()) and PlanLimit DTO.
    • Upload enforcement: max file size in DashboardConverter; storage quota in StoreUploadedFileAction using StorageUsageService and StorageLimitExceededException; retention via FileExpirationPolicy.
    • UI: UserDropdown shows max file, storage, retention, and API access. config/livewire.php sets 2 GB temp upload and 60-minute window.
  • Bug Fixes

    • FeatureAccessService::limits() falls back to free plan when plan key is unknown.
    • PlanLimit::fromArray validates required keys and throws InvalidArgumentException if missing.
    • Moved storage/retention label formatting into UserDropdown; Blade uses component props.
    • Storage quota failures now set uploadError in the dashboard for consistent UX.

Written for commit 6943736. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced plan-based storage limits and file upload size restrictions
    • Added plan limits display in the user dashboard showing maximum file size, total storage, retention period, and API access availability
    • Implemented storage quota enforcement to prevent uploads exceeding your plan limits
    • File retention is now determined by your subscription plan

menvil and others added 11 commits June 2, 2026 12:45
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CONV-183: Test free plan feature access
CONV-184: Implement free plan feature access
CONV-185: Test pro and max feature access
CONV-186: Implement pro and max feature access

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CONV-189: Enforce max file size limit in upload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CONV-191: Implement StorageUsageService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CONV-193: Enforce storage limit in upload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CONV-195: Apply retention days to files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements plan-based feature access and storage quota enforcement. It introduces FeatureAccessService to query plan configuration, StorageUsageService to track user storage, and extends the upload action to validate quotas before storing files. File retention is now plan-driven, and the dashboard UI displays plan limits and enforces per-plan upload size limits.

Changes

Plan-based Feature Access, Storage Quotas, and Retention

Layer / File(s) Summary
Feature Access Service and Configuration
app/Services/FeatureAccess/FeatureAccessService.php, app/Services/FeatureAccess/PlanLimit.php, config/feature-access.php, tests/Unit/FeatureAccess/*
FeatureAccessService exposes allows(), limit(), and limits() methods that read plan-based feature flags and numeric limits from config. PlanLimit is a readonly DTO holding max file size, storage, retention days, and monthly credits. Configuration defines free, pro, and max plans with respective features and limits. Comprehensive unit tests verify config structure, service method behavior, and DTO factories.
Storage Usage and Quota Enforcement
app/Services/Storage/StorageUsageService.php, app/Exceptions/Storage/StorageLimitExceededException.php, app/Actions/Files/StoreUploadedFileAction.php, tests/Unit/Storage/StorageUsageServiceTest.php, tests/Feature/Files/StoreUploadedFileActionTest.php
StorageUsageService queries the sum of active file bytes per user by filtering FileRecord rows (excluding expired/deleted). StorageLimitExceededException formats limit-exceeded messages with byte/MB values. StoreUploadedFileAction now injects both services, pre-computes the user's allowed storage quota in bytes, and throws the exception if the incoming upload would exceed it. Tests verify storage calculation, status filtering, user scoping, and quota enforcement.
Plan-based File Retention
app/Support/Files/FileExpirationPolicy.php, tests/Feature/Files/StoreUploadedFileActionTest.php
FileExpirationPolicy now injects FeatureAccessService and derives retentionDays from plan limits instead of using a fixed single-day increment. Tests verify free-plan files expire after 1 day and pro-plan files expire after 30 days.
Dashboard Upload Flow and Plan Display
app/Livewire/Dashboard/DashboardConverter.php, app/View/Components/UserDropdown.php, resources/views/components/user-dropdown.blade.php, config/livewire.php, tests/Feature/Livewire/DashboardUploadFlowTest.php, tests/Feature/FeatureAccess/FeatureAccessIntegrationTest.php
DashboardConverter injects FeatureAccessService, derives per-user maxFileSizeMb for Livewire validation rules and error messages, and catches StorageLimitExceededException to report upload errors. UserDropdown component queries plan limits and API access via the service and passes them to the view. Dashboard template conditionally renders a plan limits section with formatted storage, file size, retention, and API access. config/livewire.php configures upload disk selection and size validation. Feature tests validate dashboard rendering, upload size validation, quota enforcement, and integration across the full flow.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • menvil/FileConverter#76: Both PRs modify StoreUploadedFileAction and FileExpirationPolicy; the retrieved PR introduces the core upload/record pipeline, while this PR extends quota and retention enforcement using new services.
  • menvil/FileConverter#123: This PR updates FileExpirationPolicy to derive retention from FeatureAccessService, and the retrieved PR's RecordConversionResultFileAction uses FileExpirationPolicy to compute expires_at, so the retention behavior changes impact conversion result recording.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% 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
Title check ✅ Passed The title clearly and specifically identifies the pull request as introducing a Feature Access Service as part of release v0.1.13, Phase 13, which aligns with the main 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 release/v0.1.13-phase13-feature-access-service

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 github-actions Bot added the release Triggers AI code review (CodeRabbit, Cubic) label Jun 2, 2026

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 19 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread config/livewire.php Outdated
…eiling

5 min was insufficient — a 2 GB file at 5 Mbps takes ~53 min to upload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@menvil

menvil commented Jun 2, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
app/Livewire/Dashboard/DashboardConverter.php (1)

91-106: 💤 Low value

Inconsistent error surfacing across catch branches.

UnsupportedFileFormatException and FileStorageException set the public $this->uploadError property, while StorageLimitExceededException uses addError('upload', ...) (the error bag). Unless the blade renders both channels for the upload step, the storage-limit message may display differently (or not at all) from the other upload errors. Consider routing all three through the same mechanism for consistent UX.

🤖 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/Livewire/Dashboard/DashboardConverter.php` around lines 91 - 106, The
catch branches in DashboardConverter (UnsupportedFileFormatException,
StorageLimitExceededException, FileStorageException) are inconsistent:
UnsupportedFileFormatException and FileStorageException set the public
$this->uploadError while StorageLimitExceededException calls
$this->addError('upload', ...), causing inconsistent UX; update the
StorageLimitExceededException branch to set $this->uploadError to the same
user-facing string (and keep $this->step = 'upload' and return) so all upload
errors are surfaced via $this->uploadError, or alternatively change the other
two branches to use addError('upload', ...) if your blade expects the error
bag—ensure all three use the same mechanism ($this->uploadError or addError) and
keep $this->step = 'upload' and return behavior.
🤖 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/Services/FeatureAccess/FeatureAccessService.php`:
- Around line 26-31: The limits() method currently passes
config("feature-access.plans.{$plan}.limits") directly into PlanLimit::fromArray
which can be null; update FeatureAccessService::limits to guard the config
result from null (using a default array fallback or resolving a safe default
plan) before calling PlanLimit::fromArray so it always receives an array — e.g.,
fetch the config into a local $limits variable, if it's null replace it with
config("feature-access.plans.free.limits", []) or an empty array, then call
PlanLimit::fromArray($limits); keep references to the existing planKey() and
PlanLimit::fromArray() symbols.

In `@app/Services/FeatureAccess/PlanLimit.php`:
- Around line 17-24: The fromArray factory in PlanLimit accesses array keys
directly which can throw "Undefined array key"; modify PlanLimit::fromArray to
first validate required keys
('max_file_size_mb','storage_mb','retention_days','monthly_credits') exist
(throw a clear InvalidArgumentException listing missing keys) or supply explicit
defaults, then cast values to int; also update the PHPDoc for fromArray to
reflect the accepted input types (e.g., array<string,int|string>) to match the
explicit (int) casts so types are consistent.

In `@resources/views/components/user-dropdown.blade.php`:
- Line 71: Move the pluralization logic out of the Blade view by adding a
formatted retention property or accessor on the UserDropdown component class
(e.g., add a getRetentionDaysLabel() method or a public $retentionDaysLabel set
in app/View/Components/UserDropdown.php that returns "1 day" or "{N} days"),
populate it using the existing $planLimits->retentionDays value inside the
component constructor or a computed getter, and then update the Blade template
to use that new property (replace the inline ternary in user-dropdown.blade.php
with the component property like {{ $retentionDaysLabel }}).
- Line 69: Move the storage unit/formatting logic out of the Blade into the
UserDropdown component by computing a formatted string property (e.g.,
$formattedStorage or $storageFormatted) inside the
app/View/Components/UserDropdown.php (in the constructor or render method) using
the existing $planLimits->storageMb value, and expose that property on the
component; then replace the inline ternary in
resources/views/components/user-dropdown.blade.php with the new component
property (e.g., {{ $formattedStorage }}). Ensure the property name is public on
the UserDropdown class so the Blade view can access it.

---

Nitpick comments:
In `@app/Livewire/Dashboard/DashboardConverter.php`:
- Around line 91-106: The catch branches in DashboardConverter
(UnsupportedFileFormatException, StorageLimitExceededException,
FileStorageException) are inconsistent: UnsupportedFileFormatException and
FileStorageException set the public $this->uploadError while
StorageLimitExceededException calls $this->addError('upload', ...), causing
inconsistent UX; update the StorageLimitExceededException branch to set
$this->uploadError to the same user-facing string (and keep $this->step =
'upload' and return) so all upload errors are surfaced via $this->uploadError,
or alternatively change the other two branches to use addError('upload', ...) if
your blade expects the error bag—ensure all three use the same mechanism
($this->uploadError or addError) and keep $this->step = 'upload' and return
behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f13c285-a4cb-470a-9fd0-7de0b54bea94

📥 Commits

Reviewing files that changed from the base of the PR and between 87690a3 and 737ecb6.

📒 Files selected for processing (19)
  • app/Actions/Files/StoreUploadedFileAction.php
  • app/Exceptions/Storage/StorageLimitExceededException.php
  • app/Livewire/Dashboard/DashboardConverter.php
  • app/Services/FeatureAccess/FeatureAccessService.php
  • app/Services/FeatureAccess/PlanLimit.php
  • app/Services/Storage/StorageUsageService.php
  • app/Support/Files/FileExpirationPolicy.php
  • app/View/Components/UserDropdown.php
  • config/feature-access.php
  • config/livewire.php
  • resources/views/components/user-dropdown.blade.php
  • tests/Feature/FeatureAccess/FeatureAccessIntegrationTest.php
  • tests/Feature/Files/StoreUploadedFileActionTest.php
  • tests/Feature/Livewire/DashboardUploadFlowTest.php
  • tests/Unit/FeatureAccess/FeatureAccessConfigTest.php
  • tests/Unit/FeatureAccess/FeatureAccessServiceLimitsTest.php
  • tests/Unit/FeatureAccess/FeatureAccessServiceTest.php
  • tests/Unit/FeatureAccess/PlanLimitTest.php
  • tests/Unit/Storage/StorageUsageServiceTest.php

Comment thread app/Services/FeatureAccess/FeatureAccessService.php
Comment thread app/Services/FeatureAccess/PlanLimit.php
Comment thread resources/views/components/user-dropdown.blade.php Outdated
Comment thread resources/views/components/user-dropdown.blade.php Outdated
…e formatting to component, align upload error mechanism

- FeatureAccessService::limits() falls back to free plan config when plan key
  is unknown, preventing null passed to PlanLimit::fromArray
- PlanLimit::fromArray validates required keys and throws InvalidArgumentException
  with missing key names; updates @param type to array<string,int|string>
- UserDropdown component computes $formattedStorage and $retentionDaysLabel;
  Blade uses the new properties instead of inline ternaries
- StorageLimitExceededException handler uses $this->uploadError (consistent with
  UnsupportedFileFormatException and FileStorageException); test updated to
  assertSet('uploadError', ...) instead of assertHasErrors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@menvil menvil merged commit b8f68dd into main Jun 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Triggers AI code review (CodeRabbit, Cubic)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant