Implement creator studio and upload updates
This commit is contained in:
292
app/Services/Moderation/ContentModerationReviewService.php
Normal file
292
app/Services/Moderation/ContentModerationReviewService.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Moderation;
|
||||
|
||||
use App\Enums\ModerationActionType;
|
||||
use App\Enums\ModerationContentType;
|
||||
use App\Enums\ModerationStatus;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkComment;
|
||||
use App\Models\ContentModerationFinding;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ContentModerationReviewService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ContentModerationActionLogService $actionLogs,
|
||||
private readonly DomainReputationService $domains,
|
||||
private readonly ModerationFeedbackService $feedback,
|
||||
) {
|
||||
}
|
||||
|
||||
public function markSafe(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void
|
||||
{
|
||||
$this->updateFinding($finding, ModerationStatus::ReviewedSafe, $reviewer, $notes, ModerationActionType::MarkSafe);
|
||||
$this->feedback->record($finding->fresh(), 'marked_safe', $reviewer, $notes);
|
||||
}
|
||||
|
||||
public function confirmSpam(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void
|
||||
{
|
||||
$this->updateFinding($finding, ModerationStatus::ConfirmedSpam, $reviewer, $notes, ModerationActionType::ConfirmSpam);
|
||||
$this->domains->trackDomains((array) $finding->matched_domains_json, true, true);
|
||||
$this->feedback->record($finding->fresh(), 'confirmed_spam', $reviewer, $notes);
|
||||
}
|
||||
|
||||
public function ignore(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void
|
||||
{
|
||||
$this->updateFinding($finding, ModerationStatus::Ignored, $reviewer, $notes, ModerationActionType::Ignore);
|
||||
}
|
||||
|
||||
public function resolve(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void
|
||||
{
|
||||
$this->updateFinding($finding, ModerationStatus::Resolved, $reviewer, $notes, ModerationActionType::Resolve);
|
||||
$this->feedback->record($finding->fresh(), 'resolved', $reviewer, $notes);
|
||||
}
|
||||
|
||||
public function markFalsePositive(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void
|
||||
{
|
||||
DB::transaction(function () use ($finding, $reviewer, $notes): void {
|
||||
$oldVisibility = null;
|
||||
$newVisibility = null;
|
||||
|
||||
if (in_array($finding->content_type, [ModerationContentType::ArtworkComment, ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle], true)) {
|
||||
[$action, $oldVisibility, $newVisibility] = match ($finding->content_type) {
|
||||
ModerationContentType::ArtworkComment => $this->restoreComment($finding),
|
||||
default => $this->restoreArtwork($finding),
|
||||
};
|
||||
|
||||
$actionType = $action;
|
||||
} else {
|
||||
$actionType = ModerationActionType::MarkFalsePositive;
|
||||
}
|
||||
|
||||
$oldStatus = $finding->status->value;
|
||||
$finding->forceFill([
|
||||
'status' => ModerationStatus::ReviewedSafe,
|
||||
'reviewed_by' => $reviewer->id,
|
||||
'reviewed_at' => now(),
|
||||
'resolved_by' => $reviewer->id,
|
||||
'resolved_at' => now(),
|
||||
'restored_by' => $oldVisibility !== null ? $reviewer->id : $finding->restored_by,
|
||||
'restored_at' => $oldVisibility !== null ? now() : $finding->restored_at,
|
||||
'is_auto_hidden' => false,
|
||||
'is_false_positive' => true,
|
||||
'false_positive_count' => ((int) $finding->false_positive_count) + 1,
|
||||
'action_taken' => ModerationActionType::MarkFalsePositive->value,
|
||||
'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes),
|
||||
])->save();
|
||||
|
||||
$this->actionLogs->log(
|
||||
$finding,
|
||||
$finding->content_type->value,
|
||||
$finding->content_id,
|
||||
ModerationActionType::MarkFalsePositive->value,
|
||||
$reviewer,
|
||||
$oldStatus,
|
||||
ModerationStatus::ReviewedSafe->value,
|
||||
$oldVisibility,
|
||||
$newVisibility,
|
||||
$notes,
|
||||
['restored_action' => $actionType->value],
|
||||
);
|
||||
|
||||
$this->feedback->record($finding->fresh(), 'false_positive', $reviewer, $notes, ['restored' => $oldVisibility !== null]);
|
||||
});
|
||||
}
|
||||
|
||||
public function hideContent(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): ModerationActionType
|
||||
{
|
||||
return DB::transaction(function () use ($finding, $reviewer, $notes): ModerationActionType {
|
||||
[$action, $oldVisibility, $newVisibility] = match ($finding->content_type) {
|
||||
ModerationContentType::ArtworkComment => $this->hideComment($finding, false),
|
||||
ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle => $this->hideArtwork($finding, false),
|
||||
};
|
||||
|
||||
$this->updateFinding($finding, ModerationStatus::ConfirmedSpam, $reviewer, $notes, $action, $oldVisibility, $newVisibility);
|
||||
$this->domains->trackDomains((array) $finding->matched_domains_json, true, true);
|
||||
|
||||
return $action;
|
||||
});
|
||||
}
|
||||
|
||||
public function autoHideContent(ContentModerationFinding $finding, ?string $notes = null): ModerationActionType
|
||||
{
|
||||
return DB::transaction(function () use ($finding, $notes): ModerationActionType {
|
||||
[$action, $oldVisibility, $newVisibility] = match ($finding->content_type) {
|
||||
ModerationContentType::ArtworkComment => $this->hideComment($finding, true),
|
||||
ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle => $this->hideArtwork($finding, true),
|
||||
};
|
||||
|
||||
$finding->forceFill([
|
||||
'is_auto_hidden' => true,
|
||||
'auto_action_taken' => $action->value,
|
||||
'auto_hidden_at' => \now(),
|
||||
'action_taken' => $action->value,
|
||||
'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes),
|
||||
])->save();
|
||||
|
||||
$this->actionLogs->log(
|
||||
$finding,
|
||||
$finding->content_type->value,
|
||||
$finding->content_id,
|
||||
$action->value,
|
||||
null,
|
||||
$finding->status->value,
|
||||
$finding->status->value,
|
||||
$oldVisibility,
|
||||
$newVisibility,
|
||||
$notes,
|
||||
['automated' => true],
|
||||
);
|
||||
|
||||
$this->domains->trackDomains((array) $finding->matched_domains_json, true, false);
|
||||
|
||||
return $action;
|
||||
});
|
||||
}
|
||||
|
||||
public function restoreContent(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): ModerationActionType
|
||||
{
|
||||
return DB::transaction(function () use ($finding, $reviewer, $notes): ModerationActionType {
|
||||
[$action, $oldVisibility, $newVisibility] = match ($finding->content_type) {
|
||||
ModerationContentType::ArtworkComment => $this->restoreComment($finding),
|
||||
ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle => $this->restoreArtwork($finding),
|
||||
};
|
||||
|
||||
$oldStatus = $finding->status->value;
|
||||
|
||||
$finding->forceFill([
|
||||
'status' => ModerationStatus::ReviewedSafe,
|
||||
'reviewed_by' => $reviewer->id,
|
||||
'reviewed_at' => \now(),
|
||||
'resolved_by' => $reviewer->id,
|
||||
'resolved_at' => \now(),
|
||||
'restored_by' => $reviewer->id,
|
||||
'restored_at' => \now(),
|
||||
'is_auto_hidden' => false,
|
||||
'action_taken' => $action->value,
|
||||
'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes),
|
||||
])->save();
|
||||
|
||||
$this->actionLogs->log(
|
||||
$finding,
|
||||
$finding->content_type->value,
|
||||
$finding->content_id,
|
||||
$action->value,
|
||||
$reviewer,
|
||||
$oldStatus,
|
||||
ModerationStatus::ReviewedSafe->value,
|
||||
$oldVisibility,
|
||||
$newVisibility,
|
||||
$notes,
|
||||
['restored' => true],
|
||||
);
|
||||
|
||||
return $action;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:ModerationActionType,1:string,2:string}
|
||||
*/
|
||||
private function hideComment(ContentModerationFinding $finding, bool $automated): array
|
||||
{
|
||||
$comment = ArtworkComment::query()->find($finding->content_id);
|
||||
$oldVisibility = $comment && $comment->is_approved ? 'visible' : 'hidden';
|
||||
|
||||
if ($comment) {
|
||||
$comment->forceFill(['is_approved' => false])->save();
|
||||
}
|
||||
|
||||
return [$automated ? ModerationActionType::AutoHideComment : ModerationActionType::HideComment, $oldVisibility, 'hidden'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:ModerationActionType,1:string,2:string}
|
||||
*/
|
||||
private function hideArtwork(ContentModerationFinding $finding, bool $automated): array
|
||||
{
|
||||
$artworkId = (int) ($finding->artwork_id ?? $finding->content_id);
|
||||
$artwork = Artwork::query()->find($artworkId);
|
||||
$oldVisibility = $artwork && $artwork->is_public ? 'visible' : 'hidden';
|
||||
|
||||
if ($artwork) {
|
||||
$artwork->forceFill(['is_public' => false])->save();
|
||||
}
|
||||
|
||||
return [$automated ? ModerationActionType::AutoHideArtwork : ModerationActionType::HideArtwork, $oldVisibility, 'hidden'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:ModerationActionType,1:string,2:string}
|
||||
*/
|
||||
private function restoreComment(ContentModerationFinding $finding): array
|
||||
{
|
||||
$comment = ArtworkComment::query()->find($finding->content_id);
|
||||
$oldVisibility = $comment && $comment->is_approved ? 'visible' : 'hidden';
|
||||
|
||||
if ($comment) {
|
||||
$comment->forceFill(['is_approved' => true])->save();
|
||||
}
|
||||
|
||||
return [ModerationActionType::RestoreComment, $oldVisibility, 'visible'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:ModerationActionType,1:string,2:string}
|
||||
*/
|
||||
private function restoreArtwork(ContentModerationFinding $finding): array
|
||||
{
|
||||
$artworkId = (int) ($finding->artwork_id ?? $finding->content_id);
|
||||
$artwork = Artwork::query()->find($artworkId);
|
||||
$oldVisibility = $artwork && $artwork->is_public ? 'visible' : 'hidden';
|
||||
|
||||
if ($artwork) {
|
||||
$artwork->forceFill(['is_public' => true])->save();
|
||||
}
|
||||
|
||||
return [ModerationActionType::RestoreArtwork, $oldVisibility, 'visible'];
|
||||
}
|
||||
|
||||
private function updateFinding(
|
||||
ContentModerationFinding $finding,
|
||||
ModerationStatus $status,
|
||||
User $reviewer,
|
||||
?string $notes,
|
||||
ModerationActionType $action,
|
||||
?string $oldVisibility = null,
|
||||
?string $newVisibility = null,
|
||||
): void {
|
||||
$oldStatus = $finding->status->value;
|
||||
$finding->forceFill([
|
||||
'status' => $status,
|
||||
'reviewed_by' => $reviewer->id,
|
||||
'reviewed_at' => \now(),
|
||||
'resolved_by' => $reviewer->id,
|
||||
'resolved_at' => \now(),
|
||||
'action_taken' => $action->value,
|
||||
'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes),
|
||||
])->save();
|
||||
|
||||
$this->actionLogs->log(
|
||||
$finding,
|
||||
$finding->content_type->value,
|
||||
$finding->content_id,
|
||||
$action->value,
|
||||
$reviewer,
|
||||
$oldStatus,
|
||||
$status->value,
|
||||
$oldVisibility,
|
||||
$newVisibility,
|
||||
$notes,
|
||||
);
|
||||
}
|
||||
|
||||
private function normalizeNotes(?string $incoming, ?string $existing): ?string
|
||||
{
|
||||
$normalized = is_string($incoming) ? trim($incoming) : '';
|
||||
|
||||
return $normalized !== '' ? $normalized : $existing;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user