Send new artwork uploads through a trust-based review policy.
Require review for untrusted accounts, add admin artwork review APIs, and keep queued or auto-trusted publishes from counting as established history.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\IndexArtworkJob;
|
||||
use App\Models\Artwork;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ArtworkModerationController extends Controller
|
||||
{
|
||||
public function pending(): JsonResponse
|
||||
{
|
||||
$artworks = Artwork::query()
|
||||
->with([
|
||||
'user:id,name,username,level,email',
|
||||
'tags:id,name,slug',
|
||||
'categories:id,name,slug',
|
||||
])
|
||||
->where('artwork_status', 'review')
|
||||
->where('is_approved', false)
|
||||
->latest('created_at')
|
||||
->limit(100)
|
||||
->get([
|
||||
'id', 'user_id', 'title', 'description', 'hash', 'thumb_ext',
|
||||
'artwork_status', 'moderation_note', 'created_at', 'slug',
|
||||
'is_mature', 'maturity_status', 'visibility',
|
||||
])
|
||||
->map(fn (Artwork $artwork): array => $this->present($artwork))
|
||||
->values();
|
||||
|
||||
return response()->json(['data' => $artworks], Response::HTTP_OK);
|
||||
}
|
||||
|
||||
public function approve(int $id, Request $request, NotificationService $notifications): JsonResponse
|
||||
{
|
||||
$artwork = Artwork::query()->with('user')->where('artwork_status', 'review')->find($id);
|
||||
if (! $artwork) {
|
||||
return response()->json(['message' => 'Artwork not found in review queue.'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$artwork->forceFill([
|
||||
'is_approved' => true,
|
||||
'is_public' => $artwork->visibility !== Artwork::VISIBILITY_PRIVATE,
|
||||
'artwork_status' => 'published',
|
||||
'published_at' => now(),
|
||||
'approval_source' => 'moderator',
|
||||
'moderated_at' => now(),
|
||||
'moderated_by' => $request->user()->id,
|
||||
'moderation_note' => $request->input('note'),
|
||||
])->save();
|
||||
|
||||
IndexArtworkJob::dispatch((int) $artwork->id);
|
||||
|
||||
if ($artwork->user) {
|
||||
$notifications->notifyArtworkApproved($artwork->user, $request->user(), $artwork);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'id' => $artwork->id, 'status' => 'published']);
|
||||
}
|
||||
|
||||
public function reject(int $id, Request $request, NotificationService $notifications): JsonResponse
|
||||
{
|
||||
$artwork = Artwork::query()->with('user')->where('artwork_status', 'review')->find($id);
|
||||
if (! $artwork) {
|
||||
return response()->json(['message' => 'Artwork not found in review queue.'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$note = (string) $request->input('note', 'Rejected during upload moderation.');
|
||||
|
||||
$artwork->forceFill([
|
||||
'is_approved' => false,
|
||||
'is_public' => false,
|
||||
'artwork_status' => 'rejected',
|
||||
'published_at' => null,
|
||||
'moderated_at' => now(),
|
||||
'moderated_by' => $request->user()->id,
|
||||
'moderation_note' => $note,
|
||||
])->save();
|
||||
|
||||
IndexArtworkJob::dispatch((int) $artwork->id);
|
||||
|
||||
if ($artwork->user) {
|
||||
$notifications->notifyArtworkRejected($artwork->user, $request->user(), $artwork, $note);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'id' => $artwork->id, 'status' => 'rejected']);
|
||||
}
|
||||
|
||||
private function present(Artwork $artwork): array
|
||||
{
|
||||
$previewUrl = $artwork->thumbUrl('md') ?: $artwork->thumbUrl('sm');
|
||||
$previewLgUrl = $artwork->thumbUrl('lg') ?: $previewUrl;
|
||||
|
||||
return [
|
||||
'id' => (int) $artwork->id,
|
||||
'title' => (string) ($artwork->title ?: '(untitled artwork)'),
|
||||
'description' => (string) ($artwork->description ?? ''),
|
||||
'type' => 'artwork',
|
||||
'preview_url' => $previewUrl,
|
||||
'preview_lg_url' => $previewLgUrl,
|
||||
'tags' => $artwork->tags
|
||||
->map(fn ($tag): string => trim((string) ($tag->name ?: $tag->slug)))
|
||||
->filter()
|
||||
->values()
|
||||
->all(),
|
||||
'categories' => $artwork->categories
|
||||
->map(fn ($category): string => trim((string) ($category->name ?: $category->slug)))
|
||||
->filter()
|
||||
->values()
|
||||
->all(),
|
||||
'user' => $artwork->user ? [
|
||||
'id' => (int) $artwork->user->id,
|
||||
'name' => (string) $artwork->user->name,
|
||||
'username' => $artwork->user->username,
|
||||
] : null,
|
||||
'slug' => $artwork->slug,
|
||||
'is_mature' => (bool) $artwork->is_mature,
|
||||
'maturity_status' => $artwork->maturity_status,
|
||||
'visibility' => $artwork->visibility,
|
||||
'moderation_note' => $artwork->moderation_note,
|
||||
'created_at' => optional($artwork->created_at)?->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user