Files
landing-api/app/Support/Expiring.php
Josep Piguillem 773d3901c6 Warn about calls closing and content coming off the site
A dashboard widget lists what expires in the next two weeks, and a daily
command reminds whoever edits it a week and a day ahead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:16:24 +02:00

84 lines
2.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support;
use App\Enums\CallStatus;
use App\Filament\Resources\Calls\CallResource;
use App\Models\Call;
use App\Models\Page;
use App\Models\Project;
use App\Models\Service;
use App\Models\User;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* What is about to stop being true on the site: calls about to close, and
* published content about to come off by itself.
*
* Neither is a mistake — both were set on purpose — which is why the health
* check does not flag them. But a call that closes on Friday with nobody
* having replied, or a page that vanishes on the first of the month, is better
* known about a week ahead than found out after.
*/
final class Expiring
{
/**
* @return Collection<int, array{type: string, record: Model, title: string, event: string, at: CarbonInterface, url: string}>
*/
public static function within(int $days): Collection
{
$until = now()->addDays($days)->endOfDay();
$items = collect();
Call::query()
->where('status', CallStatus::Open)
->whereNotNull('closes_at')
->whereBetween('closes_at', [now()->startOfDay(), $until])
->get()
->each(fn (Call $call) => $items->push(self::item('calls', $call, 'closes', $call->closes_at)));
foreach (['pages' => Page::class, 'projects' => Project::class, 'services' => Service::class] as $type => $model) {
$model::query()
->published()
->whereNotNull('unpublish_at')
->whereBetween('unpublish_at', [now(), $until])
->get()
->each(fn (Model $record) => $items->push(self::item($type, $record, 'unpublishes', $record->unpublish_at)));
}
return $items->sortBy(fn (array $item) => $item['at']->getTimestamp())->values();
}
/**
* Only those this person may do something about.
*
* @param Collection<int, array<string, mixed>> $items
* @return Collection<int, array<string, mixed>>
*/
public static function for(Collection $items, User $user): Collection
{
return $items->filter(fn (array $item) => $user->can('update', $item['record']))->values();
}
/**
* @return array{type: string, record: Model, title: string, event: string, at: CarbonInterface, url: string}
*/
protected static function item(string $type, Model $record, string $event, CarbonInterface $at): array
{
return [
'type' => $type,
'record' => $record,
'title' => ContentTypes::title($record),
'event' => $event,
'at' => $at,
'url' => $record instanceof Call
? CallResource::getUrl('edit', ['record' => $record])
: ReviewTarget::url($record),
];
}
}