Skip to content
Merged
18 changes: 17 additions & 1 deletion app/Events/ChatMessageDeleted.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ class ChatMessageDeleted implements ShouldBroadcastNow

public int $messageId;

public string $deletedAt;

/**
* Create a new event instance.
*/
public function __construct(int $messageId)
public function __construct(int $messageId, ?string $deletedAt = null)
{
$this->messageId = $messageId;
$this->deletedAt = $deletedAt ?? now()->toIso8601String();
}

/**
Expand All @@ -38,4 +41,17 @@ public function broadcastAs(): string
{
return 'message.deleted';
}

/**
* Data to broadcast with the event.
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return [
'messageId' => $this->messageId,
'deleted_at' => $this->deletedAt,
];
}
}
57 changes: 57 additions & 0 deletions app/Events/ChatMessageReacted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ChatMessageReacted implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public int $messageId;

public array $reactions;

/**
* Create a new event instance.
*/
public function __construct(int $messageId, array $reactions)
{
$this->messageId = $messageId;
$this->reactions = $reactions;
}

/**
* Get the channels the event should broadcast on.
*
* @return array<int, Channel>
*/
public function broadcastOn(): array
{
return [
new Channel('global-chat'),
];
}

public function broadcastAs(): string
{
return 'message.reacted';
}

/**
* Data to broadcast with the event.
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return [
'messageId' => $this->messageId,
'reactions' => $this->reactions,
];
}
}
5 changes: 4 additions & 1 deletion app/Events/ChatMessageSent.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ public function __construct(ChatMessage $chatMessage)

$this->message = [
'id' => $chatMessage->id,
'content' => $chatMessage->content,
'content' => $chatMessage->deleted_at ? 'This message was deleted by a moderator.' : $chatMessage->content,
'is_deleted' => $chatMessage->deleted_at !== null,
'deleted_at' => $chatMessage->deleted_at?->toIso8601String(),
'reply_to_id' => $chatMessage->reply_to_id,
'reply_to_content' => $chatMessage->reply_to_content,
'reactions' => $chatMessage->getFormattedReactions(null),
'created_at' => $chatMessage->created_at->toIso8601String(),
'user' => [
'id' => $chatMessage->user->id,
Expand Down
1 change: 1 addition & 0 deletions app/Events/ChatSettingsUpdated.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function __construct()
'cooldown_seconds' => (int) AppSetting::get('global_chat_cooldown_seconds', 30),
'max_messages' => (int) AppSetting::get('global_chat_max_messages', 200),
'max_length' => (int) AppSetting::get('global_chat_max_length', 280),
'allowed_emojis' => (array) AppSetting::get('global_chat_allowed_emojis', ['👍', '❤️', '🔥', '😂', '🎉', '😮', '😢', '👏']),
];
}

Expand Down
47 changes: 46 additions & 1 deletion app/Http/Controllers/Admin/ChatSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace App\Http\Controllers\Admin;

use App\Events\ChatSettingsUpdated;
use App\Http\Controllers\Controller;
use App\Models\AppSetting;
use App\Models\ChatMessage;
use App\Models\ChatReport;
use App\Models\User;
use Illuminate\Http\Request;
use Inertia\Inertia;

Expand All @@ -15,6 +17,7 @@ public function edit()
{
$bannedWords = AppSetting::get('global_chat_banned_words', '');
$bannedWordsText = is_array($bannedWords) ? implode(', ', $bannedWords) : (string) $bannedWords;
$allowedEmojis = AppSetting::get('global_chat_allowed_emojis', ['👍', '❤️', '🔥', '😂', '🎉', '😮', '😢', '👏']);

return Inertia::render('admin/ChatSettings', [
'settings' => [
Expand All @@ -26,6 +29,8 @@ public function edit()
'max_length' => (int) AppSetting::get('global_chat_max_length', 280),
'profanity_filter_enabled' => (bool) AppSetting::get('global_chat_profanity_filter_enabled', true),
'banned_words' => $bannedWordsText,
'allowed_emojis' => (array) $allowedEmojis,
'bot_username' => (string) AppSetting::get('global_chat_bot_username', 'hscstack'),
],
'totalMessages' => ChatMessage::count(),
'recentMessagesCount' => ChatMessage::where('created_at', '>=', now()->subHours(24))->count(),
Expand Down Expand Up @@ -83,6 +88,9 @@ public function update(Request $request)
'max_length' => ['required', 'integer', 'min:50', 'max:1000'],
'profanity_filter_enabled' => ['required', 'boolean'],
'banned_words' => ['nullable', 'string'],
'allowed_emojis' => ['nullable', 'array'],
'allowed_emojis.*' => ['required', 'string', 'max:32'],
'bot_username' => ['nullable', 'string', 'max:50', 'exists:users,username'],
]);

$isEnabled = $validated['audience'] !== 'disabled';
Expand All @@ -95,12 +103,21 @@ public function update(Request $request)
AppSetting::set('global_chat_profanity_filter_enabled', $validated['profanity_filter_enabled'], 'boolean');
AppSetting::set('global_chat_banned_words', $validated['banned_words'] ?? '', 'string');

$emojis = ! empty($validated['allowed_emojis'])
? array_values(array_unique(array_filter($validated['allowed_emojis'])))
: ['👍', '❤️', '🔥', '😂', '🎉', '😮', '😢', '👏'];
AppSetting::set('global_chat_allowed_emojis', $emojis, 'json');

if (isset($validated['bot_username']) && ! empty($validated['bot_username'])) {
AppSetting::set('global_chat_bot_username', $validated['bot_username'], 'string');
}

// Immediately prune if current count exceeds new limit
ChatMessage::pruneOldMessages($validated['max_messages']);

// Broadcast settings update to global-chat channel
try {
broadcast(new \App\Events\ChatSettingsUpdated());
broadcast(new ChatSettingsUpdated);
} catch (\Throwable $e) {
// Log without failing response
}
Expand Down Expand Up @@ -132,4 +149,32 @@ public function deleteReport(ChatReport $report)

return back()->with('success', 'Report deleted successfully.');
}

public function updateUserBan(Request $request, User $user)
{
if ($user->can('view admin')) {
return back()->with('error', 'Staff and Admin accounts cannot be banned from chat.');
}

$validated = $request->validate([
'chat_banned_until' => ['nullable', 'date'],
]);

$user->update([
'chat_banned_until' => $validated['chat_banned_until'],
]);

$moderator = $request->user();

if ($user->isChatBanned()) {
$durationText = $user->chat_banned_until->diffForHumans();
ChatMessage::sendBotMessage("Moderator @{$moderator->username} banned @{$user->username} from chat until {$user->chat_banned_until->toDayDateTimeString()} ({$durationText}).");
$message = "User @{$user->username} has been banned from chat until {$user->chat_banned_until->toDateTimeString()}.";
} else {
ChatMessage::sendBotMessage("Moderator @{$moderator->username} unbanned @{$user->username} from chat.");
$message = "User @{$user->username} has been unbanned from chat.";
}

return back()->with('success', $message);
}
}
35 changes: 23 additions & 12 deletions app/Http/Controllers/Admin/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,20 @@ public function store(StoreUserRequest $request)
}

$user = User::create($validated);
$role = $validated['role'];
$user->syncRoles([$role]);
$role = $validated['role'] ?? null;

if ($role === 'editor') {
$user->syncPermissions(
$validated['permissions'] ?? []
);
if (! empty($role)) {
$user->syncRoles([$role]);

if ($role === 'editor') {
$user->syncPermissions(
$validated['permissions'] ?? []
);
} else {
$user->syncPermissions([]);
}
} else {
$user->syncRoles([]);
$user->syncPermissions([]);
}

Expand Down Expand Up @@ -86,16 +92,21 @@ public function update(UpdateUserRequest $request, User $user)

$user->update($validated);

if (isset($validated['role'])) {
if (array_key_exists('role', $validated)) {
$role = $validated['role'];

$user->syncRoles([$role]);
if (! empty($role)) {
$user->syncRoles([$role]);

if ($role === 'editor') {
$user->syncPermissions(
$validated['permissions'] ?? []
);
if ($role === 'editor') {
$user->syncPermissions(
$validated['permissions'] ?? []
);
} else {
$user->syncPermissions([]);
}
} else {
$user->syncRoles([]);
$user->syncPermissions([]);
}
}
Expand Down
Loading