abortIfInvalidTarget($reportable, $reporter); $shouldNotifyModerators = false; try { $report = DB::transaction(function () use ($reportable, $reporter, $reason, $details, &$shouldNotifyModerators): Report { $moderationCase = $this->findOrCreateModerationCase($reportable); abort_if( Report::query() ->where('reporter_id', $reporter->getKey()) ->where('reportable_type', $reportable::class) ->where('reportable_id', $reportable->getKey()) ->exists(), 409, __('api.reports.already_exists'), ); $report = Report::create([ 'moderation_case_id' => $moderationCase->getKey(), 'reporter_id' => $reporter->getKey(), 'reportable_type' => $reportable::class, 'reportable_id' => $reportable->getKey(), 'reason' => $reason, 'details' => $details, 'status' => ReportStatus::OPEN, ]); $reportsCount = $moderationCase->reports()->count(); $updates = ['reports_count' => $reportsCount]; if ($reportsCount >= $moderationCase->threshold && $moderationCase->threshold_notified_at === null) { $updates['threshold_notified_at'] = now(); $shouldNotifyModerators = true; } $moderationCase->forceFill($updates)->save(); return $report->load('moderationCase.caseable'); }); } catch (QueryException $exception) { if ($this->isDuplicateReportException($exception)) { abort(409, __('api.reports.already_exists')); } throw $exception; } if ($shouldNotifyModerators) { $this->notifyModerators($report->moderationCase); } return $report; } private function findOrCreateModerationCase(Model $reportable): ModerationCase { $moderationCase = ModerationCase::query() ->where('caseable_type', $reportable::class) ->where('caseable_id', $reportable->getKey()) ->lockForUpdate() ->first(); if ($moderationCase) { return $moderationCase; } return ModerationCase::create([ 'caseable_type' => $reportable::class, 'caseable_id' => $reportable->getKey(), 'status' => ModerationCaseStatus::OPEN, 'threshold' => $this->thresholdFor($reportable), ]); } private function thresholdFor(Model $reportable): int { $thresholds = config('moderation.report_thresholds', []); return (int) ($thresholds[$reportable::class] ?? config('moderation.default_report_threshold', 3)); } private function notifyModerators(ModerationCase $moderationCase): void { $freshCase = $moderationCase->fresh('caseable'); if (! $freshCase) { return; } User::query() ->whereIn('role', [UserRole::ADMIN->value, UserRole::MODERATOR->value]) ->get() ->whenNotEmpty(fn ($moderators) => FilamentNotification::make() ->title(__('api.notifications.moderation_threshold_title')) ->body(__('api.notifications.moderation_threshold_body', [ 'type' => $freshCase->targetLabel(), 'count' => $freshCase->reports_count, ])) ->warning() ->sendToDatabase($moderators, isEventDispatched: true)); } private function abortIfInvalidTarget(Model $reportable, User $reporter): void { abort_if( $reportable instanceof User && $reportable->is($reporter), 422, __('api.reports.cannot_report_self'), ); abort_if( $reportable instanceof MealPosts && $reportable->user_id === $reporter->getKey(), 422, __('api.reports.cannot_report_own_meal'), ); abort_if( $reportable instanceof PostReviews && $reportable->user_id === $reporter->getKey(), 422, __('api.reports.cannot_report_own_review'), ); } private function isDuplicateReportException(QueryException $exception): bool { return $exception->getCode() === '23000' && str_contains($exception->getMessage(), 'reports_reporter_reportable_unique'); } }