Defer artwork comments from initial render
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
@@ -8,16 +7,13 @@ use App\Enums\ReactionType;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\ArtworkResource;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkComment;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\ContentSanitizer;
|
||||
use App\Services\ThumbnailPresenter;
|
||||
use App\Services\ArtworkNavigationService;
|
||||
use App\Services\ErrorSuggestionService;
|
||||
use App\Services\GroupService;
|
||||
use App\Services\Maturity\ArtworkMaturityService;
|
||||
use App\Support\Seo\SeoFactory;
|
||||
use App\Support\AvatarUrl;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -32,6 +28,7 @@ final class ArtworkPageController extends Controller
|
||||
public function __construct(
|
||||
private readonly GroupService $groups,
|
||||
private readonly ArtworkMaturityService $maturity,
|
||||
private readonly ArtworkNavigationService $navigation,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, int $id, ?string $slug = null): View|RedirectResponse|Response|InertiaResponse
|
||||
@@ -98,12 +95,13 @@ final class ArtworkPageController extends Controller
|
||||
]), 403);
|
||||
}
|
||||
|
||||
// ── Step 2: full load with all relations ───────────────────────────
|
||||
$artwork = Artwork::with(['user.profile', 'group.owner.profile', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile', 'categories.contentType', 'categories.parent.contentType', 'tags', 'stats', 'awardStat'])
|
||||
->where('id', $id)
|
||||
->public()
|
||||
->published()
|
||||
->firstOrFail();
|
||||
// ── Step 2: hydrate the already-validated model ────────────────────
|
||||
// The initial withTrashed lookup is intentionally retained so that
|
||||
// missing, deleted, private, and unpublished artwork keep their
|
||||
// distinct responses. Reusing that model avoids a second identical
|
||||
// primary-key lookup on the public path.
|
||||
$artwork = $raw;
|
||||
$artwork->load(['user.profile', 'group.owner.profile', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile', 'categories.contentType', 'categories.parent.contentType', 'tags', 'stats', 'awardStat']);
|
||||
|
||||
$this->loadCategoryAncestors($artwork->categories);
|
||||
|
||||
@@ -119,12 +117,15 @@ final class ArtworkPageController extends Controller
|
||||
], 301);
|
||||
}
|
||||
|
||||
$thumbMd = ThumbnailPresenter::present($artwork, 'md');
|
||||
$thumbLg = ThumbnailPresenter::present($artwork, 'lg');
|
||||
$thumbXl = ThumbnailPresenter::present($artwork, 'xl');
|
||||
$thumbSq = ThumbnailPresenter::present($artwork, 'sq');
|
||||
|
||||
$artworkData = (new ArtworkResource($artwork))->toArray($request);
|
||||
$navigationData = $this->navigation->navigationFor($artwork);
|
||||
// ArtworkResource is the canonical thumbnail projection. Reuse its
|
||||
// exact values for page props and SEO instead of presenting each
|
||||
// variant a second time in the controller.
|
||||
$thumbMd = $artworkData['thumbs']['md'] ?? [];
|
||||
$thumbLg = $artworkData['thumbs']['lg'] ?? [];
|
||||
$thumbXl = $artworkData['thumbs']['xl'] ?? [];
|
||||
$thumbSq = $artworkData['thumbs']['sq'] ?? [];
|
||||
$groupSummary = null;
|
||||
|
||||
if ($artwork->group) {
|
||||
@@ -206,84 +207,22 @@ final class ArtworkPageController extends Controller
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$approvedComments = ArtworkComment::query()
|
||||
->with('user.profile')
|
||||
->where('artwork_id', $artwork->id)
|
||||
->where('is_approved', true)
|
||||
->orderBy('created_at')
|
||||
->limit(500)
|
||||
->get();
|
||||
|
||||
$commentsByParent = $approvedComments->groupBy(
|
||||
static fn (ArtworkComment $comment): string => $comment->parent_id === null
|
||||
? 'root'
|
||||
: (string) $comment->parent_id
|
||||
);
|
||||
|
||||
// Recursive helper to format a comment and its nested replies.
|
||||
$formatComment = null;
|
||||
$formatComment = function (ArtworkComment $c) use (&$formatComment, $commentsByParent): array {
|
||||
/** @var Collection<int, ArtworkComment> $replies */
|
||||
$replies = $commentsByParent->get((string) $c->id, collect());
|
||||
$user = $c->user;
|
||||
$userId = (int) ($c->user_id ?? 0);
|
||||
$avatarHash = $user?->profile?->avatar_hash ?? null;
|
||||
$canPublishLinks = (int) ($user?->level ?? 1) > 1 && strtolower((string) ($user?->rank ?? 'Newbie')) !== 'newbie';
|
||||
$rawContent = (string) ($c->raw_content ?? $c->content ?? '');
|
||||
$renderedContent = $c->rendered_content;
|
||||
|
||||
if (! is_string($renderedContent) || trim($renderedContent) === '') {
|
||||
$renderedContent = $rawContent !== ''
|
||||
? ContentSanitizer::render($rawContent)
|
||||
: nl2br(e(strip_tags((string) ($c->content ?? ''))));
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $c->id,
|
||||
'parent_id' => $c->parent_id,
|
||||
'content' => html_entity_decode((string) $c->content, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'raw_content' => $c->raw_content ?? $c->content,
|
||||
'rendered_content' => ContentSanitizer::sanitizeRenderedHtml($renderedContent, $canPublishLinks),
|
||||
'created_at' => $c->created_at?->toIso8601String(),
|
||||
'time_ago' => $c->created_at ? Carbon::parse($c->created_at)->diffForHumans() : null,
|
||||
'user' => [
|
||||
'id' => $userId,
|
||||
'name' => $user?->name,
|
||||
'username' => $user?->username,
|
||||
'display' => $user?->username ?? $user?->name ?? 'User',
|
||||
'profile_url' => $user?->username ? '/@' . $user->username : ($userId > 0 ? '/profile/' . $userId : null),
|
||||
'avatar_url' => $avatarHash !== null
|
||||
? AvatarUrl::forUser($userId, $avatarHash, 64)
|
||||
: AvatarUrl::default(),
|
||||
'level' => (int) ($user?->level ?? 1),
|
||||
'rank' => (string) ($user?->rank ?? 'Newbie'),
|
||||
],
|
||||
'replies' => $replies->map($formatComment)->values()->all(),
|
||||
];
|
||||
};
|
||||
|
||||
$comments = $commentsByParent
|
||||
->get('root', collect())
|
||||
->map($formatComment)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$canReadSession = $request->hasSession() && ! $request->attributes->get('skinbase.session_skipped');
|
||||
$viewerId = ($canReadSession && $request->user() !== null) ? (int) $request->user()->id : null;
|
||||
|
||||
$userId = ($canReadSession && $request->user() !== null) ? (int) $request->user()->id : null;
|
||||
|
||||
return Inertia::render('ArtworkPage', [
|
||||
'artwork' => $artworkData,
|
||||
'navigation' => $navigationData,
|
||||
'presentMd' => $thumbMd,
|
||||
'presentLg' => $thumbLg,
|
||||
'presentXl' => $thumbXl,
|
||||
'presentSq' => $thumbSq,
|
||||
'related' => $related,
|
||||
'canonicalUrl' => $canonical,
|
||||
'comments' => $comments,
|
||||
'groupSummary' => $groupSummary,
|
||||
'isAuthenticated' => $userId !== null,
|
||||
'reactionTotals' => $this->artworkReactionTotals((int) $artwork->id, $userId),
|
||||
'isAuthenticated' => $viewerId !== null,
|
||||
'reactionTotals' => $this->artworkReactionTotals((int) $artwork->id, $viewerId),
|
||||
'seo' => $seo,
|
||||
])->rootView('artworks.show');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user