Files
SkinbaseNova/app/Http/Controllers/Api/Posts/PostCommentController.php
Gregor Klevze dc51d65440 feat: forum rich-text editor, emoji picker, mentions, discover nav, feed, uploads, profile
Forum:
- TipTap WYSIWYG editor with full toolbar
- @emoji-mart/react emoji picker (consistent with tweets)
- @mention autocomplete with user search API
- Fix PHP 8.4 parse errors in Blade templates
- Fix thread data display (paginator items)
- Align forum page widths to max-w-5xl

Discover:
- Extract shared _nav.blade.php partial
- Add missing nav links to for-you page
- Add Following link for authenticated users

Feed/Posts:
- Post model, controllers, policies, migrations
- Feed page components (PostComposer, FeedCard, etc)
- Post reactions, comments, saves, reports, sharing
- Scheduled publishing support
- Link preview controller

Profile:
- Profile page components (ProfileHero, ProfileTabs)
- Profile API controller

Uploads:
- Upload wizard enhancements
- Scheduled publish picker
- Studio status bar and readiness checklist
2026-03-03 09:48:31 +01:00

123 lines
5.4 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Posts;
use App\Events\Posts\PostCommented;
use App\Http\Controllers\Controller;
use App\Http\Requests\Posts\CreateCommentRequest;
use App\Models\Post;
use App\Models\PostComment;
use App\Services\ContentSanitizer;
use App\Services\Posts\PostCountersService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
class PostCommentController extends Controller
{
public function __construct(private PostCountersService $counters) {}
// ─────────────────────────────────────────────────────────────────────────
// List
// ─────────────────────────────────────────────────────────────────────────
public function index(Request $request, int $postId): JsonResponse
{
$post = Post::findOrFail($postId);
$page = max(1, (int) $request->query('page', 1));
$comments = PostComment::with(['user', 'user.profile'])
->where('post_id', $post->id)
->orderByDesc('is_highlighted') // highlighted first
->orderBy('created_at')
->paginate(20, ['*'], 'page', $page);
$formatted = $comments->getCollection()->map(fn ($c) => $this->formatComment($c));
return response()->json([
'data' => $formatted,
'meta' => [
'total' => $comments->total(),
'current_page' => $comments->currentPage(),
'last_page' => $comments->lastPage(),
'per_page' => $comments->perPage(),
],
]);
}
// ─────────────────────────────────────────────────────────────────────────
// Store
// ─────────────────────────────────────────────────────────────────────────
public function store(CreateCommentRequest $request, int $postId): JsonResponse
{
$user = $request->user();
// Rate limit: 30 comments per hour
$key = 'comment_post:' . $user->id;
if (RateLimiter::tooManyAttempts($key, 30)) {
$seconds = RateLimiter::availableIn($key);
return response()->json([
'message' => "You're commenting too quickly. Please wait {$seconds} seconds.",
], 429);
}
RateLimiter::hit($key, 3600);
$post = Post::findOrFail($postId);
$body = ContentSanitizer::render($request->input('body'));
$comment = PostComment::create([
'post_id' => $post->id,
'user_id' => $user->id,
'body' => $body,
]);
$this->counters->incrementComments($post);
// Fire event for notification
if ($post->user_id !== $user->id) {
event(new PostCommented($post, $comment, $user));
}
$comment->load(['user', 'user.profile']);
return response()->json(['comment' => $this->formatComment($comment)], 201);
}
// ─────────────────────────────────────────────────────────────────────────
// Destroy
// ─────────────────────────────────────────────────────────────────────────
public function destroy(Request $request, int $postId, int $commentId): JsonResponse
{
$comment = PostComment::where('post_id', $postId)->findOrFail($commentId);
Gate::authorize('delete', $comment);
$comment->delete();
$this->counters->decrementComments(Post::findOrFail($postId));
return response()->json(['message' => 'Comment deleted.']);
}
// ─────────────────────────────────────────────────────────────────────────
// Format
// ─────────────────────────────────────────────────────────────────────────
private function formatComment(PostComment $comment): array
{
return [
'id' => $comment->id,
'body' => $comment->body,
'is_highlighted' => (bool) $comment->is_highlighted,
'created_at' => $comment->created_at->toISOString(),
'author' => [
'id' => $comment->user->id,
'username' => $comment->user->username,
'name' => $comment->user->name,
'avatar' => $comment->user->profile?->avatar_url ?? null,
],
];
}
}