Skip to content

feat: added tenant quotas#3967

Merged
thorsten merged 3 commits intomainfrom
feat/tenant-quotas
Feb 11, 2026
Merged

feat: added tenant quotas#3967
thorsten merged 3 commits intomainfrom
feat/tenant-quotas

Conversation

@thorsten
Copy link
Owner

@thorsten thorsten commented Feb 10, 2026

Summary by CodeRabbit

  • New Features

    • Added storage abstraction with local filesystem and Amazon S3 support
    • Added experimental OAuth2 API-key authentication
    • Added experimental per-tenant quotas (max FAQs, categories, users, attachments, API rate limits)
    • Improved multi-tenant robustness
  • Tests

    • Added comprehensive tests covering tenant quota enforcement and related scenarios

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Adds tenant quota enforcement: new TenantQuotaEnforcer and QuotaExceededException, integrates quota checks into FAQ, Category, User, and Attachment creation/storage, and adds tests covering quota-exceeded scenarios; CHANGELOG updated with related feature notes.

Changes

Cohort / File(s) Summary
Quota Foundation
phpmyfaq/src/phpMyFAQ/Tenant/QuotaExceededException.php, phpmyfaq/src/phpMyFAQ/Tenant/TenantQuotaEnforcer.php
Add QuotaExceededException and TenantQuotaEnforcer with factory, DB-backed checks (counts, attachment size), and assert methods for FAQs, categories, users, and attachments; runtime and quota-specific exceptions on failures.
Entity Quota Integration
phpmyfaq/src/phpMyFAQ/Attachment/AbstractAttachment.php, phpmyfaq/src/phpMyFAQ/Category/CategoryRepository.php, phpmyfaq/src/phpMyFAQ/Faq.php, phpmyfaq/src/phpMyFAQ/User.php
Inject lazy-initialized TenantQuotaEnforcer and call assertCanCreate* / assertCanStoreAttachment() before persisting new attachments, categories, FAQs, and users.
Tests — Quota Behavior
tests/phpMyFAQ/Attachment/AbstractAttachmentTest.php, tests/phpMyFAQ/Category/CategoryRepositoryTest.php, tests/phpMyFAQ/FaqTest.php, tests/phpMyFAQ/UserTest.php, tests/phpMyFAQ/Tenant/TenantQuotaEnforcerTest.php
Add and update unit tests to assert QuotaExceededException on quota breaches and to validate TenantQuotaEnforcer behavior and error conditions; include test teardown hygiene for env vars.
Tests — Formatting / Minor
tests/phpMyFAQ/Http/RateLimiterTest.php, tests/phpMyFAQ/Setup/Migration/Versions/Migration420AlphaTest.php
Formatting and mock-callback refactors only; no behavioral changes.
Changelog & Manifests
CHANGELOG.md, manifest_file, composer.json
CHANGELOG entries added for storage abstraction, experimental API key/OAuth2, per-tenant quotas, and multi-tenancy hardening; small manifest/composer metadata bumps.

Sequence Diagram

sequenceDiagram
    participant Entity as Entity (FAQ/Category/User/Attachment)
    participant Enforcer as TenantQuotaEnforcer
    participant Resolver as TenantContextResolver
    participant DB as DatabaseDriver

    Entity->>Enforcer: getTenantQuotaEnforcer()
    Enforcer->>Resolver: resolveTenantContext()
    Resolver-->>Enforcer: TenantContext
    Enforcer->>DB: run COUNT/SUM queries for tenant
    DB-->>Enforcer: current_count_or_size
    Entity->>Enforcer: assertCanCreate... / assertCanStoreAttachment(size)
    alt quota exceeded
        Enforcer-->>Entity: throw QuotaExceededException
    else within quota
        Enforcer-->>Entity: return (void)
        Entity->>DB: proceed with insert/store
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I counted carrots, bytes, and more,
Ensured each tenant stayed within the store.
With gentle hops and careful measure,
Quotas keep balance — my tiny treasure. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: added tenant quotas' directly and clearly describes the main feature added in this changeset: implementation of tenant quota enforcement system.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/tenant-quotas

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
CHANGELOG.md (1)

29-29: Consider hyphenating “multi-tenancy” for consistency.

Line 23 uses “per-tenant”; line 29 reads “multi tenancy.” Consider “multi-tenancy” for consistent compound adjective styling.

✏️ Suggested tweak
-- improved and hardened multi tenancy support (Thorsten)
+- improved and hardened multi-tenancy support (Thorsten)

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.

Copy link

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

🤖 Fix all issues with AI agents
In `@CHANGELOG.md`:
- Line 30: Remove the incomplete changelog list item: locate the lone
bullet-only entry (a line containing just "-") in CHANGELOG.md and delete it or
replace it with a complete entry sentence describing the intended change; ensure
the file retains proper markdown list spacing and formatting after removal.
- Around line 21-29: Update CHANGELOG.md to document the new tenant quota
enforcement feature by replacing the incomplete "- " entry on line 30 with a
clear entry that mentions TenantQuotaEnforcer and QuotaExceededException and
summarizes enforced limits (per-tenant max FAQs, categories, users, max
attachment size, and API request rate limits). Mention where quota checks were
added (Faq, CategoryRepository, AbstractAttachment, User) so readers can find
related implementation points.

In `@phpmyfaq/src/phpMyFAQ/Tenant/TenantQuotaEnforcer.php`:
- Around line 59-77: The multiplication maxAttachmentSizeMb * 1024 * 1024 can
overflow on 32-bit PHP; update assertCanStoreAttachment in TenantQuotaEnforcer
to guard against overflow by checking if $maxAttachmentSizeMb exceeds
PHP_INT_MAX / (1024*1024) and, if so, set $maxSizeBytes to PHP_INT_MAX (or use a
safe float-based calculation and cast), then use that safe $maxSizeBytes for the
comparison with readAttachmentSizeBytes() and $newAttachmentSizeBytes before
throwing QuotaExceededException; reference the assertCanStoreAttachment method,
$maxAttachmentSizeMb, readAttachmentSizeBytes(), and QuotaExceededException so
the check is added in that function.
🧹 Nitpick comments (3)
phpmyfaq/src/phpMyFAQ/Tenant/TenantQuotaEnforcer.php (1)

79-107: TOCTOU race condition between quota check and insert.

The count query and the subsequent insert (in the caller) are not atomic. Under concurrent requests, two callers could both pass the quota check and then both insert, exceeding the limit. This is an inherent limitation of application-level quota enforcement.

For most use cases this is acceptable, but if strict enforcement is required, consider a DB-level approach (e.g., a trigger or a serializable transaction wrapping the check + insert).

phpmyfaq/src/phpMyFAQ/Faq.php (1)

95-95: Minor: Missing docblock and blank-line separator for the new property.

Other properties in this class have docblocks (e.g., $plurals, $user, $groups, $groupSupport). Adding one here would keep the style consistent.

Proposed fix
     private bool $groupSupport = false;
+
+    /**
+     * Tenant quota enforcer.
+     */
     private ?TenantQuotaEnforcer $tenantQuotaEnforcer = null;
tests/phpMyFAQ/FaqTest.php (1)

54-61: Consider also clearing the cached enforcer in tearDown.

After testCreateThrowsWhenFaqQuotaIsExceeded, the Faq instance retains a lazily-cached TenantQuotaEnforcer holding the old TenantContext (with maxFaqs=0). This is safe today because a fresh Faq is created in each setUp(), but if tests ever share instances, the stale enforcer could cause surprising failures. Clearing the env var (line 57) is sufficient for the current test structure.

Copy link

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

🤖 Fix all issues with AI agents
In `@CHANGELOG.md`:
- Line 23: Update the CHANGELOG entry line that reads "added experimental
per-tenant quota enforcement covering max FAQs, categories users, attachment
size, and API request rate limits (Thorsten)" to insert the missing comma
between "categories" and "users" so it reads "categories, users"; keep the rest
of the entry unchanged.

In `@phpmyfaq/src/phpMyFAQ/Tenant/TenantQuotaEnforcer.php`:
- Line 87: Replace calls to the global Database::getTablePrefix() with the
tenant-specific prefix from the injected TenantContext: use
$this->tenantContext->getTablePrefix() in the COUNT query construction in
TenantQuotaEnforcer (the occurrences around the COUNT query and the later
similar usage in the same class), and after making these replacements remove the
now-unused use phpMyFAQ\Database; import if it is no longer referenced elsewhere
in TenantQuotaEnforcer.
🧹 Nitpick comments (2)
phpmyfaq/src/phpMyFAQ/User.php (1)

138-139: Nit: Add a blank line before the new property to separate concerns.

The $authSource and $tenantQuotaEnforcer properties serve different purposes. A blank line would improve readability.

 private string $authSource = 'local';
+
 private ?TenantQuotaEnforcer $tenantQuotaEnforcer = null;
phpmyfaq/src/phpMyFAQ/Attachment/AbstractAttachment.php (1)

267-270: Inconsistent visibility: protected here vs private in Faq, CategoryRepository, and User.

The getTenantQuotaEnforcer() method is private in the other three integration points but protected here. If the intent is to allow test subclasses to override it, the same reasoning would apply to the other classes. Consider aligning to private for consistency, or making all four protected if override flexibility is desired.

@thorsten thorsten merged commit 0340496 into main Feb 11, 2026
10 checks passed
@thorsten thorsten deleted the feat/tenant-quotas branch February 11, 2026 17:44
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