Add tenant lifecycle view and limit controls
This commit is contained in:
@@ -5,9 +5,12 @@ namespace App\Services\Packages;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventPackage;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\Tenant\TenantUsageService;
|
||||
|
||||
class PackageLimitEvaluator
|
||||
{
|
||||
public function __construct(private readonly TenantUsageService $tenantUsageService) {}
|
||||
|
||||
public function assessEventCreation(Tenant $tenant): ?array
|
||||
{
|
||||
$hasEndcustomerPackage = $tenant->tenantPackages()
|
||||
@@ -60,8 +63,12 @@ class PackageLimitEvaluator
|
||||
];
|
||||
}
|
||||
|
||||
public function assessPhotoUpload(Tenant $tenant, int $eventId, ?Event $preloadedEvent = null): ?array
|
||||
{
|
||||
public function assessPhotoUpload(
|
||||
Tenant $tenant,
|
||||
int $eventId,
|
||||
?Event $preloadedEvent = null,
|
||||
?int $incomingBytes = null
|
||||
): ?array {
|
||||
[$event, $eventPackage] = $this->resolveEventAndPackage($tenant, $eventId, $preloadedEvent);
|
||||
|
||||
if (! $event) {
|
||||
@@ -92,11 +99,7 @@ class PackageLimitEvaluator
|
||||
|
||||
$maxPhotos = $eventPackage->effectivePhotoLimit();
|
||||
|
||||
if ($maxPhotos === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($eventPackage->used_photos >= $maxPhotos) {
|
||||
if ($maxPhotos !== null && $eventPackage->used_photos >= $maxPhotos) {
|
||||
return [
|
||||
'code' => 'photo_limit_exceeded',
|
||||
'title' => 'Photo upload limit reached',
|
||||
@@ -113,6 +116,51 @@ class PackageLimitEvaluator
|
||||
];
|
||||
}
|
||||
|
||||
$tenantPhotoLimit = $this->normalizeTenantLimit($tenant->max_photos_per_event);
|
||||
|
||||
if ($tenantPhotoLimit !== null && ($maxPhotos === null || $tenantPhotoLimit < $maxPhotos)) {
|
||||
if ($eventPackage->used_photos >= $tenantPhotoLimit) {
|
||||
return [
|
||||
'code' => 'tenant_photo_limit_exceeded',
|
||||
'title' => 'Tenant photo limit reached',
|
||||
'message' => 'This tenant has reached its photo allowance for the event.',
|
||||
'status' => 402,
|
||||
'meta' => [
|
||||
'scope' => 'photos',
|
||||
'used' => (int) $eventPackage->used_photos,
|
||||
'limit' => (int) $tenantPhotoLimit,
|
||||
'remaining' => max(0, (int) $tenantPhotoLimit - (int) $eventPackage->used_photos),
|
||||
'event_id' => $event->id,
|
||||
'limit_source' => 'tenant',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$storageLimitBytes = $this->tenantUsageService->storageLimitBytes($tenant);
|
||||
|
||||
if ($storageLimitBytes !== null) {
|
||||
$usedBytes = $this->tenantUsageService->storageUsedBytes($tenant);
|
||||
$projectedBytes = $usedBytes + max(0, (int) ($incomingBytes ?? 0));
|
||||
|
||||
if ($projectedBytes >= $storageLimitBytes) {
|
||||
return [
|
||||
'code' => 'tenant_storage_limit_exceeded',
|
||||
'title' => 'Tenant storage limit reached',
|
||||
'message' => 'This tenant has reached its storage allowance.',
|
||||
'status' => 402,
|
||||
'meta' => [
|
||||
'scope' => 'storage',
|
||||
'used_bytes' => $usedBytes,
|
||||
'limit_bytes' => $storageLimitBytes,
|
||||
'remaining_bytes' => max(0, $storageLimitBytes - $usedBytes),
|
||||
'event_id' => $event->id,
|
||||
'limit_source' => 'tenant',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -307,4 +355,19 @@ class PackageLimitEvaluator
|
||||
'expired_notified_at' => $eventPackage->gallery_expired_notified_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeTenantLimit(?int $value): ?int
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = (int) $value;
|
||||
|
||||
if ($value <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
27
app/Services/Tenant/TenantLifecycleLogger.php
Normal file
27
app/Services/Tenant/TenantLifecycleLogger.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tenant;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use App\Models\TenantLifecycleEvent;
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonInterface;
|
||||
|
||||
class TenantLifecycleLogger
|
||||
{
|
||||
public function record(
|
||||
Tenant $tenant,
|
||||
string $type,
|
||||
array $payload = [],
|
||||
?User $actor = null,
|
||||
?CarbonInterface $occurredAt = null
|
||||
): TenantLifecycleEvent {
|
||||
return TenantLifecycleEvent::create([
|
||||
'tenant_id' => $tenant->getKey(),
|
||||
'actor_id' => $actor?->getKey(),
|
||||
'type' => $type,
|
||||
'payload' => $payload,
|
||||
'occurred_at' => $occurredAt ?? now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
59
app/Services/Tenant/TenantUsageService.php
Normal file
59
app/Services/Tenant/TenantUsageService.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tenant;
|
||||
|
||||
use App\Models\EventMediaAsset;
|
||||
use App\Models\Tenant;
|
||||
|
||||
class TenantUsageService
|
||||
{
|
||||
public function storageSummary(Tenant $tenant): array
|
||||
{
|
||||
$limitBytes = $this->storageLimitBytes($tenant);
|
||||
$usedBytes = $this->storageUsedBytes($tenant);
|
||||
$remainingBytes = $limitBytes !== null
|
||||
? max(0, $limitBytes - $usedBytes)
|
||||
: null;
|
||||
|
||||
return [
|
||||
'used_bytes' => $usedBytes,
|
||||
'limit_bytes' => $limitBytes,
|
||||
'remaining_bytes' => $remainingBytes,
|
||||
'used_mb' => $this->bytesToMegabytes($usedBytes),
|
||||
'limit_mb' => $limitBytes !== null ? $this->bytesToMegabytes($limitBytes) : null,
|
||||
'remaining_mb' => $remainingBytes !== null ? $this->bytesToMegabytes($remainingBytes) : null,
|
||||
'percentage' => $limitBytes !== null && $limitBytes > 0
|
||||
? round(min(1, $usedBytes / $limitBytes) * 100, 1)
|
||||
: null,
|
||||
];
|
||||
}
|
||||
|
||||
public function storageLimitBytes(Tenant $tenant): ?int
|
||||
{
|
||||
$limitMb = $tenant->max_storage_mb;
|
||||
|
||||
if ($limitMb === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$limitMb = (int) $limitMb;
|
||||
|
||||
if ($limitMb <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $limitMb * 1024 * 1024;
|
||||
}
|
||||
|
||||
public function storageUsedBytes(Tenant $tenant): int
|
||||
{
|
||||
return (int) EventMediaAsset::query()
|
||||
->whereHas('event', fn ($query) => $query->where('tenant_id', $tenant->getKey()))
|
||||
->sum('size_bytes');
|
||||
}
|
||||
|
||||
private function bytesToMegabytes(int $bytes): float
|
||||
{
|
||||
return round($bytes / (1024 * 1024), 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user