106 lines
3.8 KiB
PHP
106 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Collection;
|
|
use App\Models\CollectionComment;
|
|
use App\Models\User;
|
|
use App\Support\AvatarUrl;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class CollectionCommentService
|
|
{
|
|
public function __construct(
|
|
private readonly NotificationService $notifications,
|
|
) {
|
|
}
|
|
|
|
public function create(Collection $collection, User $actor, string $body, ?CollectionComment $parent = null): CollectionComment
|
|
{
|
|
if (! $collection->canReceiveCommentsFrom($actor)) {
|
|
throw ValidationException::withMessages([
|
|
'collection' => 'Comments are disabled for this collection.',
|
|
]);
|
|
}
|
|
|
|
$comment = CollectionComment::query()->create([
|
|
'collection_id' => $collection->id,
|
|
'user_id' => $actor->id,
|
|
'parent_id' => $parent?->id,
|
|
'body' => trim($body),
|
|
'rendered_body' => nl2br(e(trim($body))),
|
|
'status' => Collection::COMMENT_VISIBLE,
|
|
]);
|
|
|
|
$collection->increment('comments_count');
|
|
$collection->forceFill(['last_activity_at' => now()])->save();
|
|
|
|
if (! $collection->isOwnedBy($actor)) {
|
|
$this->notifications->notifyCollectionComment($collection->user, $actor, $collection, $comment);
|
|
}
|
|
|
|
return $comment->fresh(['user.profile', 'replies.user.profile']);
|
|
}
|
|
|
|
public function delete(CollectionComment $comment, User $actor): void
|
|
{
|
|
if ((int) $comment->user_id !== (int) $actor->id && ! $comment->collection->canBeManagedBy($actor)) {
|
|
throw ValidationException::withMessages([
|
|
'comment' => 'You are not allowed to remove this comment.',
|
|
]);
|
|
}
|
|
|
|
if ($comment->trashed()) {
|
|
return;
|
|
}
|
|
|
|
$comment->delete();
|
|
$comment->collection()->decrement('comments_count');
|
|
}
|
|
|
|
public function mapComments(Collection $collection, ?User $viewer = null): array
|
|
{
|
|
$comments = $collection->comments()
|
|
->whereNull('parent_id')
|
|
->where('status', Collection::COMMENT_VISIBLE)
|
|
->with(['user.profile', 'replies.user.profile'])
|
|
->latest()
|
|
->limit(30)
|
|
->get();
|
|
|
|
return $comments->map(fn (CollectionComment $comment) => $this->mapComment($comment, $viewer))->all();
|
|
}
|
|
|
|
private function mapComment(CollectionComment $comment, ?User $viewer = null): array
|
|
{
|
|
$user = $comment->user;
|
|
|
|
return [
|
|
'id' => (int) $comment->id,
|
|
'body' => (string) $comment->body,
|
|
'rendered_content' => (string) $comment->rendered_body,
|
|
'time_ago' => $comment->created_at?->diffForHumans(),
|
|
'created_at' => $comment->created_at?->toISOString(),
|
|
'can_delete' => $viewer !== null && ((int) $viewer->id === (int) $comment->user_id || $comment->collection->canBeManagedBy($viewer)),
|
|
'can_report' => $viewer !== null && (int) $viewer->id !== (int) $comment->user_id,
|
|
'user' => [
|
|
'id' => (int) $user->id,
|
|
'display' => (string) ($user->name ?: $user->username),
|
|
'username' => (string) $user->username,
|
|
'level' => (int) ($user->level ?? 0),
|
|
'rank' => (string) ($user->rank ?? ''),
|
|
'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 64),
|
|
'profile_url' => '/@' . Str::lower((string) $user->username),
|
|
],
|
|
'replies' => $comment->replies
|
|
->where('status', Collection::COMMENT_VISIBLE)
|
|
->map(fn (CollectionComment $reply) => $this->mapComment($reply, $viewer))
|
|
->values()
|
|
->all(),
|
|
];
|
|
}
|
|
}
|