optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -0,0 +1,231 @@
<?php
declare(strict_types=1);
namespace App\Services\NovaCards;
use App\Models\NovaCard;
use App\Models\User;
class NovaCardPublishModerationService
{
private const REASON_LABELS = [
'duplicate_content' => 'Duplicate content',
'self_remix_loop' => 'Self-remix loop',
];
public const DISPOSITION_LABELS = [
'cleared_after_review' => 'Cleared after review',
'approved_with_watch' => 'Approved with watch',
'escalated_for_review' => 'Escalated for review',
'rights_review_required' => 'Rights review required',
'rejected_after_review' => 'Rejected after review',
'returned_to_pending' => 'Returned to pending',
];
public function evaluate(NovaCard $card): array
{
$card->loadMissing(['originalCard.user', 'rootCard.user']);
$reasons = [];
if ($this->hasDuplicateContent($card)) {
$reasons[] = 'duplicate_content';
}
if ($this->hasSelfRemixLoop($card)) {
$reasons[] = 'self_remix_loop';
}
return [
'flagged' => $reasons !== [],
'reasons' => $reasons,
];
}
public function moderationStatus(NovaCard $card): string
{
return $this->evaluate($card)['flagged'] ? NovaCard::MOD_FLAGGED : NovaCard::MOD_APPROVED;
}
public function applyPublishOutcome(NovaCard $card, array $evaluation): NovaCard
{
$project = (array) ($card->project_json ?? []);
$project['moderation'] = [
'source' => 'publish_heuristics',
'flagged' => (bool) ($evaluation['flagged'] ?? false),
'reasons' => $this->normalizeReasons($evaluation['reasons'] ?? []),
'updated_at' => now()->toISOString(),
];
$card->forceFill([
'project_json' => $project,
'status' => NovaCard::STATUS_PUBLISHED,
'moderation_status' => (bool) ($evaluation['flagged'] ?? false) ? NovaCard::MOD_FLAGGED : NovaCard::MOD_APPROVED,
])->save();
return $card->refresh();
}
public function storedReasons(NovaCard $card): array
{
return $this->normalizeReasons(((array) (($card->project_json ?? [])['moderation'] ?? []))['reasons'] ?? []);
}
public function storedReasonLabels(NovaCard $card): array
{
return $this->labelsFor($this->storedReasons($card));
}
public function storedSource(NovaCard $card): ?string
{
$source = ((array) (($card->project_json ?? [])['moderation'] ?? []))['source'] ?? null;
return is_string($source) && $source !== '' ? $source : null;
}
public function latestOverride(NovaCard $card): ?array
{
$override = ((array) (($card->project_json ?? [])['moderation'] ?? []))['override'] ?? null;
return is_array($override) && $override !== [] ? $override : null;
}
public function dispositionOptions(?string $moderationStatus = null): array
{
$keys = match ($moderationStatus) {
NovaCard::MOD_APPROVED => ['cleared_after_review', 'approved_with_watch'],
NovaCard::MOD_FLAGGED => ['escalated_for_review', 'rights_review_required'],
NovaCard::MOD_REJECTED => ['rejected_after_review'],
NovaCard::MOD_PENDING => ['returned_to_pending'],
default => array_keys(self::DISPOSITION_LABELS),
};
return array_values(array_map(fn (string $key): array => [
'value' => $key,
'label' => self::DISPOSITION_LABELS[$key] ?? ucwords(str_replace('_', ' ', $key)),
], $keys));
}
public function overrideHistory(NovaCard $card): array
{
$history = ((array) (($card->project_json ?? [])['moderation'] ?? []))['override_history'] ?? [];
return array_values(array_filter($history, fn ($entry): bool => is_array($entry) && $entry !== []));
}
public function recordStaffOverride(
NovaCard $card,
string $moderationStatus,
?User $actor,
string $source,
array $context = [],
): NovaCard {
$project = (array) ($card->project_json ?? []);
$moderation = (array) ($project['moderation'] ?? []);
$disposition = $this->normalizeDisposition(
$context['disposition'] ?? $this->defaultDispositionForStatus($moderationStatus),
$moderationStatus,
);
$override = array_filter([
'moderation_status' => $moderationStatus,
'previous_status' => (string) $card->moderation_status,
'disposition' => $disposition,
'disposition_label' => self::DISPOSITION_LABELS[$disposition] ?? ucwords(str_replace('_', ' ', $disposition)),
'source' => $source,
'actor_user_id' => $actor?->id,
'actor_username' => $actor?->username,
'note' => isset($context['note']) && is_string($context['note']) && trim($context['note']) !== '' ? trim($context['note']) : null,
'report_id' => isset($context['report_id']) ? (int) $context['report_id'] : null,
'updated_at' => now()->toISOString(),
], fn ($value): bool => $value !== null);
$history = $this->overrideHistory($card);
array_unshift($history, $override);
$moderation['override'] = $override;
$moderation['override_history'] = array_slice($history, 0, 10);
$project['moderation'] = $moderation;
$card->forceFill([
'moderation_status' => $moderationStatus,
'project_json' => $project,
])->save();
return $card->refresh();
}
public function labelsFor(array $reasons): array
{
return array_values(array_map(
fn (string $reason): string => self::REASON_LABELS[$reason] ?? ucwords(str_replace('_', ' ', $reason)),
$this->normalizeReasons($reasons),
));
}
private function normalizeReasons(array $reasons): array
{
return array_values(array_unique(array_filter(array_map(
fn ($reason): string => is_string($reason) ? trim($reason) : '',
$reasons,
))));
}
private function normalizeDisposition(mixed $disposition, string $moderationStatus): string
{
$value = is_string($disposition) ? trim($disposition) : '';
return $value !== '' && array_key_exists($value, self::DISPOSITION_LABELS)
? $value
: $this->defaultDispositionForStatus($moderationStatus);
}
private function defaultDispositionForStatus(string $moderationStatus): string
{
return match ($moderationStatus) {
NovaCard::MOD_APPROVED => 'cleared_after_review',
NovaCard::MOD_FLAGGED => 'escalated_for_review',
NovaCard::MOD_REJECTED => 'rejected_after_review',
default => 'returned_to_pending',
};
}
private function hasDuplicateContent(NovaCard $card): bool
{
$title = mb_strtolower(trim((string) $card->title));
$quote = mb_strtolower(trim((string) $card->quote_text));
if ($title === '' || $quote === '') {
return false;
}
return NovaCard::query()
->where('id', '!=', $card->id)
->where('status', NovaCard::STATUS_PUBLISHED)
->whereNotIn('moderation_status', [NovaCard::MOD_FLAGGED, NovaCard::MOD_REJECTED])
->whereRaw('LOWER(title) = ?', [$title])
->whereRaw('LOWER(quote_text) = ?', [$quote])
->exists();
}
private function hasSelfRemixLoop(NovaCard $card): bool
{
if (! $card->originalCard || ! $card->rootCard) {
return false;
}
$depth = 0;
$cursor = $card;
$visited = [];
while ($cursor->originalCard && ! in_array($cursor->id, $visited, true)) {
$visited[] = $cursor->id;
$cursor = $cursor->originalCard;
$depth++;
}
return $depth >= 3
&& (int) $card->user_id === (int) ($card->originalCard->user_id ?? 0)
&& (int) $card->user_id === (int) ($card->rootCard->user_id ?? 0);
}
}