# 14 — Chat Messaging System

> **Status**: Implemented (core + welcome chat)  
> **Date**: March 9, 2026  
> **Priority**: High  
> **Complexity**: Medium-High

---

## Overview

User-to-user direct messaging system with real-time polling, multi-channel notifications (web bell, push, email), message search, read receipts, blocking, and a modern WhatsApp-style UI.

**New (March 9, 2026)**: Automated welcome chat messages sent to new users upon registration.

---

## What Was Built

### Core Features

1. **1:1 Conversations** — Any authenticated user can start a conversation with any other user
2. **Inbox View** — List of all conversations sorted by most recent, with unread badges, last message preview, and search
3. **Conversation View** — Full chat interface with message history, auto-scroll, load older messages, and Enter-to-send
4. **Real-Time Polling** — Messages poll every 3 seconds in active conversations; header badge polls every 30 seconds
5. **Read Receipts** — Double checkmark (✓✓) shown when the recipient opens and reads messages
6. **Message Deletion** — Sender can soft-delete their own messages (shows "message was deleted")
7. **Block/Unblock** — Either participant can block the conversation; blocked conversations prevent sending
8. **Conversation Deletion** — Soft delete per-user (the other person still sees their copy)
9. **Message Search** — Full-text search across all user's conversations from the inbox
10. **Header Badge** — Unread message count badge next to a chat icon in the main navbar (desktop + mobile)

### Welcome Chat (March 9, 2026)

Automated personalized welcome message sent from the CEO to every new user upon registration.

| Feature | Details |
|---------|---------|
| **Sender** | Configurable username (default: `shadi`) |
| **Language** | Detects user's locale; sends Arabic or English message |
| **Personalization** | `{{firstName}}` placeholder replaced with user's first name |
| **Notifications** | Uses full chat pipeline (web bell, push, email) |
| **Admin Control** | Enable/disable toggle + editable templates in admin panel |
| **Storage** | Messages stored in `app_setting` table (key-value) |

**Admin Panel**: Dashboard → "💬 Welcome Chat" tab at `/jim19ud83/playground/dashboard`

### Notification Channels (Async via Messenger)

| Channel | Trigger | Details |
|---------|---------|---------|
| **Web Bell** | Every message | Adds to the notification dropdown with sender name + preview |
| **Firebase Push** | Every message | Push notification to all registered devices with RTL support |
| **Email** | First unread message in a conversation | Sent via Listmonk TX (`template_id=7`) from `info@shamra-academia.com`; no message body content in email |

Users can disable message notifications via `messageNotificationEnabled` preference.

---

## Architecture

### New Files

| File | Purpose |
|------|---------|
| `src/Entity/Conversation.php` | Conversation entity (participants, block/delete flags) |
| `src/Entity/ChatMessage.php` | Message entity (body, read receipts, soft delete) |
| `src/Repository/ConversationRepository.php` | Inbox queries, unread counts |
| `src/Repository/ChatMessageRepository.php` | Message retrieval, pagination, search |
| `src/Service/ChatService.php` | Business logic layer |
| `src/Controller/ChatController.php` | 14 routes (pages + API) |
| `src/Message/SendChatNotification.php` | Async message class |
| `src/MessageHandler/SendChatNotificationHandler.php` | Handles web, push, email notifications |
| `templates/chat/inbox.html.twig` | Inbox page |
| `templates/chat/conversation.html.twig` | Chat page |
| `templates/emails/new_chat_message.html.twig` | Email notification |
| `translations/Chat.ar.yml` | Arabic translations |
| `translations/Chat.en.yml` | English translations |
| `migrations/Version20260307100000.php` | DB migration |
| `migrations/Version20260309200000.php` | Welcome chat settings migration |

### Modified Files

| File | Change |
|------|--------|
| `src/Entity/User.php` | Added `messageNotificationEnabled` field + getter/setter |
| `templates/header.html.twig` | Added chat icon with badge + polling JS + mobile sidebar link |
| `config/packages/messenger.yaml` | Routed `SendChatNotification` to async transport |
| `templates/Profile/show/personal_info.html.twig` | Added "Send Message" button in profile actions |
| `translations/Social.ar.yml` | Added `send_message` translation key |
| `translations/Social.en.yml` | Added `send_message` translation key |
| `src/Service/ChatService.php` | Added `sendWelcomeMessage()`, `getWelcomeSettings()`, `updateWelcomeSetting()` methods |
| `src/Controller/RegistrationController.php` | Calls `sendWelcomeMessage()` after successful registration |
| `src/Controller/PlaygroundAdminController.php` | Added GET/POST `/api/welcome-chat-settings` endpoints |
| `templates/admin/playground/dashboard.html.twig` | Added "💬 Welcome Chat" tab with enable toggle, sender input, AR/EN textareas |

### Database Tables

**`conversation`** — utf8mb4_unicode_ci
| Column | Type | Notes |
|--------|------|-------|
| id | INT PK | Auto-increment |
| participant_one_id | INT FK | → fos_user, CASCADE |
| participant_two_id | INT FK | → fos_user, CASCADE |
| last_message_at | DATETIME | For sorting inbox |
| created_at | DATETIME | |
| deleted_by_one/two | TINYINT(1) | Per-user soft delete |
| blocked_by_one/two | TINYINT(1) | Per-user block |

**`chat_message`** — utf8mb4_unicode_ci
| Column | Type | Notes |
|--------|------|-------|
| id | INT PK | Auto-increment |
| conversation_id | INT FK | → conversation, CASCADE |
| sender_id | INT FK | → fos_user, CASCADE |
| body | LONGTEXT | Message content |
| created_at | DATETIME | |
| read_at | DATETIME NULL | NULL = unread |
| is_deleted | TINYINT(1) | Soft delete |
| message_type | VARCHAR(20) | Default 'text', extensible |
| attachment_url | VARCHAR(500) NULL | For future file uploads |

### API Routes

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/messages` | Inbox page |
| GET | `/messages/{id}` | Conversation page |
| GET | `/messages/new/{username}` | Start conversation (redirect) |
| GET | `/api/chat/inbox` | Get conversations list |
| GET | `/api/chat/{id}/messages` | Get/poll messages |
| POST | `/api/chat/{id}/send` | Send a message |
| POST | `/api/chat/start` | Start conversation (API) |
| GET | `/api/chat/unread-count` | Unread badge count |
| POST | `/api/chat/{id}/read` | Mark conversation read |
| POST | `/api/chat/{id}/delete` | Delete conversation |
| POST | `/api/chat/{id}/block` | Toggle block |
| POST | `/api/chat/message/{id}/delete` | Delete single message |
| GET | `/api/chat/search` | Search messages |
| GET | `/api/chat/can-message/{username}` | Check if can message |
| GET | `/jim19ud83/playground/api/welcome-chat-settings` | Get welcome chat settings (admin) |
| POST | `/jim19ud83/playground/api/welcome-chat-settings` | Save welcome chat settings (admin) |

---

## Deployment Steps

1. Push to `main` (triggers deploy action)
2. Migration runs automatically: `php bin/console doctrine:migrations:migrate`
3. Messenger worker picks up `SendChatNotification` messages automatically (same worker)

---

## Bugs Fixed

| Date | Issue | Fix |
|------|-------|-----|
| March 7, 2026 | `Constant "App\Entity\User::IMAGE_UPLOAD_PATH" is undefined` error on `/messages/{id}` | Replaced `constant('App\\Entity\\User::IMAGE_UPLOAD_PATH')` in `conversation.html.twig` with hardcoded `'uploads/profile/img/'` path (matching all other templates). Added proper fallback for `default_profile_pic` images. |
| March 7, 2026 | Push notifications failed with `Call to undefined method PushNotificationDevice::getToken()` | Replaced `getToken()` with `getDeviceId()` in `SendChatNotificationHandler`. |
| March 7, 2026 | Inbox flooding from one email per chat message | Added first-unread throttling: email sends only once per unread streak in a conversation. |
| March 7, 2026 | Privacy issue: email included chat message body | Removed message body preview from chat notification emails. |
| March 7, 2026 | Mail transport inconsistency for chat notifications | Switched chat email sending to `ListmonkService::sendTransactional()` (`template_id=7`) with sender `info@shamra-academia.com`. |
| March 7, 2026 | Rare sender name mismatch in chat notifications | Added sender derivation safety: notification sender is resolved from conversation participants relative to recipient. |
| March 31, 2026 | Chat search returns `SyntaxError: Unexpected token '<'` — all fetch calls used hardcoded `/api/chat/...` paths without locale prefix, so English-locale requests hit non-matching routes and got HTML back | Replaced all hardcoded API paths in `inbox.html.twig`, `conversation.html.twig`, and `header.html.twig` with Twig-generated `chatApiBase` variable derived from `{{ path('api_chat_inbox') }}`, ensuring correct locale prefix for all fetch calls. |

---

## Nice Ideas for Future Enhancement

### Phase 2 — Quick Wins

1. ~~**"Send Message" button on user profiles**~~ ✅ **Done** (March 7, 2026) — Green outlined pill button (`btn-profile-message`) next to the Follow button on every profile page. Links to `/messages/new/{username}`. Translation key `social.send_message` in `Social.ar.yml` / `Social.en.yml`. Only shows for logged-in users viewing another user's profile.

2. **Reply from notification dropdown** — Show a mini reply input directly in the notification bell dropdown when a message notification is clicked, without navigating away.

3. ~~**Online presence indicator**~~ ✅ **Done** (March 24, 2026) — Added `last_active_at` column to `fos_user` with `UserActivitySubscriber` (kernel listener, throttled to 2min). `User::isOnlineNow()` returns true if active within 5 minutes. Green dots shown on feed sidebar (suggested researchers + "Active Now" card). `GET /api/active-researchers` returns online users. **Chat integration pending**: show green dot + "Active now" / "Active 2h ago" in chat topbar and inbox list.

4. **Typing indicator** — When the user starts typing, send a lightweight `POST /api/chat/{id}/typing` beacon; poll for typing status alongside message polling. Show "typing..." with animated dots.

5. **Notification quiet hours** — Let users set "Do Not Disturb" hours (e.g., 11pm-7am) in settings. Skip push + email during those hours; web notifications still accumulate silently.

### Phase 3 — Rich Messaging

6. **Image & file sharing** — Allow drag-and-drop or paste images in the chat input. Upload to `/uploads/chat/` and store URL in `attachment_url`. Show image thumbnails inline with a lightbox.

7. **Voice messages** — Record short audio clips using the MediaRecorder API. Store as `.webm` in uploads. Show a playable waveform in the chat bubble.

8. **Message reactions** — Quick emoji reactions (👍 ❤️ 😂 😮 😢) on any message. Lightweight overlay on hover/long-press. Store in a `chat_message_reaction` join table.

9. **Link previews** — When a message contains a URL, generate an Open Graph preview card (title, description, image) server-side on send. Cache in `attachment_url` as JSON.

### Phase 4 — Discovery & Growth

10. **"Message the Author" on research papers** — Add a "Contact Researcher" button on paper detail pages for papers where the author is a registered user. Drives engagement and new conversations.

11. **Community chat channels** — Extend the model to support multi-user conversations tied to Communities. New `ConversationParticipant` join table + `is_group` flag on Conversation.

12. **Chat from search results** — When a user finds a researcher via search, show a small "Message" icon next to their name in results.

13. **Smart email digest** — Instead of emailing on every message, batch unread messages into a single email after 30 minutes of inactivity. Reduces email fatigue while keeping users informed. Requires a scheduled command or delayed dispatch.

14. **Chat analytics dashboard** — Admin tab showing total messages/day, active conversations, average response time, most active chatters. Useful for platform health monitoring.

15. **Export conversation** — Allow users to download a conversation as PDF/TXT for academic reference or record-keeping.

---

## Settings Integration

Add to the user settings page (`/settings`):

```
☑ Message notifications (email + push when someone sends you a message)
```

Maps to `User::$messageNotificationEnabled`. Same pattern as `followNotificationEnabled`.

---

## Decisions Log

| Decision | Choice | Rationale |
|---|---|---|
| Chat model | **1:1 Conversation entity** with per-user block/delete flags | Clean two-participant model; avoids join table complexity; supports independent soft delete + block per side |
| Chat real-time | **Polling (3s in conversation, 30s for badge)** | No WebSocket/Mercure needed at current scale. Revisit if concurrent chatters > 500 |
| Chat notifications | **Async via Symfony Messenger** | Same Doctrine transport + worker as OCR/reference processing. Web + push + email in one handler |
| Chat message types | **Extensible `message_type` + `attachment_url` columns** | Default 'text'; prepared for image/file/voice without migration |
| Chat email | **First-unread throttling** | Email sends only once per unread streak. Future: smart digest (batch after 30min inactivity) |
| Online presence | **`lastActiveAt` on User + kernel subscriber** | Throttled to 2min writes. `isOnlineNow()` = 5-minute window. Stored in DB (not Redis) for simplicity at current scale |
