feat: Add comprehensive notification system with email support
- Create notification management system with VNotifications class - Build notification API endpoints (get, mark read, delete, preferences) - Implement notification bell UI widget with real-time polling - Add email queue system with digest support (instant/hourly/daily/weekly) - Create notification trigger helpers for common events - Add fine-grained user preference controls - Implement cron script for email processing and cleanup - Document setup and usage in docs/NOTIFICATIONS.md Features: - In-app notifications with dropdown - Email delivery with batching - 6 notification types (comment, like, subscribe, upload, mention, system) - User preference controls per notification type - Lightweight polling (30s interval) - Auto-cleanup of old notifications (90 days) - Production-ready with proper error handling
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
# Notification System
|
||||
|
||||
EasyStream now includes a comprehensive notification system with in-app and email delivery.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔔 **In-app notifications** with real-time updates
|
||||
- 📧 **Email notifications** with digest support (instant, hourly, daily, weekly)
|
||||
- 🎯 **Fine-grained preferences** - users control what they receive
|
||||
- ⚡ **Lightweight polling** - updates every 30 seconds without performance impact
|
||||
- 📱 **Modern UI** - notification bell with dropdown list
|
||||
- 🎨 **Multiple notification types** - comments, likes, subscribes, uploads, mentions, system
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Database Migration
|
||||
|
||||
Run the migration to create notification tables:
|
||||
|
||||
```bash
|
||||
docker-compose exec db mysql -u easystream -peasystream easystream < __install/migrations/002_add_notifications_system.sql
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `db_notifications` - stores all notifications
|
||||
- `db_notification_preferences` - user notification settings
|
||||
- `db_email_queue` - queued emails for batched sending
|
||||
|
||||
### 2. Add Notification Bell to Header
|
||||
|
||||
Include the JavaScript and add the widget container to your header template:
|
||||
|
||||
```html
|
||||
<!-- In your header template -->
|
||||
<script src="/f_scripts/fe/js/notifications.js"></script>
|
||||
|
||||
<!-- Add the notification bell to your navigation -->
|
||||
<div id="notification-widget"></div>
|
||||
```
|
||||
|
||||
The widget will auto-initialize and start polling for updates.
|
||||
|
||||
### 3. Configure Email Sending
|
||||
|
||||
By default, the system uses PHP's `mail()` function. For production, integrate with a proper email service:
|
||||
|
||||
Edit `f_core/f_classes/class.notifications.php` → `sendEmail()` method to integrate:
|
||||
- **SendGrid**
|
||||
- **Mailgun**
|
||||
- **Amazon SES**
|
||||
- **SMTP**
|
||||
|
||||
### 4. Set Up Cron Job
|
||||
|
||||
Add a cron job to process the email queue:
|
||||
|
||||
```bash
|
||||
# Process email queue every 15 minutes
|
||||
*/15 * * * * cd /srv/easystream && php app_scripts/cron/process_notification_emails.php >> /var/log/notification-emails.log 2>&1
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Triggering Notifications
|
||||
|
||||
Use the `VNotificationTriggers` helper class for common events:
|
||||
|
||||
```php
|
||||
require_once 'f_core/f_classes/class.notification_triggers.php';
|
||||
$triggers = new VNotificationTriggers();
|
||||
|
||||
// When someone comments on a video
|
||||
$triggers->notifyVideoComment(
|
||||
$video_owner_id,
|
||||
$commenter_id,
|
||||
$commenter_name,
|
||||
$video_id,
|
||||
$video_title,
|
||||
$comment_text
|
||||
);
|
||||
|
||||
// When someone likes a video
|
||||
$triggers->notifyVideoLike(
|
||||
$video_owner_id,
|
||||
$liker_id,
|
||||
$liker_name,
|
||||
$video_id,
|
||||
$video_title
|
||||
);
|
||||
|
||||
// When someone subscribes
|
||||
$triggers->notifySubscribe(
|
||||
$channel_owner_id,
|
||||
$subscriber_id,
|
||||
$subscriber_name
|
||||
);
|
||||
|
||||
// When uploading a new video (notifies all subscribers)
|
||||
$triggers->notifySubscribersNewVideo(
|
||||
$channel_owner_id,
|
||||
$channel_name,
|
||||
$video_id,
|
||||
$video_title
|
||||
);
|
||||
|
||||
// When mentioning a user in a comment
|
||||
$triggers->notifyMention(
|
||||
$mentioned_user_id,
|
||||
$mentioner_id,
|
||||
$mentioner_name,
|
||||
$video_id,
|
||||
$comment_text
|
||||
);
|
||||
|
||||
// System notification
|
||||
$triggers->notifySystem(
|
||||
$user_id,
|
||||
'Your video was approved',
|
||||
'Your video "Tutorial" is now live!',
|
||||
'/watch?v=123'
|
||||
);
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
#### Get Notifications
|
||||
|
||||
```http
|
||||
GET /api/notifications.php?limit=20&offset=0&unread_only=true
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"notifications": [
|
||||
{
|
||||
"notification_id": 123,
|
||||
"type": "comment",
|
||||
"title": "John commented on your video",
|
||||
"message": "Great tutorial!",
|
||||
"link": "/watch?v=456#comments",
|
||||
"is_read": 0,
|
||||
"created_at": "2026-03-30 17:30:00",
|
||||
"actor_username": "john",
|
||||
"actor_avatar": "/avatars/john.jpg"
|
||||
}
|
||||
],
|
||||
"unread_count": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Unread Count (Lightweight)
|
||||
|
||||
```http
|
||||
GET /api/notifications_count.php
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"unread_count": 5
|
||||
}
|
||||
```
|
||||
|
||||
#### Mark as Read
|
||||
|
||||
```http
|
||||
POST /api/notifications.php?action=mark_read
|
||||
Content-Type: application/json
|
||||
|
||||
{"id": 123}
|
||||
```
|
||||
|
||||
Or mark multiple:
|
||||
```json
|
||||
{"id": "123,124,125"}
|
||||
```
|
||||
|
||||
#### Mark All as Read
|
||||
|
||||
```http
|
||||
POST /api/notifications.php?action=mark_all_read
|
||||
```
|
||||
|
||||
#### Delete Notification
|
||||
|
||||
```http
|
||||
DELETE /api/notifications.php?id=123
|
||||
```
|
||||
|
||||
### User Preferences
|
||||
|
||||
#### Get Preferences
|
||||
|
||||
```http
|
||||
GET /api/notification_preferences.php
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"email_comments": 1,
|
||||
"email_likes": 1,
|
||||
"email_subscribes": 1,
|
||||
"email_uploads": 1,
|
||||
"email_mentions": 1,
|
||||
"email_digest": "instant",
|
||||
"push_enabled": 1,
|
||||
"push_comments": 1,
|
||||
"push_likes": 1,
|
||||
"push_subscribes": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Preferences
|
||||
|
||||
```http
|
||||
POST /api/notification_preferences.php
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email_digest": "daily",
|
||||
"email_likes": 0,
|
||||
"push_comments": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Email Digest Modes
|
||||
|
||||
Users can choose how often they receive emails:
|
||||
|
||||
- **instant** - Send immediately when notification is created
|
||||
- **hourly** - Batch and send once per hour
|
||||
- **daily** - Send once per day (at midnight)
|
||||
- **weekly** - Send once per week (Monday)
|
||||
- **never** - No email notifications
|
||||
|
||||
## Notification Types
|
||||
|
||||
| Type | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `comment` | New comment on video or reply to comment | "John commented on your video" |
|
||||
| `like` | Someone liked a video | "Sarah liked your video" |
|
||||
| `subscribe` | New channel subscriber | "Mike subscribed to your channel" |
|
||||
| `video_upload` | Subscribed channel uploaded new video | "TechChannel uploaded a new video" |
|
||||
| `mention` | User mentioned in a comment | "@username mentioned you" |
|
||||
| `system` | Admin/system messages | "Your video was approved" |
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
The notification widget provides:
|
||||
- Real-time bell icon with unread count badge
|
||||
- Dropdown with last 20 notifications
|
||||
- Mark individual or all as read
|
||||
- Auto-polling every 30 seconds
|
||||
- Click notification to navigate and mark as read
|
||||
|
||||
### JavaScript API
|
||||
|
||||
```javascript
|
||||
// Access the widget instance
|
||||
const widget = window.notificationWidget;
|
||||
|
||||
// Manually fetch notifications
|
||||
widget.fetchNotifications();
|
||||
|
||||
// Manually fetch unread count
|
||||
widget.fetchUnreadCount();
|
||||
|
||||
// Change poll interval (milliseconds)
|
||||
widget.pollInterval = 60000; // 1 minute
|
||||
widget.stopPolling();
|
||||
widget.startPolling();
|
||||
|
||||
// Programmatically mark as read
|
||||
widget.markAsRead(notification_id);
|
||||
|
||||
// Mark all as read
|
||||
widget.markAllAsRead();
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### db_notifications
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| notification_id | INT | Primary key |
|
||||
| usr_id | INT | User receiving notification |
|
||||
| type | ENUM | Notification type |
|
||||
| title | VARCHAR(255) | Short title |
|
||||
| message | TEXT | Optional detailed message |
|
||||
| link | VARCHAR(512) | URL to navigate to |
|
||||
| actor_id | INT | User who triggered notification |
|
||||
| related_video_id | INT | Associated video |
|
||||
| related_comment_id | INT | Associated comment |
|
||||
| is_read | TINYINT | Read status |
|
||||
| is_seen | TINYINT | Seen status (viewed in UI) |
|
||||
| email_sent | TINYINT | Email delivery status |
|
||||
| created_at | DATETIME | Creation timestamp |
|
||||
| read_at | DATETIME | When marked as read |
|
||||
|
||||
### db_notification_preferences
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| usr_id | INT | User ID (unique) |
|
||||
| email_comments | TINYINT | Email for comments |
|
||||
| email_likes | TINYINT | Email for likes |
|
||||
| email_subscribes | TINYINT | Email for subscribes |
|
||||
| email_uploads | TINYINT | Email for new uploads |
|
||||
| email_mentions | TINYINT | Email for mentions |
|
||||
| email_digest | ENUM | Digest frequency |
|
||||
| push_enabled | TINYINT | Push notifications enabled |
|
||||
| push_comments | TINYINT | Push for comments |
|
||||
| push_likes | TINYINT | Push for likes |
|
||||
| push_subscribes | TINYINT | Push for subscribes |
|
||||
|
||||
### db_email_queue
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| queue_id | INT | Primary key |
|
||||
| usr_id | INT | Recipient user |
|
||||
| email | VARCHAR(255) | Email address |
|
||||
| subject | VARCHAR(255) | Email subject |
|
||||
| body | TEXT | Email HTML body |
|
||||
| scheduled_for | DATETIME | When to send |
|
||||
| sent_at | DATETIME | Actual send time |
|
||||
| attempts | INT | Send attempts |
|
||||
| last_error | TEXT | Last error message |
|
||||
|
||||
## Performance
|
||||
|
||||
- **Polling:** Lightweight count query every 30s (~1-2ms)
|
||||
- **Dropdown:** Full notification list only when opened
|
||||
- **Email queue:** Batched processing reduces database load
|
||||
- **Auto-cleanup:** Old read notifications purged after 90 days
|
||||
|
||||
## Cleanup
|
||||
|
||||
The cron script automatically cleans up:
|
||||
- Read notifications older than 90 days
|
||||
- Sent emails from queue
|
||||
|
||||
Adjust retention in `app_scripts/cron/process_notification_emails.php`:
|
||||
|
||||
```php
|
||||
$notifications->cleanup(90); // Days to keep
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Notifications not appearing
|
||||
|
||||
1. Check database tables exist
|
||||
2. Verify JavaScript is loaded: `window.notificationWidget`
|
||||
3. Check browser console for errors
|
||||
4. Verify user is authenticated (session active)
|
||||
|
||||
### Emails not sending
|
||||
|
||||
1. Check cron job is running
|
||||
2. View email queue: `SELECT * FROM db_email_queue WHERE sent_at IS NULL`
|
||||
3. Check email configuration in `class.notifications.php`
|
||||
4. View logs: `docker-compose logs php | grep email`
|
||||
|
||||
### High polling load
|
||||
|
||||
Increase poll interval in the frontend:
|
||||
|
||||
```javascript
|
||||
// In notifications.js
|
||||
this.pollInterval = 60000; // 60 seconds instead of 30
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] WebSocket/SSE for true real-time updates (eliminate polling)
|
||||
- [ ] Push notifications (web push API)
|
||||
- [ ] Mobile app push notifications (FCM/APNS)
|
||||
- [ ] Notification grouping (e.g., "5 people liked your video")
|
||||
- [ ] Rich notification preview with thumbnails
|
||||
- [ ] Notification filtering/search
|
||||
- [ ] Mark as unread
|
||||
- [ ] Snooze notifications
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Web Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
|
||||
- [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
||||
- [Email Best Practices](https://developers.google.com/gmail/design/reference/supported_css)
|
||||
Reference in New Issue
Block a user