agent/php/Website/Hub/Concerns/HasRateLimiting.php
Snider f96bd67bd6 feat(agent/admin+hub): RFC foundation — admin scaffold + Hub global components
Foundation slice for Mantis #843 php/Mod/Admin + php/Website/Hub RFC:

* php/Mod/Admin/Boot.php — search registry, menu registry, form component
  layer, HasRateLimiting concern, reusable form/view primitives under
  Mod/Admin/Forms
* php/Website/Hub/Boot.php — host-aware Hub route naming for secondary
  domains
* WorkspaceSwitcher and GlobalSearch global Hub Livewire components
* Foundation routed slice in Hub/Routes/admin.php: dashboard shell,
  workspace listing, site settings (with WordPress/webhook connector),
  account usage, platform user list+detail
* Foundation tests under php/tests/Feature/Mod/Admin/

53 PHP files. php -l clean. Pest unrunnable in sandbox (no vendor/).

Foundation slice only — composer.json kept off-limits so namespace stays
under Core\Mod\Agentic\... rather than standalone Core\Admin package.
Deferred: Profile, Settings, ServiceManager, ServicesAdmin, Honeypot,
Entitlement\{Dashboard,FeatureManager,PackageManager}, PromptManager,
WaitlistManager, Console, Databases, Deployments, Content,
ContentManager, ContentEditor, ActivityLog, Analytics, AIServices,
BoostPurchase. Lane was under-instructed by supervisor with stop-at
framing — follow-up tickets needed for remainder.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=843
2026-04-25 21:09:22 +01:00

52 lines
1.3 KiB
PHP

<?php
// SPDX-License-Identifier: EUPL-1.2
declare(strict_types=1);
namespace Core\Mod\Agentic\Website\Hub\Concerns;
use Illuminate\Support\Facades\RateLimiter;
trait HasRateLimiting
{
protected function rateLimit(
string $action,
int $maxAttempts,
callable $callback,
int $decaySeconds = 60
): mixed {
$key = sprintf('%s:%s', $action, auth()->id() ?? 'guest');
$executed = false;
$result = null;
RateLimiter::attempt($key, $maxAttempts, function () use (&$executed, &$result, $callback) {
$executed = true;
$result = $callback();
}, $decaySeconds);
if (! $executed) {
$this->onRateLimited($action, $key);
return null;
}
return $result;
}
protected function onRateLimited(string $action, string $key): void
{
$seconds = RateLimiter::availableIn($key);
$message = sprintf('Too many %s attempts. Try again in %d seconds.', str_replace('-', ' ', $action), $seconds);
if (property_exists($this, 'actionMessage')) {
$this->actionMessage = $message;
} else {
session()->flash('warning', $message);
}
if (property_exists($this, 'actionType')) {
$this->actionType = 'warning';
}
}
}