Repair: copy legacy joinDate into new user's created_at when creating users from legacy wallz
This commit is contained in:
@@ -197,7 +197,7 @@ class ArtworkCommentController extends Controller
|
||||
'id' => $c->id,
|
||||
'parent_id' => $c->parent_id,
|
||||
'raw_content' => $c->raw_content ?? $c->content,
|
||||
'rendered_content' => $c->rendered_content ?? e(strip_tags($c->content ?? '')),
|
||||
'rendered_content' => $this->renderCommentContent($c),
|
||||
'created_at' => $c->created_at?->toIso8601String(),
|
||||
'time_ago' => $c->created_at ? Carbon::parse($c->created_at)->diffForHumans() : null,
|
||||
'can_edit' => $currentUserId === $userId,
|
||||
@@ -224,6 +224,31 @@ class ArtworkCommentController extends Controller
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function renderCommentContent(ArtworkComment $comment): string
|
||||
{
|
||||
$rawContent = (string) ($comment->raw_content ?? $comment->content ?? '');
|
||||
$renderedContent = $comment->rendered_content;
|
||||
|
||||
if (! is_string($renderedContent) || trim($renderedContent) === '') {
|
||||
$renderedContent = $rawContent !== ''
|
||||
? ContentSanitizer::render($rawContent)
|
||||
: nl2br(e(strip_tags((string) ($comment->content ?? ''))));
|
||||
}
|
||||
|
||||
return ContentSanitizer::sanitizeRenderedHtml(
|
||||
$renderedContent,
|
||||
$this->commentAuthorCanPublishLinks($comment)
|
||||
);
|
||||
}
|
||||
|
||||
private function commentAuthorCanPublishLinks(ArtworkComment $comment): bool
|
||||
{
|
||||
$level = (int) ($comment->user?->level ?? 1);
|
||||
$rank = strtolower((string) ($comment->user?->rank ?? 'Newbie'));
|
||||
|
||||
return $level > 1 && $rank !== 'newbie';
|
||||
}
|
||||
|
||||
private function notifyRecipients(Artwork $artwork, ArtworkComment $comment, User $actor, ?int $parentId): void
|
||||
{
|
||||
$notifiedUserIds = [];
|
||||
|
||||
@@ -9,10 +9,11 @@ use App\Http\Requests\Messaging\RenameConversationRequest;
|
||||
use App\Http\Requests\Messaging\StoreConversationRequest;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\ConversationParticipant;
|
||||
use App\Models\Message;
|
||||
use App\Models\User;
|
||||
use App\Services\Messaging\ConversationReadService;
|
||||
use App\Services\Messaging\ConversationStateService;
|
||||
use App\Services\Messaging\SendMessageAction;
|
||||
use App\Services\Messaging\UnreadCounterService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -23,7 +24,9 @@ class ConversationController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConversationStateService $conversationState,
|
||||
private readonly ConversationReadService $conversationReads,
|
||||
private readonly SendMessageAction $sendMessage,
|
||||
private readonly UnreadCounterService $unreadCounters,
|
||||
) {}
|
||||
|
||||
// ── GET /api/messages/conversations ─────────────────────────────────────
|
||||
@@ -36,26 +39,13 @@ class ConversationController extends Controller
|
||||
$cacheKey = $this->conversationListCacheKey($user->id, $page, $cacheVersion);
|
||||
|
||||
$conversations = Cache::remember($cacheKey, now()->addSeconds(20), function () use ($user, $page) {
|
||||
return Conversation::query()
|
||||
$query = Conversation::query()
|
||||
->select('conversations.*')
|
||||
->join('conversation_participants as cp_me', function ($join) use ($user) {
|
||||
$join->on('cp_me.conversation_id', '=', 'conversations.id')
|
||||
->where('cp_me.user_id', '=', $user->id)
|
||||
->whereNull('cp_me.left_at');
|
||||
})
|
||||
->addSelect([
|
||||
'unread_count' => Message::query()
|
||||
->selectRaw('count(*)')
|
||||
->whereColumn('messages.conversation_id', 'conversations.id')
|
||||
->where('messages.sender_id', '!=', $user->id)
|
||||
->whereNull('messages.deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->whereNull('cp_me.last_read_message_id')
|
||||
->whereNull('cp_me.last_read_at')
|
||||
->orWhereColumn('messages.id', '>', 'cp_me.last_read_message_id')
|
||||
->orWhereColumn('messages.created_at', '>', 'cp_me.last_read_at');
|
||||
}),
|
||||
])
|
||||
->where('conversations.is_active', true)
|
||||
->with([
|
||||
'allParticipants' => fn ($q) => $q->whereNull('left_at')->with(['user:id,username']),
|
||||
@@ -64,8 +54,11 @@ class ConversationController extends Controller
|
||||
->orderByDesc('cp_me.is_pinned')
|
||||
->orderByDesc('cp_me.pinned_at')
|
||||
->orderByDesc('last_message_at')
|
||||
->orderByDesc('conversations.id')
|
||||
->paginate(20, ['conversations.*'], 'page', $page);
|
||||
->orderByDesc('conversations.id');
|
||||
|
||||
$this->unreadCounters->applyUnreadCountSelect($query, $user, 'cp_me');
|
||||
|
||||
return $query->paginate(20, ['conversations.*'], 'page', $page);
|
||||
});
|
||||
|
||||
$conversations->through(function ($conv) use ($user) {
|
||||
@@ -74,7 +67,12 @@ class ConversationController extends Controller
|
||||
return $conv;
|
||||
});
|
||||
|
||||
return response()->json($conversations);
|
||||
return response()->json([
|
||||
...$conversations->toArray(),
|
||||
'summary' => [
|
||||
'unread_total' => $this->unreadCounters->totalUnreadForUser($user),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
// ── GET /api/messages/conversation/{id} ─────────────────────────────────
|
||||
@@ -110,7 +108,7 @@ class ConversationController extends Controller
|
||||
public function markRead(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$conversation = $this->findAuthorized($request, $id);
|
||||
$participant = $this->conversationState->markConversationRead(
|
||||
$participant = $this->conversationReads->markConversationRead(
|
||||
$conversation,
|
||||
$request->user(),
|
||||
$request->integer('message_id') ?: null,
|
||||
@@ -120,6 +118,7 @@ class ConversationController extends Controller
|
||||
'ok' => true,
|
||||
'last_read_at' => optional($participant->last_read_at)?->toIso8601String(),
|
||||
'last_read_message_id' => $participant->last_read_message_id,
|
||||
'unread_total' => $this->unreadCounters->totalUnreadForUser($request->user()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Models\Conversation;
|
||||
use App\Models\ConversationParticipant;
|
||||
use App\Models\Message;
|
||||
use App\Models\MessageReaction;
|
||||
use App\Services\Messaging\ConversationDeltaService;
|
||||
use App\Services\Messaging\ConversationStateService;
|
||||
use App\Services\Messaging\MessagingPayloadFactory;
|
||||
use App\Services\Messaging\MessageSearchIndexer;
|
||||
@@ -26,6 +27,7 @@ class MessageController extends Controller
|
||||
private const PAGE_SIZE = 30;
|
||||
|
||||
public function __construct(
|
||||
private readonly ConversationDeltaService $conversationDelta,
|
||||
private readonly ConversationStateService $conversationState,
|
||||
private readonly MessagingPayloadFactory $payloadFactory,
|
||||
private readonly SendMessageAction $sendMessage,
|
||||
@@ -40,15 +42,7 @@ class MessageController extends Controller
|
||||
$afterId = $request->integer('after_id');
|
||||
|
||||
if ($afterId) {
|
||||
$messages = Message::withTrashed()
|
||||
->where('conversation_id', $conversationId)
|
||||
->with(['sender:id,username', 'reactions', 'attachments'])
|
||||
->where('id', '>', $afterId)
|
||||
->orderBy('id')
|
||||
->limit(100)
|
||||
->get()
|
||||
->map(fn (Message $message) => $this->payloadFactory->message($message, (int) $request->user()->id))
|
||||
->values();
|
||||
$messages = $this->conversationDelta->messagesAfter($conversation, $request->user(), $afterId);
|
||||
|
||||
return response()->json([
|
||||
'data' => $messages,
|
||||
@@ -77,6 +71,18 @@ class MessageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function delta(Request $request, int $conversationId): JsonResponse
|
||||
{
|
||||
$conversation = $this->findConversationOrFail($conversationId);
|
||||
$afterMessageId = max(0, (int) $request->integer('after_message_id'));
|
||||
|
||||
abort_if($afterMessageId < 1, 422, 'after_message_id is required.');
|
||||
|
||||
return response()->json([
|
||||
'data' => $this->conversationDelta->messagesAfter($conversation, $request->user(), $afterMessageId),
|
||||
]);
|
||||
}
|
||||
|
||||
// ── POST /api/messages/{conversation_id} ─────────────────────────────────
|
||||
|
||||
public function store(StoreMessageRequest $request, int $conversationId): JsonResponse
|
||||
|
||||
33
app/Http/Controllers/Api/Messaging/PresenceController.php
Normal file
33
app/Http/Controllers/Api/Messaging/PresenceController.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Messaging;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Conversation;
|
||||
use App\Services\Messaging\MessagingPresenceService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PresenceController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MessagingPresenceService $presence,
|
||||
) {}
|
||||
|
||||
public function heartbeat(Request $request): JsonResponse
|
||||
{
|
||||
$conversationId = $request->integer('conversation_id') ?: null;
|
||||
|
||||
if ($conversationId) {
|
||||
$conversation = Conversation::query()->findOrFail($conversationId);
|
||||
$this->authorize('view', $conversation);
|
||||
}
|
||||
|
||||
$this->presence->touch($request->user(), $conversationId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'conversation_id' => $conversationId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,14 @@ final class ProfileApiController extends Controller
|
||||
$isOwner = Auth::check() && Auth::id() === $user->id;
|
||||
$sort = $request->input('sort', 'latest');
|
||||
|
||||
$query = Artwork::with('user:id,name,username')
|
||||
$query = Artwork::with([
|
||||
'user:id,name,username,level,rank',
|
||||
'stats:artwork_id,views,downloads,favorites',
|
||||
'categories' => function ($query) {
|
||||
$query->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
|
||||
->with(['contentType:id,slug,name']);
|
||||
},
|
||||
])
|
||||
->where('user_id', $user->id)
|
||||
->whereNull('deleted_at');
|
||||
|
||||
@@ -106,7 +113,14 @@ final class ProfileApiController extends Controller
|
||||
return response()->json(['data' => [], 'next_cursor' => null, 'has_more' => false]);
|
||||
}
|
||||
|
||||
$indexed = Artwork::with('user:id,name,username')
|
||||
$indexed = Artwork::with([
|
||||
'user:id,name,username,level,rank',
|
||||
'stats:artwork_id,views,downloads,favorites',
|
||||
'categories' => function ($query) {
|
||||
$query->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
|
||||
->with(['contentType:id,slug,name']);
|
||||
},
|
||||
])
|
||||
->whereIn('id', $favIds)
|
||||
->get()
|
||||
->keyBy('id');
|
||||
@@ -173,6 +187,9 @@ final class ProfileApiController extends Controller
|
||||
private function mapArtworkCardPayload(Artwork $art): array
|
||||
{
|
||||
$present = ThumbnailPresenter::present($art, 'md');
|
||||
$category = $art->categories->first();
|
||||
$contentType = $category?->contentType;
|
||||
$stats = $art->stats;
|
||||
|
||||
return [
|
||||
'id' => $art->id,
|
||||
@@ -183,6 +200,13 @@ final class ProfileApiController extends Controller
|
||||
'height' => $art->height,
|
||||
'username' => $art->user->username ?? null,
|
||||
'uname' => $art->user->username ?? $art->user->name ?? 'Skinbase',
|
||||
'content_type' => $contentType?->name,
|
||||
'content_type_slug' => $contentType?->slug,
|
||||
'category' => $category?->name,
|
||||
'category_slug' => $category?->slug,
|
||||
'views' => (int) ($stats?->views ?? $art->view_count ?? 0),
|
||||
'downloads' => (int) ($stats?->downloads ?? 0),
|
||||
'likes' => (int) ($stats?->favorites ?? $art->favourite_count ?? 0),
|
||||
'published_at' => $this->formatIsoDate($art->published_at),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -49,6 +49,18 @@ use Inertia\Inertia;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
private const PROFILE_TABS = [
|
||||
'posts',
|
||||
'artworks',
|
||||
'stories',
|
||||
'achievements',
|
||||
'collections',
|
||||
'about',
|
||||
'stats',
|
||||
'favourites',
|
||||
'activity',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly ArtworkService $artworkService,
|
||||
private readonly UsernameApprovalService $usernameApprovalService,
|
||||
@@ -84,7 +96,12 @@ class ProfileController extends Controller
|
||||
return redirect()->route('profile.show', ['username' => strtolower((string) $user->username)], 301);
|
||||
}
|
||||
|
||||
return $this->renderProfilePage($request, $user);
|
||||
$tab = $this->normalizeProfileTab($request->query('tab'));
|
||||
if ($tab !== null) {
|
||||
return $this->redirectToProfileTab($request, (string) $user->username, $tab);
|
||||
}
|
||||
|
||||
return $this->renderProfilePage($request, $user, 'Profile/ProfileShow', false, 'posts');
|
||||
}
|
||||
|
||||
public function showGalleryByUsername(Request $request, string $username)
|
||||
@@ -111,6 +128,45 @@ class ProfileController extends Controller
|
||||
return $this->renderProfilePage($request, $user, 'Profile/ProfileGallery', true);
|
||||
}
|
||||
|
||||
public function showTabByUsername(Request $request, string $username, string $tab)
|
||||
{
|
||||
$normalized = UsernamePolicy::normalize($username);
|
||||
$user = User::query()->whereRaw('LOWER(username) = ?', [$normalized])->first();
|
||||
$normalizedTab = $this->normalizeProfileTab($tab);
|
||||
|
||||
if ($normalizedTab === null) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
$redirect = DB::table('username_redirects')
|
||||
->whereRaw('LOWER(old_username) = ?', [$normalized])
|
||||
->value('new_username');
|
||||
|
||||
if ($redirect) {
|
||||
return redirect()->route('profile.tab', [
|
||||
'username' => strtolower((string) $redirect),
|
||||
'tab' => $normalizedTab,
|
||||
], 301);
|
||||
}
|
||||
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if ($username !== strtolower((string) $user->username)) {
|
||||
return redirect()->route('profile.tab', [
|
||||
'username' => strtolower((string) $user->username),
|
||||
'tab' => $normalizedTab,
|
||||
], 301);
|
||||
}
|
||||
|
||||
if ($request->query->has('tab')) {
|
||||
return $this->redirectToProfileTab($request, (string) $user->username, $normalizedTab);
|
||||
}
|
||||
|
||||
return $this->renderProfilePage($request, $user, 'Profile/ProfileShow', false, $normalizedTab);
|
||||
}
|
||||
|
||||
public function legacyById(Request $request, int $id, ?string $username = null)
|
||||
{
|
||||
$user = User::query()->findOrFail($id);
|
||||
@@ -836,7 +892,13 @@ class ProfileController extends Controller
|
||||
return Redirect::route('dashboard.profile')->with('status', 'password-updated');
|
||||
}
|
||||
|
||||
private function renderProfilePage(Request $request, User $user, string $component = 'Profile/ProfileShow', bool $galleryOnly = false)
|
||||
private function renderProfilePage(
|
||||
Request $request,
|
||||
User $user,
|
||||
string $component = 'Profile/ProfileShow',
|
||||
bool $galleryOnly = false,
|
||||
?string $initialTab = null,
|
||||
)
|
||||
{
|
||||
$isOwner = Auth::check() && Auth::id() === $user->id;
|
||||
$viewer = Auth::user();
|
||||
@@ -1088,8 +1150,19 @@ class ProfileController extends Controller
|
||||
$usernameSlug = strtolower((string) ($user->username ?? ''));
|
||||
$canonical = url('/@' . $usernameSlug);
|
||||
$galleryUrl = url('/@' . $usernameSlug . '/gallery');
|
||||
$profileTabUrls = collect(self::PROFILE_TABS)
|
||||
->mapWithKeys(fn (string $tab) => [$tab => url('/@' . $usernameSlug . '/' . $tab)])
|
||||
->all();
|
||||
$achievementSummary = $this->achievements->summary((int) $user->id);
|
||||
$leaderboardRank = $this->leaderboards->creatorRankSummary((int) $user->id);
|
||||
$resolvedInitialTab = $this->normalizeProfileTab($initialTab);
|
||||
$isTabLanding = ! $galleryOnly && $resolvedInitialTab !== null;
|
||||
$activeProfileUrl = $resolvedInitialTab !== null
|
||||
? ($profileTabUrls[$resolvedInitialTab] ?? $canonical)
|
||||
: $canonical;
|
||||
$tabMetaLabel = $resolvedInitialTab !== null
|
||||
? ucfirst($resolvedInitialTab)
|
||||
: null;
|
||||
|
||||
return Inertia::render($component, [
|
||||
'user' => [
|
||||
@@ -1133,20 +1206,51 @@ class ProfileController extends Controller
|
||||
'countryName' => $countryName,
|
||||
'isOwner' => $isOwner,
|
||||
'auth' => $authData,
|
||||
'initialTab' => $resolvedInitialTab,
|
||||
'profileUrl' => $canonical,
|
||||
'galleryUrl' => $galleryUrl,
|
||||
'profileTabUrls' => $profileTabUrls,
|
||||
])->withViewData([
|
||||
'page_title' => $galleryOnly
|
||||
? (($user->username ?? $user->name ?? 'User') . ' Gallery on Skinbase')
|
||||
: (($user->username ?? $user->name ?? 'User') . ' on Skinbase'),
|
||||
'page_canonical' => $galleryOnly ? $galleryUrl : $canonical,
|
||||
: ($isTabLanding
|
||||
? (($user->username ?? $user->name ?? 'User') . ' ' . $tabMetaLabel . ' on Skinbase')
|
||||
: (($user->username ?? $user->name ?? 'User') . ' on Skinbase')),
|
||||
'page_canonical' => $galleryOnly ? $galleryUrl : $activeProfileUrl,
|
||||
'page_meta_description' => $galleryOnly
|
||||
? ('Browse the public gallery of ' . ($user->username ?? $user->name) . ' on Skinbase.')
|
||||
: ('View the profile of ' . ($user->username ?? $user->name) . ' on Skinbase.org — artworks, favourites and more.'),
|
||||
: ($isTabLanding
|
||||
? ('Explore the ' . strtolower((string) $tabMetaLabel) . ' section for ' . ($user->username ?? $user->name) . ' on Skinbase.')
|
||||
: ('View the profile of ' . ($user->username ?? $user->name) . ' on Skinbase.org — artworks, favourites and more.')),
|
||||
'og_image' => $avatarUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeProfileTab(mixed $tab): ?string
|
||||
{
|
||||
if (! is_string($tab)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalized = strtolower(trim($tab));
|
||||
|
||||
return in_array($normalized, self::PROFILE_TABS, true) ? $normalized : null;
|
||||
}
|
||||
|
||||
private function redirectToProfileTab(Request $request, string $username, string $tab): RedirectResponse
|
||||
{
|
||||
$baseUrl = url('/@' . strtolower($username) . '/' . $tab);
|
||||
|
||||
$query = $request->query();
|
||||
unset($query['tab']);
|
||||
|
||||
if ($query !== []) {
|
||||
$baseUrl .= '?' . http_build_query($query);
|
||||
}
|
||||
|
||||
return redirect()->to($baseUrl, 301);
|
||||
}
|
||||
|
||||
private function resolveFavouriteTable(): ?string
|
||||
{
|
||||
foreach (['artwork_favourites', 'user_favorites', 'artworks_favourites', 'favourites'] as $table) {
|
||||
@@ -1164,6 +1268,9 @@ class ProfileController extends Controller
|
||||
private function mapArtworkCardPayload(Artwork $art): array
|
||||
{
|
||||
$present = ThumbnailPresenter::present($art, 'md');
|
||||
$category = $art->categories->first();
|
||||
$contentType = $category?->contentType;
|
||||
$stats = $art->stats;
|
||||
|
||||
return [
|
||||
'id' => $art->id,
|
||||
@@ -1178,6 +1285,13 @@ class ProfileController extends Controller
|
||||
'user_id' => $art->user_id,
|
||||
'author_level' => (int) ($art->user?->level ?? 1),
|
||||
'author_rank' => (string) ($art->user?->rank ?? 'Newbie'),
|
||||
'content_type' => $contentType?->name,
|
||||
'content_type_slug' => $contentType?->slug,
|
||||
'category' => $category?->name,
|
||||
'category_slug' => $category?->slug,
|
||||
'views' => (int) ($stats?->views ?? $art->view_count ?? 0),
|
||||
'downloads' => (int) ($stats?->downloads ?? 0),
|
||||
'likes' => (int) ($stats?->favorites ?? $art->favourite_count ?? 0),
|
||||
'width' => $art->width,
|
||||
'height' => $art->height,
|
||||
];
|
||||
|
||||
@@ -8,8 +8,11 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\ArtworkResource;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkComment;
|
||||
use App\Services\ContentSanitizer;
|
||||
use App\Services\ThumbnailPresenter;
|
||||
use App\Services\ErrorSuggestionService;
|
||||
use App\Support\AvatarUrl;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
@@ -167,23 +170,38 @@ final class ArtworkPageController extends Controller
|
||||
|
||||
// Recursive helper to format a comment and its nested replies
|
||||
$formatComment = null;
|
||||
$formatComment = function(ArtworkComment $c) use (&$formatComment) {
|
||||
$formatComment = function (ArtworkComment $c) use (&$formatComment): array {
|
||||
$replies = $c->relationLoaded('approvedReplies') ? $c->approvedReplies : 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' => $c->rendered_content,
|
||||
'created_at' => $c->created_at?->toIsoString(),
|
||||
'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' => $c->user?->id,
|
||||
'name' => $c->user?->name,
|
||||
'username' => $c->user?->username,
|
||||
'display' => $c->user?->username ?? $c->user?->name ?? 'User',
|
||||
'profile_url' => $c->user?->username ? '/@' . $c->user->username : null,
|
||||
'avatar_url' => $c->user?->profile?->avatar_url,
|
||||
'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' => AvatarUrl::forUser($userId, $avatarHash, 64),
|
||||
'level' => (int) ($user?->level ?? 1),
|
||||
'rank' => (string) ($user?->rank ?? 'Newbie'),
|
||||
],
|
||||
'replies' => $replies->map($formatComment)->values()->all(),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user