From 0a2374629824941591a231ebbea71c4ffcbd6a44 Mon Sep 17 00:00:00 2001 From: Tajim Date: Thu, 27 Aug 2026 21:44:15 +0600 Subject: [PATCH 01/15] refactor: remove unused chatSettings from DashboardController --- app/Http/Controllers/Admin/DashboardController.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index a941f73..0d19664 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; -use App\Models\AppSetting; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; @@ -14,12 +13,7 @@ class DashboardController extends Controller { public function index() { - return Inertia::render('admin/Dashboard', [ - 'chatSettings' => [ - 'enabled' => AppSetting::get('global_chat_enabled', true), - 'audience' => AppSetting::get('global_chat_audience', 'verified_members'), - ], - ]); + return Inertia::render('admin/Dashboard'); } public function analytics(Request $request) From 3eea086b728ee5930c99d82aea156f15481b74a5 Mon Sep 17 00:00:00 2001 From: Tajim Date: Thu, 27 Aug 2026 22:09:10 +0600 Subject: [PATCH 02/15] feat(chat): add chat ban support with moderation shortcuts and timestamps --- app/Http/Controllers/ChatController.php | 12 ++ app/Http/Requests/User/UpdateUserRequest.php | 1 + app/Models/User.php | 7 + ...7_add_chat_banned_until_to_users_table.php | 28 ++++ resources/js/components/admin/UserRow.vue | 18 ++- resources/js/pages/Chat/Index.vue | 16 ++- .../js/pages/admin/users/CreateOrEdit.vue | 123 +++++++++++++++++- 7 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 database/migrations/2026_08_27_160307_add_chat_banned_until_to_users_table.php diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index 58f8829..e85262e 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -29,6 +29,9 @@ public function index(Request $request) $reason = 'Global chat is currently disabled for maintenance.'; } elseif (! $user) { $reason = 'Please sign in to join the conversation.'; + } elseif ($user->isChatBanned()) { + $bannedUntilFormatted = $user->chat_banned_until->diffForHumans(); + $reason = "You are temporarily banned from chat until {$user->chat_banned_until->toDateTimeString()} ({$bannedUntilFormatted})."; } elseif ($audience === 'verified_members') { if ($user->is_verified || $user->can('view admin')) { $canPost = true; @@ -104,6 +107,15 @@ public function store(Request $request) return response()->json(['message' => 'Global chat is currently disabled.'], 403); } + // Check if user is chat banned + if ($user->isChatBanned()) { + $bannedUntilFormatted = $user->chat_banned_until->diffForHumans(); + + return response()->json([ + 'message' => "You are banned from sending messages until {$user->chat_banned_until->toDateTimeString()} ({$bannedUntilFormatted}).", + ], 403); + } + // Check audience permission if ($audience === 'verified_members' && ! $user->is_verified && ! $user->can('view admin')) { return response()->json(['message' => 'Global chat is currently restricted to verified members.'], 403); diff --git a/app/Http/Requests/User/UpdateUserRequest.php b/app/Http/Requests/User/UpdateUserRequest.php index 84638ae..71ff13d 100644 --- a/app/Http/Requests/User/UpdateUserRequest.php +++ b/app/Http/Requests/User/UpdateUserRequest.php @@ -37,6 +37,7 @@ public function rules(): array 'role' => ['sometimes', 'string'], 'permissions' => ['sometimes', 'array'], 'permissions.*' => ['string', 'exists:permissions,name'], + 'chat_banned_until' => ['sometimes', 'nullable', 'date'], ]; return $rules; diff --git a/app/Models/User.php b/app/Models/User.php index c86e494..03f0191 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -43,6 +43,7 @@ class User extends Authenticatable 'receive_emails', 'google_id', 'email_verified_at', + 'chat_banned_until', 'image_path', 'about', 'title', @@ -74,6 +75,11 @@ public function getIsVerifiedAttribute(): bool : $this->roles()->exists(); } + public function isChatBanned(): bool + { + return $this->chat_banned_until !== null && $this->chat_banned_until->isFuture(); + } + public function getImageUrlAttribute() { if (! $this->image_path) { @@ -87,6 +93,7 @@ protected function casts(): array { return [ 'email_verified_at' => 'datetime', + 'chat_banned_until' => 'datetime', 'password' => 'hashed', 'receive_emails' => 'boolean', ]; diff --git a/database/migrations/2026_08_27_160307_add_chat_banned_until_to_users_table.php b/database/migrations/2026_08_27_160307_add_chat_banned_until_to_users_table.php new file mode 100644 index 0000000..90df143 --- /dev/null +++ b/database/migrations/2026_08_27_160307_add_chat_banned_until_to_users_table.php @@ -0,0 +1,28 @@ +timestamp('chat_banned_until')->nullable()->after('receive_emails'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('chat_banned_until'); + }); + } +}; diff --git a/resources/js/components/admin/UserRow.vue b/resources/js/components/admin/UserRow.vue index 26175ca..8ac1d8c 100644 --- a/resources/js/components/admin/UserRow.vue +++ b/resources/js/components/admin/UserRow.vue @@ -1,17 +1,22 @@ + + diff --git a/routes/admin.php b/routes/admin.php index ab9d605..813ce34 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -115,6 +115,9 @@ // Chat Management & Settings Route::middleware('permission:manage chat')->group(function () { Route::get('/chat', [ChatSettingsController::class, 'edit'])->name('chat.edit'); + Route::get('/chat/reports', [ChatSettingsController::class, 'reports'])->name('chat.reports.index'); Route::post('/chat/settings', [ChatSettingsController::class, 'update'])->name('chat.settings.update'); Route::post('/chat/clear', [ChatSettingsController::class, 'clearMessages'])->name('chat.clear'); + Route::patch('/chat/reports/{report}/status', [ChatSettingsController::class, 'updateReportStatus'])->name('chat.reports.update-status'); + Route::delete('/chat/reports/{report}', [ChatSettingsController::class, 'deleteReport'])->name('chat.reports.destroy'); }); diff --git a/routes/web.php b/routes/web.php index 81af179..eeec2f7 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,6 +38,7 @@ // Global Chat Actions Route::post('/api/chat/messages', [ChatController::class, 'store'])->name('chat.messages.store'); Route::delete('/api/chat/messages/{message}', [ChatController::class, 'destroy'])->name('chat.messages.destroy'); + Route::post('/api/chat/reports', [ChatController::class, 'report'])->name('chat.reports.store'); }); // Global Chat Messages List (Public Read) From fc5592b295cbe9063e0889d0e0ead8dddc3c2c3d Mon Sep 17 00:00:00 2001 From: Tajim Date: Thu, 27 Aug 2026 22:39:19 +0600 Subject: [PATCH 04/15] feat(chat): add message reply support with ellipsized snapshot and interactive jump --- app/Events/ChatMessageSent.php | 2 + app/Http/Controllers/ChatController.php | 19 +++ app/Models/ChatMessage.php | 2 + ...dd_reply_fields_to_chat_messages_table.php | 29 +++++ resources/js/pages/Chat/Index.vue | 108 ++++++++++++++++-- 5 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 database/migrations/2026_08_27_163500_add_reply_fields_to_chat_messages_table.php diff --git a/app/Events/ChatMessageSent.php b/app/Events/ChatMessageSent.php index ad7f18c..1d85bd6 100644 --- a/app/Events/ChatMessageSent.php +++ b/app/Events/ChatMessageSent.php @@ -25,6 +25,8 @@ public function __construct(ChatMessage $chatMessage) $this->message = [ 'id' => $chatMessage->id, 'content' => $chatMessage->content, + 'reply_to_id' => $chatMessage->reply_to_id, + 'reply_to_content' => $chatMessage->reply_to_content, 'created_at' => $chatMessage->created_at->toIso8601String(), 'user' => [ 'id' => $chatMessage->user->id, diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index cca5d22..932e0f3 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -8,6 +8,7 @@ use App\Models\ChatMessage; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; +use Illuminate\Support\Str; use Inertia\Inertia; class ChatController extends Controller @@ -52,6 +53,8 @@ public function index(Request $request) ->map(fn (ChatMessage $msg) => [ 'id' => $msg->id, 'content' => $msg->content, + 'reply_to_id' => $msg->reply_to_id, + 'reply_to_content' => $msg->reply_to_content, 'created_at' => $msg->created_at->toIso8601String(), 'user' => [ 'id' => $msg->user->id, @@ -134,13 +137,27 @@ public function store(Request $request) $validated = $request->validate([ 'content' => ['required', 'string', 'max:280'], + 'reply_to_id' => ['nullable', 'integer'], ]); $content = trim($validated['content']); + $replyToId = $validated['reply_to_id'] ?? null; + $replyToContent = null; + + if ($replyToId) { + $parentMessage = ChatMessage::find($replyToId); + if ($parentMessage) { + $replyToContent = Str::limit($parentMessage->content, 97, '...'); + } else { + $replyToId = null; + } + } $message = ChatMessage::create([ 'user_id' => $user->id, 'content' => $content, + 'reply_to_id' => $replyToId, + 'reply_to_content' => $replyToContent, ]); // Record rate limit for configured cooldown seconds @@ -163,6 +180,8 @@ public function store(Request $request) return response()->json([ 'id' => $message->id, 'content' => $message->content, + 'reply_to_id' => $message->reply_to_id, + 'reply_to_content' => $message->reply_to_content, 'created_at' => $message->created_at->toIso8601String(), 'user' => [ 'id' => $message->user->id, diff --git a/app/Models/ChatMessage.php b/app/Models/ChatMessage.php index 9f2f71f..9f87c1d 100644 --- a/app/Models/ChatMessage.php +++ b/app/Models/ChatMessage.php @@ -10,6 +10,8 @@ class ChatMessage extends Model protected $fillable = [ 'user_id', 'content', + 'reply_to_id', + 'reply_to_content', ]; public function user(): BelongsTo diff --git a/database/migrations/2026_08_27_163500_add_reply_fields_to_chat_messages_table.php b/database/migrations/2026_08_27_163500_add_reply_fields_to_chat_messages_table.php new file mode 100644 index 0000000..43a6481 --- /dev/null +++ b/database/migrations/2026_08_27_163500_add_reply_fields_to_chat_messages_table.php @@ -0,0 +1,29 @@ +unsignedBigInteger('reply_to_id')->nullable()->after('user_id'); + $table->string('reply_to_content', 100)->nullable()->after('reply_to_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chat_messages', function (Blueprint $table) { + $table->dropColumn(['reply_to_id', 'reply_to_content']); + }); + } +}; diff --git a/resources/js/pages/Chat/Index.vue b/resources/js/pages/Chat/Index.vue index bdfdaa2..b44f674 100644 --- a/resources/js/pages/Chat/Index.vue +++ b/resources/js/pages/Chat/Index.vue @@ -1,6 +1,6 @@