352 lines
16 KiB
PHP
352 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Moderation;
|
|
|
|
use App\Enums\ModerationContentType;
|
|
use App\Models\Collection;
|
|
use App\Models\NovaCard;
|
|
use App\Models\Story;
|
|
use App\Models\Artwork;
|
|
use App\Models\ArtworkComment;
|
|
use App\Models\ContentModerationFinding;
|
|
use App\Models\UserProfile;
|
|
use App\Models\UserSocialLink;
|
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
|
|
|
class ContentModerationSourceService
|
|
{
|
|
public function queryForType(ModerationContentType $type): EloquentBuilder
|
|
{
|
|
return match ($type) {
|
|
ModerationContentType::ArtworkComment => ArtworkComment::query()
|
|
->with('artwork:id,title,slug,user_id')
|
|
->whereNull('deleted_at')
|
|
->where(function (EloquentBuilder $query): void {
|
|
$query->whereNotNull('raw_content')->where('raw_content', '!=', '')
|
|
->orWhere(function (EloquentBuilder $fallback): void {
|
|
$fallback->whereNotNull('content')->where('content', '!=', '');
|
|
});
|
|
})
|
|
->orderBy('id'),
|
|
ModerationContentType::ArtworkDescription => Artwork::query()
|
|
->whereNotNull('description')
|
|
->where('description', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::ArtworkTitle => Artwork::query()
|
|
->whereNotNull('title')
|
|
->where('title', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::UserBio => UserProfile::query()
|
|
->with('user:id,username,name')
|
|
->where(function (EloquentBuilder $query): void {
|
|
$query->whereNotNull('about')->where('about', '!=', '')
|
|
->orWhere(function (EloquentBuilder $fallback): void {
|
|
$fallback->whereNotNull('description')->where('description', '!=', '');
|
|
});
|
|
})
|
|
->orderBy('user_id'),
|
|
ModerationContentType::UserProfileLink => UserSocialLink::query()
|
|
->with('user:id,username,name')
|
|
->whereNotNull('url')
|
|
->where('url', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::CollectionTitle => Collection::query()
|
|
->with('user:id,username,name')
|
|
->whereNotNull('title')
|
|
->where('title', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::CollectionDescription => Collection::query()
|
|
->with('user:id,username,name')
|
|
->whereNotNull('description')
|
|
->where('description', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::StoryTitle => Story::query()
|
|
->with('creator:id,username,name')
|
|
->whereNotNull('title')
|
|
->where('title', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::StoryContent => Story::query()
|
|
->with('creator:id,username,name')
|
|
->whereNotNull('content')
|
|
->where('content', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::CardTitle => NovaCard::query()
|
|
->with('user:id,username,name')
|
|
->whereNotNull('title')
|
|
->where('title', '!=', '')
|
|
->orderBy('id'),
|
|
ModerationContentType::CardText => NovaCard::query()
|
|
->with('user:id,username,name')
|
|
->where(function (EloquentBuilder $query): void {
|
|
$query->whereNotNull('quote_text')->where('quote_text', '!=', '')
|
|
->orWhere(function (EloquentBuilder $description): void {
|
|
$description->whereNotNull('description')->where('description', '!=', '');
|
|
});
|
|
})
|
|
->orderBy('id'),
|
|
default => throw new \InvalidArgumentException('Unsupported moderation content type: ' . $type->value),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param Artwork|ArtworkComment|Collection|Story|NovaCard|UserProfile|UserSocialLink $row
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function buildContext(ModerationContentType $type, object $row): array
|
|
{
|
|
return match ($type) {
|
|
ModerationContentType::ArtworkComment => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'artwork_comment',
|
|
'content_target_id' => (int) $row->id,
|
|
'artwork_id' => (int) $row->artwork_id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => (string) ($row->raw_content ?: $row->content),
|
|
'is_publicly_exposed' => true,
|
|
],
|
|
ModerationContentType::ArtworkDescription => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'artwork',
|
|
'content_target_id' => (int) $row->id,
|
|
'artwork_id' => (int) $row->id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => (string) ($row->description ?? ''),
|
|
'is_publicly_exposed' => (bool) ($row->is_public ?? false),
|
|
],
|
|
ModerationContentType::ArtworkTitle => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'artwork',
|
|
'content_target_id' => (int) $row->id,
|
|
'artwork_id' => (int) $row->id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => (string) ($row->title ?? ''),
|
|
'is_publicly_exposed' => (bool) ($row->is_public ?? false),
|
|
],
|
|
ModerationContentType::UserBio => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->user_id,
|
|
'content_target_type' => 'user_profile',
|
|
'content_target_id' => (int) $row->user_id,
|
|
'user_id' => (int) $row->user_id,
|
|
'content_snapshot' => trim((string) ($row->about ?: $row->description ?: '')),
|
|
'is_publicly_exposed' => true,
|
|
],
|
|
ModerationContentType::UserProfileLink => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'user_social_link',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => (int) $row->user_id,
|
|
'content_snapshot' => trim((string) ($row->url ?? '')),
|
|
'is_publicly_exposed' => true,
|
|
],
|
|
ModerationContentType::CollectionTitle => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'collection',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => (string) ($row->title ?? ''),
|
|
'is_publicly_exposed' => in_array((string) ($row->visibility ?? ''), ['public', 'unlisted'], true),
|
|
],
|
|
ModerationContentType::CollectionDescription => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'collection',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => (string) ($row->description ?? ''),
|
|
'is_publicly_exposed' => in_array((string) ($row->visibility ?? ''), ['public', 'unlisted'], true),
|
|
],
|
|
ModerationContentType::StoryTitle => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'story',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => $row->creator_id ? (int) $row->creator_id : null,
|
|
'content_snapshot' => (string) ($row->title ?? ''),
|
|
'is_publicly_exposed' => in_array((string) ($row->status ?? ''), ['published', 'scheduled'], true),
|
|
],
|
|
ModerationContentType::StoryContent => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'story',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => $row->creator_id ? (int) $row->creator_id : null,
|
|
'content_snapshot' => (string) ($row->content ?? ''),
|
|
'is_publicly_exposed' => in_array((string) ($row->status ?? ''), ['published', 'scheduled'], true),
|
|
],
|
|
ModerationContentType::CardTitle => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'nova_card',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => (string) ($row->title ?? ''),
|
|
'is_publicly_exposed' => in_array((string) ($row->visibility ?? ''), ['public', 'unlisted'], true),
|
|
],
|
|
ModerationContentType::CardText => [
|
|
'content_type' => $type->value,
|
|
'content_id' => (int) $row->id,
|
|
'content_target_type' => 'nova_card',
|
|
'content_target_id' => (int) $row->id,
|
|
'user_id' => $row->user_id ? (int) $row->user_id : null,
|
|
'content_snapshot' => trim(implode("\n", array_filter([
|
|
(string) ($row->quote_text ?? ''),
|
|
(string) ($row->description ?? ''),
|
|
(string) ($row->quote_author ?? ''),
|
|
(string) ($row->quote_source ?? ''),
|
|
]))),
|
|
'is_publicly_exposed' => in_array((string) ($row->visibility ?? ''), ['public', 'unlisted'], true),
|
|
],
|
|
default => throw new \InvalidArgumentException('Unsupported moderation content type: ' . $type->value),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array{context:array<string, mixed>|null, type:ModerationContentType|null}
|
|
*/
|
|
public function contextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
return match ($finding->content_type) {
|
|
ModerationContentType::ArtworkComment => $this->commentContextForFinding($finding),
|
|
ModerationContentType::ArtworkDescription => $this->descriptionContextForFinding($finding),
|
|
ModerationContentType::ArtworkTitle => $this->artworkTitleContextForFinding($finding),
|
|
ModerationContentType::UserBio => $this->userBioContextForFinding($finding),
|
|
ModerationContentType::UserProfileLink => $this->userProfileLinkContextForFinding($finding),
|
|
ModerationContentType::CollectionTitle => $this->collectionTitleContextForFinding($finding),
|
|
ModerationContentType::CollectionDescription => $this->collectionDescriptionContextForFinding($finding),
|
|
ModerationContentType::StoryTitle => $this->storyTitleContextForFinding($finding),
|
|
ModerationContentType::StoryContent => $this->storyContentContextForFinding($finding),
|
|
ModerationContentType::CardTitle => $this->cardTitleContextForFinding($finding),
|
|
ModerationContentType::CardText => $this->cardTextContextForFinding($finding),
|
|
default => ['context' => null, 'type' => null],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array{context:array<string, mixed>|null, type:ModerationContentType|null}
|
|
*/
|
|
private function commentContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$comment = ArtworkComment::query()->find($finding->content_id);
|
|
if (! $comment) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return [
|
|
'context' => $this->buildContext(ModerationContentType::ArtworkComment, $comment),
|
|
'type' => ModerationContentType::ArtworkComment,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{context:array<string, mixed>|null, type:ModerationContentType|null}
|
|
*/
|
|
private function descriptionContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$artworkId = (int) ($finding->artwork_id ?? $finding->content_id);
|
|
$artwork = Artwork::query()->find($artworkId);
|
|
if (! $artwork) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return [
|
|
'context' => $this->buildContext(ModerationContentType::ArtworkDescription, $artwork),
|
|
'type' => ModerationContentType::ArtworkDescription,
|
|
];
|
|
}
|
|
|
|
private function artworkTitleContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$artwork = Artwork::query()->find((int) ($finding->artwork_id ?? $finding->content_id));
|
|
if (! $artwork) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::ArtworkTitle, $artwork), 'type' => ModerationContentType::ArtworkTitle];
|
|
}
|
|
|
|
private function userBioContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$profile = UserProfile::query()->find($finding->content_id);
|
|
if (! $profile) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::UserBio, $profile), 'type' => ModerationContentType::UserBio];
|
|
}
|
|
|
|
private function userProfileLinkContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$link = UserSocialLink::query()->find($finding->content_id);
|
|
if (! $link) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::UserProfileLink, $link), 'type' => ModerationContentType::UserProfileLink];
|
|
}
|
|
|
|
private function collectionTitleContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$collection = Collection::query()->find($finding->content_id);
|
|
if (! $collection) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::CollectionTitle, $collection), 'type' => ModerationContentType::CollectionTitle];
|
|
}
|
|
|
|
private function collectionDescriptionContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$collection = Collection::query()->find($finding->content_id);
|
|
if (! $collection) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::CollectionDescription, $collection), 'type' => ModerationContentType::CollectionDescription];
|
|
}
|
|
|
|
private function storyTitleContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$story = Story::query()->find($finding->content_id);
|
|
if (! $story) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::StoryTitle, $story), 'type' => ModerationContentType::StoryTitle];
|
|
}
|
|
|
|
private function storyContentContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$story = Story::query()->find($finding->content_id);
|
|
if (! $story) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::StoryContent, $story), 'type' => ModerationContentType::StoryContent];
|
|
}
|
|
|
|
private function cardTitleContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$card = NovaCard::query()->find($finding->content_id);
|
|
if (! $card) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::CardTitle, $card), 'type' => ModerationContentType::CardTitle];
|
|
}
|
|
|
|
private function cardTextContextForFinding(ContentModerationFinding $finding): array
|
|
{
|
|
$card = NovaCard::query()->find($finding->content_id);
|
|
if (! $card) {
|
|
return ['context' => null, 'type' => null];
|
|
}
|
|
|
|
return ['context' => $this->buildContext(ModerationContentType::CardText, $card), 'type' => ModerationContentType::CardText];
|
|
}
|
|
} |