Implement creator studio and upload updates

This commit is contained in:
2026-04-04 10:12:02 +02:00
parent 1da7d3bf88
commit 0b216b7ecd
15107 changed files with 31206 additions and 626514 deletions

View File

@@ -0,0 +1,168 @@
<?php
namespace App\Console\Commands;
use App\Enums\ModerationContentType;
use App\Enums\ModerationStatus;
use App\Models\ContentModerationFinding;
use App\Services\Moderation\ContentModerationActionLogService;
use App\Services\Moderation\ContentModerationProcessingService;
use App\Services\Moderation\ContentModerationSourceService;
use Illuminate\Console\Command;
class RescanContentModerationCommand extends Command
{
protected $signature = 'skinbase:rescan-content-moderation
{--only= : comments, descriptions, titles, bios, profile-links, collections, stories, cards, or a comma-separated list}
{--status=pending : Filter findings by moderation status}
{--limit= : Maximum number of findings to rescan}
{--from-id= : Start rescanning at or after this finding ID}
{--force : Rescan all matching findings, including already resolved findings}';
protected $description = 'Rescan existing moderation findings using the latest rules and scanner version.';
public function __construct(
private readonly ContentModerationProcessingService $processing,
private readonly ContentModerationSourceService $sources,
private readonly ContentModerationActionLogService $actionLogs,
) {
parent::__construct();
}
public function handle(): int
{
$limit = max(0, (int) ($this->option('limit') ?? 0));
$fromId = max(0, (int) ($this->option('from-id') ?? 0));
$status = trim((string) ($this->option('status') ?? 'pending'));
$force = (bool) $this->option('force');
$types = $this->selectedTypes();
$counts = [
'rescanned' => 0,
'updated' => 0,
'auto_hidden' => 0,
'missing_source' => 0,
];
$query = ContentModerationFinding::query()->orderBy('id');
if ($status !== '') {
$query->where('status', $status);
}
if ($fromId > 0) {
$query->where('id', '>=', $fromId);
}
if ($types !== []) {
$query->whereIn('content_type', array_map(static fn (ModerationContentType $type): string => $type->value, $types));
}
if (! $force) {
$query->where('status', ModerationStatus::Pending->value);
}
if ($limit > 0) {
$query->limit($limit);
}
$query->chunkById(100, function ($findings) use (&$counts): bool {
foreach ($findings as $finding) {
$counts['rescanned']++;
$rescanned = $this->processing->rescanFinding($finding, $this->sources);
if ($rescanned === null) {
$counts['missing_source']++;
continue;
}
$counts['updated']++;
if ($rescanned->is_auto_hidden) {
$counts['auto_hidden']++;
}
$this->actionLogs->log(
$rescanned,
'finding',
$rescanned->id,
'rescan',
null,
null,
$rescanned->status->value,
null,
null,
'Finding rescanned with the latest moderation rules.',
['scanner_version' => $rescanned->scanner_version],
);
}
return true;
}, 'id');
$this->table(['Metric', 'Count'], [
['Rescanned', $counts['rescanned']],
['Updated', $counts['updated']],
['Auto-hidden', $counts['auto_hidden']],
['Missing source', $counts['missing_source']],
]);
$this->info('Content moderation rescan complete.');
return self::SUCCESS;
}
/**
* @return array<int, ModerationContentType>
*/
private function selectedTypes(): array
{
$raw = trim((string) ($this->option('only') ?? ''));
if ($raw === '') {
return [];
}
$selected = \collect(explode(',', $raw))
->map(static fn (string $value): string => trim(strtolower($value)))
->filter()
->values();
$types = [];
if ($selected->contains('comments')) {
$types[] = ModerationContentType::ArtworkComment;
}
if ($selected->contains('descriptions')) {
$types[] = ModerationContentType::ArtworkDescription;
}
if ($selected->contains('titles') || $selected->contains('artwork_titles')) {
$types[] = ModerationContentType::ArtworkTitle;
}
if ($selected->contains('bios') || $selected->contains('user_bios')) {
$types[] = ModerationContentType::UserBio;
}
if ($selected->contains('profile-links') || $selected->contains('profile_links')) {
$types[] = ModerationContentType::UserProfileLink;
}
if ($selected->contains('collections') || $selected->contains('collection_titles')) {
$types[] = ModerationContentType::CollectionTitle;
$types[] = ModerationContentType::CollectionDescription;
}
if ($selected->contains('stories') || $selected->contains('story_titles')) {
$types[] = ModerationContentType::StoryTitle;
$types[] = ModerationContentType::StoryContent;
}
if ($selected->contains('cards') || $selected->contains('card_titles')) {
$types[] = ModerationContentType::CardTitle;
$types[] = ModerationContentType::CardText;
}
return $types;
}
}