optimizations
This commit is contained in:
@@ -3,23 +3,231 @@
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\NovaCard;
|
||||
use App\Models\Report;
|
||||
use App\Models\ReportHistory;
|
||||
use App\Services\NovaCards\NovaCardPublishModerationService;
|
||||
use App\Support\Moderation\ReportTargetResolver;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class ModerationReportQueueController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReportTargetResolver $targets,
|
||||
private readonly NovaCardPublishModerationService $moderation,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$status = (string) $request->query('status', 'open');
|
||||
$status = in_array($status, ['open', 'reviewing', 'closed'], true) ? $status : 'open';
|
||||
$group = (string) $request->query('group', '');
|
||||
|
||||
$items = Report::query()
|
||||
->with('reporter:id,username')
|
||||
$query = Report::query()
|
||||
->with(['reporter:id,username', 'lastModeratedBy:id,username', 'historyEntries.actor:id,username'])
|
||||
->where('status', $status)
|
||||
->orderByDesc('id')
|
||||
->paginate(30);
|
||||
->orderByDesc('id');
|
||||
|
||||
return response()->json($items);
|
||||
if ($group === 'nova_cards') {
|
||||
$query->whereIn('target_type', $this->targets->novaCardTargetTypes());
|
||||
}
|
||||
|
||||
$items = $query->paginate(30);
|
||||
|
||||
return response()->json([
|
||||
'data' => collect($items->items())
|
||||
->map(fn (Report $report): array => $this->serializeReport($report))
|
||||
->values()
|
||||
->all(),
|
||||
'meta' => [
|
||||
'current_page' => $items->currentPage(),
|
||||
'last_page' => $items->lastPage(),
|
||||
'per_page' => $items->perPage(),
|
||||
'total' => $items->total(),
|
||||
'from' => $items->firstItem(),
|
||||
'to' => $items->lastItem(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Report $report): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'status' => 'sometimes|in:open,reviewing,closed',
|
||||
'moderator_note' => 'sometimes|nullable|string|max:2000',
|
||||
]);
|
||||
|
||||
$before = [];
|
||||
$after = [];
|
||||
$user = $request->user();
|
||||
|
||||
DB::transaction(function () use ($data, $report, $user, &$before, &$after): void {
|
||||
if (array_key_exists('status', $data) && $data['status'] !== $report->status) {
|
||||
$before['status'] = (string) $report->status;
|
||||
$after['status'] = (string) $data['status'];
|
||||
$report->status = $data['status'];
|
||||
}
|
||||
|
||||
if (array_key_exists('moderator_note', $data)) {
|
||||
$normalizedNote = is_string($data['moderator_note']) ? trim($data['moderator_note']) : null;
|
||||
$normalizedNote = $normalizedNote !== '' ? $normalizedNote : null;
|
||||
|
||||
if ($normalizedNote !== $report->moderator_note) {
|
||||
$before['moderator_note'] = $report->moderator_note;
|
||||
$after['moderator_note'] = $normalizedNote;
|
||||
$report->moderator_note = $normalizedNote;
|
||||
}
|
||||
}
|
||||
|
||||
if ($before !== [] || $after !== []) {
|
||||
$report->last_moderated_by_id = $user?->id;
|
||||
$report->last_moderated_at = now();
|
||||
$report->save();
|
||||
|
||||
$report->historyEntries()->create([
|
||||
'actor_user_id' => $user?->id,
|
||||
'action_type' => 'report_updated',
|
||||
'summary' => $this->buildUpdateSummary($before, $after),
|
||||
'note' => $report->moderator_note,
|
||||
'before_json' => $before !== [] ? $before : null,
|
||||
'after_json' => $after !== [] ? $after : null,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$report = $report->fresh(['reporter:id,username', 'lastModeratedBy:id,username', 'historyEntries.actor:id,username']);
|
||||
|
||||
return response()->json([
|
||||
'report' => $this->serializeReport($report),
|
||||
]);
|
||||
}
|
||||
|
||||
public function moderateTarget(Request $request, Report $report): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'action' => 'required|in:approve_card,flag_card,reject_card',
|
||||
'disposition' => 'nullable|in:' . implode(',', array_keys(NovaCardPublishModerationService::DISPOSITION_LABELS)),
|
||||
]);
|
||||
|
||||
$card = $this->targets->resolveModerationCard($report);
|
||||
abort_unless($card !== null, 422, 'This report does not have a Nova Card moderation target.');
|
||||
|
||||
DB::transaction(function () use ($card, $data, $report, $request): void {
|
||||
$before = [
|
||||
'card_id' => (int) $card->id,
|
||||
'moderation_status' => (string) $card->moderation_status,
|
||||
];
|
||||
|
||||
$nextStatus = match ($data['action']) {
|
||||
'approve_card' => NovaCard::MOD_APPROVED,
|
||||
'flag_card' => NovaCard::MOD_FLAGGED,
|
||||
'reject_card' => NovaCard::MOD_REJECTED,
|
||||
};
|
||||
$card = $this->moderation->recordStaffOverride(
|
||||
$card,
|
||||
$nextStatus,
|
||||
$request->user(),
|
||||
'report_queue',
|
||||
[
|
||||
'note' => $report->moderator_note,
|
||||
'report_id' => $report->id,
|
||||
'disposition' => $data['disposition'] ?? null,
|
||||
],
|
||||
);
|
||||
|
||||
$report->last_moderated_by_id = $request->user()?->id;
|
||||
$report->last_moderated_at = now();
|
||||
$report->save();
|
||||
|
||||
$report->historyEntries()->create([
|
||||
'actor_user_id' => $request->user()?->id,
|
||||
'action_type' => 'target_moderated',
|
||||
'summary' => $this->buildTargetModerationSummary($data['action'], $card),
|
||||
'note' => $report->moderator_note,
|
||||
'before_json' => $before,
|
||||
'after_json' => [
|
||||
'card_id' => (int) $card->id,
|
||||
'moderation_status' => (string) $card->moderation_status,
|
||||
'action' => (string) $data['action'],
|
||||
],
|
||||
'created_at' => now(),
|
||||
]);
|
||||
});
|
||||
|
||||
$report = $report->fresh(['reporter:id,username', 'lastModeratedBy:id,username', 'historyEntries.actor:id,username']);
|
||||
|
||||
return response()->json([
|
||||
'report' => $this->serializeReport($report),
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildUpdateSummary(array $before, array $after): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
if (array_key_exists('status', $after)) {
|
||||
$parts[] = sprintf('Status %s -> %s', $before['status'], $after['status']);
|
||||
}
|
||||
|
||||
if (array_key_exists('moderator_note', $after)) {
|
||||
$parts[] = $after['moderator_note'] ? 'Moderator note updated' : 'Moderator note cleared';
|
||||
}
|
||||
|
||||
return $parts !== [] ? implode(' • ', $parts) : 'Report reviewed';
|
||||
}
|
||||
|
||||
private function buildTargetModerationSummary(string $action, NovaCard $card): string
|
||||
{
|
||||
return match ($action) {
|
||||
'approve_card' => sprintf('Approved card #%d', $card->id),
|
||||
'flag_card' => sprintf('Flagged card #%d', $card->id),
|
||||
'reject_card' => sprintf('Rejected card #%d', $card->id),
|
||||
default => sprintf('Updated card #%d', $card->id),
|
||||
};
|
||||
}
|
||||
|
||||
private function serializeReport(Report $report): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $report->id,
|
||||
'status' => (string) $report->status,
|
||||
'target_type' => (string) $report->target_type,
|
||||
'target_id' => (int) $report->target_id,
|
||||
'reason' => (string) $report->reason,
|
||||
'details' => $report->details,
|
||||
'moderator_note' => $report->moderator_note,
|
||||
'created_at' => optional($report->created_at)?->toISOString(),
|
||||
'updated_at' => optional($report->updated_at)?->toISOString(),
|
||||
'last_moderated_at' => optional($report->last_moderated_at)?->toISOString(),
|
||||
'reporter' => $report->reporter ? [
|
||||
'id' => (int) $report->reporter->id,
|
||||
'username' => (string) $report->reporter->username,
|
||||
] : null,
|
||||
'last_moderated_by' => $report->lastModeratedBy ? [
|
||||
'id' => (int) $report->lastModeratedBy->id,
|
||||
'username' => (string) $report->lastModeratedBy->username,
|
||||
] : null,
|
||||
'target' => $this->targets->summarize($report),
|
||||
'history' => $report->historyEntries
|
||||
->take(8)
|
||||
->map(fn (ReportHistory $entry): array => [
|
||||
'id' => (int) $entry->id,
|
||||
'action_type' => (string) $entry->action_type,
|
||||
'summary' => $entry->summary,
|
||||
'note' => $entry->note,
|
||||
'before' => $entry->before_json,
|
||||
'after' => $entry->after_json,
|
||||
'created_at' => optional($entry->created_at)?->toISOString(),
|
||||
'actor' => $entry->actor ? [
|
||||
'id' => (int) $entry->actor->id,
|
||||
'username' => (string) $entry->actor->username,
|
||||
] : null,
|
||||
])
|
||||
->values()
|
||||
->all(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user